Skip to content

Commit

Permalink
Merge pull request #455 from CityOfNewYork/add_wordfence_login_securi…
Browse files Browse the repository at this point in the history
…ty_plugin

added wordfence login security plugin
  • Loading branch information
mithunpandeti committed Aug 1, 2024
2 parents 9e67fcd + ac823cd commit 459d85b
Show file tree
Hide file tree
Showing 198 changed files with 14,605 additions and 47,549 deletions.
16 changes: 16 additions & 0 deletions wp-content/plugins/wordfence-login-security/classes/.htaccess
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} \.php$
RewriteRule .* - [F,L,NC]
</IfModule>
<IfModule !mod_rewrite.c>
<FilesMatch "\.php$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
<IfModule !mod_authz_core.c>
Order deny,allow
Deny from all
</IfModule>
</FilesMatch>
</IfModule>

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<?php

namespace WordfenceLS;

class Controller_CAPTCHA {
const RESPONSE_MODE_ALLOW = 'allow';
const RESPONSE_MODE_REQUIRE_VERIFICATION = 'verify';

const RECAPTCHA_ENDPOINT = 'https://www.google.com/recaptcha/api/siteverify';

/**
* Returns the singleton Controller_CAPTCHA.
*
* @return Controller_CAPTCHA
*/
public static function shared() {
static $_shared = null;
if ($_shared === null) {
$_shared = new Controller_CAPTCHA();
}
return $_shared;
}

/**
* Returns whether or not the authentication CAPTCHA is enabled.
*
* @return bool
*/
public function enabled() {
$key = $this->site_key();
$secret = $this->_secret();
return Controller_Settings::shared()->get_bool(Controller_Settings::OPTION_ENABLE_AUTH_CAPTCHA) && !empty($key) && !empty($secret);
}

/**
* Returns the public reCAPTCHA key if set.
*
* @return string|bool
*/
public function site_key() {
return Controller_Settings::shared()->get(Controller_Settings::OPTION_RECAPTCHA_SITE_KEY);
}

/**
* Returns the private reCAPTCHA secret if set.
*
* @return string|bool
*/
protected function _secret() {
return Controller_Settings::shared()->get(Controller_Settings::OPTION_RECAPTCHA_SECRET);
}

/**
* Returns the bot/human threshold for comparing the score against, defaulting to 0.5.
*
* @return float
*/
public function threshold() {
return max(0.1, Controller_Settings::shared()->get_float(Controller_Settings::OPTION_RECAPTCHA_THRESHOLD, 0.5));
}

/**
* Determine whether or not test mode for reCAPTCHA is enabled
*
* @return bool
*/
public function test_mode() {
return Controller_Settings::shared()->get_bool(\WordfenceLS\Controller_Settings::OPTION_CAPTCHA_TEST_MODE);
}

/**
* Queries the reCAPTCHA endpoint with the given token, verifies the action matches, and returns the corresponding
* score. If validation fails, false is returned. Any other failure (e.g., mangled response or connection dropped) returns 0.0.
*
* @param string $token
* @param string $action
* @param int $timeout
* @return float|false
*/
public function score($token, $action = 'login', $timeout = 10) {
try {
$payload = array(
'secret' => $this->_secret(),
'response' => $token,
'remoteip' => Model_Request::current()->ip(),
);

$response = wp_remote_post(self::RECAPTCHA_ENDPOINT,
array(
'body' => $payload,
'headers' => array(
'Referer' => false,
),
'timeout' => $timeout,
'blocking' => true,
));

if (!is_wp_error($response)) {
$jsonResponse = wp_remote_retrieve_body($response);
$decoded = @json_decode($jsonResponse, true);
if (is_array($decoded) && isset($decoded['success'])) {
if ($decoded['success']) {
if (isset($decoded['score']) && isset($decoded['action']) && $decoded['action'] == $action) {
return (float) $decoded['score'];
}
}
return false;
}
}
}
catch (\Exception $e) {
//Fall through
}

return 0.0;
}

/**
* Returns true if the score is >= the threshold to be considered a human request.
*
* @param float $score
* @return bool
*/
public function is_human($score) {
if ($this->test_mode()) {
return true;
}

$threshold = $this->threshold();
return ($score >= $threshold || abs($score - $threshold) < 0.0001);
}

/**
* Check if the current request is an XML RPC request
* @return bool
*/
private static function is_xml_rpc() {
return defined('XMLRPC_REQUEST') && XMLRPC_REQUEST;
}

/**
* Check if captcha is required for the current request
* @return bool
*/
public function is_captcha_required() {
$required = $this->enabled() && !self::is_xml_rpc();
return apply_filters('wordfence_ls_require_captcha', $required);
}

/**
* Get the captcha token provided with the current request
* @param string $key if specified, override the default token parameter
* @return string|null the captcha token, if present, null otherwise
*/
public function get_token($key = 'wfls-captcha-token') {
return (isset($_POST[$key]) && is_string($_POST[$key]) && !empty($_POST[$key]) ? $_POST[$key] : null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<?php
173 changes: 173 additions & 0 deletions wp-content/plugins/wordfence-login-security/classes/controller/db.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php

namespace WordfenceLS;

use RuntimeException;

class Controller_DB {
const TABLE_2FA_SECRETS = 'wfls_2fa_secrets';
const TABLE_SETTINGS = 'wfls_settings';
const TABLE_ROLE_COUNTS = 'wfls_role_counts';
const TABLE_ROLE_COUNTS_TEMPORARY = 'wfls_role_counts_temporary';

const SCHEMA_VERSION = 2;

/**
* Returns the singleton Controller_DB.
*
* @return Controller_DB
*/
public static function shared() {
static $_shared = null;
if ($_shared === null) {
$_shared = new Controller_DB();
}
return $_shared;
}

/**
* Returns the table prefix for the main site on multisites and the site itself on single site installations.
*
* @return string
*/
public static function network_prefix() {
global $wpdb;
return $wpdb->base_prefix;
}

/**
* Returns the table with the site (single site installations) or network (multisite) prefix added.
*
* @param string $table
* @return string
*/
public static function network_table($table) {
return self::network_prefix() . $table;
}

public function __get($key) {
switch ($key) {
case 'secrets':
return self::network_table(self::TABLE_2FA_SECRETS);
case 'settings':
return self::network_table(self::TABLE_SETTINGS);
case 'role_counts':
return self::network_table(self::TABLE_ROLE_COUNTS);
case 'role_counts_temporary':
return self::network_table(self::TABLE_ROLE_COUNTS_TEMPORARY);
}

throw new \OutOfBoundsException('Unknown key: ' . $key);
}

public function install() {
$this->_create_schema();

global $wpdb;
$table = $this->secrets;
$wpdb->query($wpdb->prepare("UPDATE `{$table}` SET `vtime` = LEAST(`vtime`, %d)", Controller_Time::time()));
}

public function uninstall() {
$tables = array(self::TABLE_2FA_SECRETS, self::TABLE_SETTINGS, self::TABLE_ROLE_COUNTS);
foreach ($tables as $table) {
global $wpdb;
$wpdb->query('DROP TABLE IF EXISTS `' . self::network_table($table) . '`');
}
}

private function create_table($name, $definition, $temporary = false) {
global $wpdb;
if (is_array($definition)) {
foreach ($definition as $attempt) {
if ($this->create_table($name, $attempt, $temporary))
return true;
}
return false;
}
else {
return $wpdb->query('CREATE ' . ($temporary ? 'TEMPORARY ' : '') . 'TABLE IF NOT EXISTS `' . self::network_table($name) . '` ' . $definition);
}
}

private function create_temporary_table($name, $definition) {
if (Controller_Settings::shared()->get_bool(Controller_Settings::OPTION_DISABLE_TEMPORARY_TABLES))
return false;
if ($this->create_table($name, $definition, true))
return true;
Controller_Settings::shared()->set(Controller_Settings::OPTION_DISABLE_TEMPORARY_TABLES, true);
return false;
}

private function get_role_counts_table_definition($engine = null) {
$engineClause = $engine === null ? '' : "ENGINE={$engine}";
return <<<SQL
(
serialized_roles VARBINARY(255) NOT NULL,
two_factor_inactive TINYINT(1) NOT NULL,
user_count BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (serialized_roles, two_factor_inactive)
) {$engineClause};
SQL;
}

private function get_role_counts_table_definition_options() {
return array(
$this->get_role_counts_table_definition('MEMORY'),
$this->get_role_counts_table_definition('MyISAM'),
$this->get_role_counts_table_definition()
);
}

protected function _create_schema() {
$tables = array(
self::TABLE_2FA_SECRETS => '(
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`user_id` bigint(20) unsigned NOT NULL,
`secret` tinyblob NOT NULL,
`recovery` blob NOT NULL,
`ctime` int(10) unsigned NOT NULL,
`vtime` int(10) unsigned NOT NULL,
`mode` enum(\'authenticator\') NOT NULL DEFAULT \'authenticator\',
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;',
self::TABLE_SETTINGS => '(
`name` varchar(191) NOT NULL DEFAULT \'\',
`value` longblob,
`autoload` enum(\'no\',\'yes\') NOT NULL DEFAULT \'yes\',
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;',
self::TABLE_ROLE_COUNTS => $this->get_role_counts_table_definition_options()
);

foreach ($tables as $table => $def) {
$this->create_table($table, $def);
}

Controller_Settings::shared()->set(Controller_Settings::OPTION_SCHEMA_VERSION, self::SCHEMA_VERSION);
}

public function require_schema_version($version) {
$current = Controller_Settings::shared()->get_int(Controller_Settings::OPTION_SCHEMA_VERSION);
if ($current < $version) {
$this->install();
}
}

public function query($query) {
global $wpdb;
if ($wpdb->query($query) === false)
throw new RuntimeException("Failed to execute query: {$query}");
}

public function get_wpdb() {
global $wpdb;
return $wpdb;
}

public function create_temporary_role_counts_table() {
return $this->create_temporary_table(self::TABLE_ROLE_COUNTS_TEMPORARY, $this->get_role_counts_table_definition_options());
}

}
Loading

0 comments on commit 459d85b

Please sign in to comment.