first commit
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_2faInitializationData {
|
||||
|
||||
private $user;
|
||||
private $raw_secret;
|
||||
private $base32_secret;
|
||||
private $otp_url;
|
||||
private $recovery_codes;
|
||||
|
||||
public function __construct($user) {
|
||||
$this->user = $user;
|
||||
$this->raw_secret = Model_Crypto::random_bytes(20);
|
||||
}
|
||||
|
||||
public function get_user() {
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function get_raw_secret() {
|
||||
return $this->raw_secret;
|
||||
}
|
||||
|
||||
public function get_base32_secret() {
|
||||
if ($this->base32_secret === null)
|
||||
$this->base32_secret = Utility_BaseConversion::base32_encode($this->raw_secret);
|
||||
return $this->base32_secret;
|
||||
}
|
||||
|
||||
private function generate_otp_url() {
|
||||
return "otpauth://totp/" . rawurlencode(preg_replace('~^https?://~i', '', home_url()) . ' (' . $this->user->user_login . ')') . '?secret=' . $this->get_base32_secret() . '&algorithm=SHA1&digits=6&period=30&issuer=Wordfence';
|
||||
}
|
||||
|
||||
public function get_otp_url() {
|
||||
if ($this->otp_url === null)
|
||||
$this->otp_url = $this->generate_otp_url();
|
||||
return $this->otp_url;
|
||||
}
|
||||
|
||||
public function get_recovery_codes() {
|
||||
if ($this->recovery_codes === null)
|
||||
$this->recovery_codes = Controller_Users::shared()->regenerate_recovery_codes();
|
||||
return $this->recovery_codes;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
abstract class Model_Asset {
|
||||
|
||||
protected $handle;
|
||||
protected $source;
|
||||
protected $dependencies;
|
||||
protected $version;
|
||||
protected $registered = false;
|
||||
|
||||
public function __construct($handle, $source = '', $dependencies = array(), $version = false) {
|
||||
$this->handle = $handle;
|
||||
$this->source = $source;
|
||||
$this->dependencies = $dependencies;
|
||||
$this->version = $version;
|
||||
}
|
||||
|
||||
public function getSourceUrl() {
|
||||
if (empty($this->source))
|
||||
return null;
|
||||
$url = $this->source;
|
||||
if (is_string($this->version))
|
||||
$url = add_query_arg('ver', $this->version, $this->source);
|
||||
return $url;
|
||||
}
|
||||
|
||||
public abstract function enqueue();
|
||||
|
||||
public abstract function isEnqueued();
|
||||
|
||||
public abstract function renderInline();
|
||||
|
||||
public function renderInlineIfNotEnqueued() {
|
||||
if (!$this->isEnqueued())
|
||||
$this->renderInline();
|
||||
}
|
||||
|
||||
public function setRegistered() {
|
||||
$this->registered = true;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function register() {
|
||||
return $this->setRegistered();
|
||||
}
|
||||
|
||||
public static function js($file) {
|
||||
return self::_pluginBaseURL() . 'js/' . self::_versionedFileName($file);
|
||||
}
|
||||
|
||||
public static function css($file) {
|
||||
return self::_pluginBaseURL() . 'css/' . self::_versionedFileName($file);
|
||||
}
|
||||
|
||||
public static function img($file) {
|
||||
return self::_pluginBaseURL() . 'img/' . $file;
|
||||
}
|
||||
|
||||
protected static function _pluginBaseURL() {
|
||||
return plugins_url('', WORDFENCE_LS_FCPATH) . '/';
|
||||
}
|
||||
|
||||
protected static function _versionedFileName($subpath) {
|
||||
$version = WORDFENCE_LS_BUILD_NUMBER;
|
||||
if ($version != 'WORDFENCE_LS_BUILD_NUMBER' && preg_match('/^(.+?)(\.[^\.]+)$/', $subpath, $matches)) {
|
||||
$prefix = $matches[1];
|
||||
$suffix = $matches[2];
|
||||
return $prefix . '.' . $version . $suffix;
|
||||
}
|
||||
|
||||
return $subpath;
|
||||
}
|
||||
|
||||
public static function create($handle, $source = '', $dependencies = array(), $version = false) {
|
||||
return new static($handle, $source, $dependencies, $version);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_Compat {
|
||||
public static function hex2bin($string) { //Polyfill for PHP < 5.4
|
||||
if (!is_string($string)) { return false; }
|
||||
if (strlen($string) % 2 == 1) { return false; }
|
||||
return pack('H*', $string);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
abstract class Model_Crypto {
|
||||
/**
|
||||
* Refreshes the secrets used by the plugin.
|
||||
*/
|
||||
public static function refresh_secrets() {
|
||||
Controller_Settings::shared()->set(Controller_Settings::OPTION_SHARED_HASH_SECRET_KEY, bin2hex(self::random_bytes(32)));
|
||||
Controller_Settings::shared()->set(Controller_Settings::OPTION_SHARED_SYMMETRIC_SECRET_KEY, bin2hex(self::random_bytes(32)));
|
||||
Controller_Settings::shared()->set(Controller_Settings::OPTION_LAST_SECRET_REFRESH, Controller_Time::time(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secret for hashing.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function shared_hash_secret() {
|
||||
return Controller_Settings::shared()->get(Controller_Settings::OPTION_SHARED_HASH_SECRET_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the secret for symmetric encryption.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function shared_symmetric_secret() {
|
||||
return Controller_Settings::shared()->get(Controller_Settings::OPTION_SHARED_SYMMETRIC_SECRET_KEY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the installation has the required crypto support for this to work.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_required_crypto_functions() {
|
||||
if (function_exists('openssl_get_publickey') && function_exists('openssl_get_cipher_methods')) {
|
||||
$ciphers = openssl_get_cipher_methods();
|
||||
return array_search('aes-256-cbc', $ciphers) !== false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility
|
||||
*/
|
||||
|
||||
public static function random_bytes($bytes) {
|
||||
$bytes = (int) $bytes;
|
||||
if (function_exists('random_bytes')) {
|
||||
try {
|
||||
$rand = random_bytes($bytes);
|
||||
if (is_string($rand) && self::strlen($rand) === $bytes) {
|
||||
return $rand;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
// Fall through
|
||||
} catch (\TypeError $e) {
|
||||
// Fall through
|
||||
} catch (\Error $e) {
|
||||
// Fall through
|
||||
}
|
||||
}
|
||||
if (function_exists('mcrypt_create_iv')) {
|
||||
// phpcs:ignore PHPCompatibility.FunctionUse.RemovedFunctions.mcrypt_create_ivDeprecatedRemoved,PHPCompatibility.Extensions.RemovedExtensions.mcryptDeprecatedRemoved,PHPCompatibility.Constants.RemovedConstants.mcrypt_dev_urandomDeprecatedRemoved
|
||||
$rand = @mcrypt_create_iv($bytes, MCRYPT_DEV_URANDOM);
|
||||
if (is_string($rand) && self::strlen($rand) === $bytes) {
|
||||
return $rand;
|
||||
}
|
||||
}
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
$rand = @openssl_random_pseudo_bytes($bytes, $strong);
|
||||
if (is_string($rand) && self::strlen($rand) === $bytes) {
|
||||
return $rand;
|
||||
}
|
||||
}
|
||||
// Last resort is insecure
|
||||
$return = '';
|
||||
for ($i = 0; $i < $bytes; $i++) {
|
||||
$return .= chr(mt_rand(0, 255));
|
||||
}
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Polyfill for random_int.
|
||||
*
|
||||
* @param int $min
|
||||
* @param int $max
|
||||
* @return int
|
||||
*/
|
||||
public static function random_int($min = 0, $max = 0x7FFFFFFF) {
|
||||
if (function_exists('random_int')) {
|
||||
try {
|
||||
return random_int($min, $max);
|
||||
} catch (\Exception $e) {
|
||||
// Fall through
|
||||
} catch (\TypeError $e) {
|
||||
// Fall through
|
||||
} catch (\Error $e) {
|
||||
// Fall through
|
||||
}
|
||||
}
|
||||
$diff = $max - $min;
|
||||
$bytes = self::random_bytes(4);
|
||||
if ($bytes === false || self::strlen($bytes) != 4) {
|
||||
throw new \RuntimeException("Unable to get 4 bytes");
|
||||
}
|
||||
$val = @unpack("Nint", $bytes);
|
||||
$val = $val['int'] & 0x7FFFFFFF;
|
||||
$fp = (float) $val / 2147483647.0; // convert to [0,1]
|
||||
return (int) (round($fp * $diff) + $min);
|
||||
}
|
||||
|
||||
public static function uuid() {
|
||||
return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
|
||||
// 32 bits for "time_low"
|
||||
self::random_int(0, 0xffff), self::random_int(0, 0xffff),
|
||||
|
||||
// 16 bits for "time_mid"
|
||||
self::random_int(0, 0xffff),
|
||||
|
||||
// 16 bits for "time_hi_and_version",
|
||||
// four most significant bits holds version number 4
|
||||
self::random_int(0, 0x0fff) | 0x4000,
|
||||
|
||||
// 16 bits, 8 bits for "clk_seq_hi_res",
|
||||
// 8 bits for "clk_seq_low",
|
||||
// two most significant bits holds zero and one for variant DCE1.1
|
||||
self::random_int(0, 0x3fff) | 0x8000,
|
||||
|
||||
// 48 bits for "node"
|
||||
self::random_int(0, 0xffff), self::random_int(0, 0xffff), self::random_int(0, 0xffff)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the mbstring internal encoding to a binary safe encoding when func_overload
|
||||
* is enabled.
|
||||
*
|
||||
* When mbstring.func_overload is in use for multi-byte encodings, the results from
|
||||
* strlen() and similar functions respect the utf8 characters, causing binary data
|
||||
* to return incorrect lengths.
|
||||
*
|
||||
* This function overrides the mbstring encoding to a binary-safe encoding, and
|
||||
* resets it to the users expected encoding afterwards through the
|
||||
* `reset_mbstring_encoding` function.
|
||||
*
|
||||
* It is safe to recursively call this function, however each
|
||||
* `_mbstring_binary_safe_encoding()` call must be followed up with an equal number
|
||||
* of `_reset_mbstring_encoding()` calls.
|
||||
*
|
||||
* @see Model_Crypto::_reset_mbstring_encoding
|
||||
*
|
||||
* @staticvar array $encodings
|
||||
* @staticvar bool $overloaded
|
||||
*
|
||||
* @param bool $reset Optional. Whether to reset the encoding back to a previously-set encoding.
|
||||
* Default false.
|
||||
*/
|
||||
protected static function _mbstring_binary_safe_encoding($reset = false) {
|
||||
static $encodings = array();
|
||||
static $overloaded = null;
|
||||
|
||||
if (is_null($overloaded)) {
|
||||
// phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.mbstring_func_overloadDeprecated
|
||||
$overloaded = function_exists('mb_internal_encoding') && (ini_get('mbstring.func_overload') & 2);
|
||||
}
|
||||
|
||||
if (false === $overloaded) { return; }
|
||||
|
||||
if (!$reset) {
|
||||
$encoding = mb_internal_encoding();
|
||||
array_push($encodings, $encoding);
|
||||
mb_internal_encoding('ISO-8859-1');
|
||||
}
|
||||
|
||||
if ($reset && $encodings) {
|
||||
$encoding = array_pop($encodings);
|
||||
mb_internal_encoding($encoding);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the mbstring internal encoding to a users previously set encoding.
|
||||
*
|
||||
* @see Model_Crypto::_mbstring_binary_safe_encoding
|
||||
*/
|
||||
protected static function _reset_mbstring_encoding() {
|
||||
self::_mbstring_binary_safe_encoding(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $function
|
||||
* @param array $args
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function _call_mb_string_function($function, $args) {
|
||||
self::_mbstring_binary_safe_encoding();
|
||||
$return = call_user_func_array($function, $args);
|
||||
self::_reset_mbstring_encoding();
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multibyte safe strlen.
|
||||
*
|
||||
* @param $binary
|
||||
* @return int
|
||||
*/
|
||||
public static function strlen($binary) {
|
||||
$args = func_get_args();
|
||||
return self::_call_mb_string_function('strlen', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $haystack
|
||||
* @param $needle
|
||||
* @param int $offset
|
||||
* @return int
|
||||
*/
|
||||
public static function stripos($haystack, $needle, $offset = 0) {
|
||||
$args = func_get_args();
|
||||
return self::_call_mb_string_function('stripos', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
* @return mixed
|
||||
*/
|
||||
public static function strtolower($string) {
|
||||
$args = func_get_args();
|
||||
return self::_call_mb_string_function('strtolower', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
* @param $start
|
||||
* @param $length
|
||||
* @return mixed
|
||||
*/
|
||||
public static function substr($string, $start, $length = null) {
|
||||
if ($length === null) { $length = self::strlen($string); }
|
||||
return self::_call_mb_string_function('substr', array(
|
||||
$string, $start, $length
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $haystack
|
||||
* @param $needle
|
||||
* @param int $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public static function strpos($haystack, $needle, $offset = 0) {
|
||||
$args = func_get_args();
|
||||
return self::_call_mb_string_function('strpos', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @param int $offset
|
||||
* @param int $length
|
||||
* @return mixed
|
||||
*/
|
||||
public static function substr_count($haystack, $needle, $offset = 0, $length = null) {
|
||||
if ($length === null) { $length = self::strlen($haystack); }
|
||||
return self::_call_mb_string_function('substr_count', array(
|
||||
$haystack, $needle, $offset, $length
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $string
|
||||
* @return mixed
|
||||
*/
|
||||
public static function strtoupper($string) {
|
||||
$args = func_get_args();
|
||||
return self::_call_mb_string_function('strtoupper', $args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $haystack
|
||||
* @param string $needle
|
||||
* @param int $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public static function strrpos($haystack, $needle, $offset = 0) {
|
||||
$args = func_get_args();
|
||||
return self::_call_mb_string_function('strrpos', $args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\Crypto;
|
||||
/**
|
||||
* Binary-to-text PHP Utilities
|
||||
*
|
||||
* @package binary-to-text-php
|
||||
* @link https://github.com/ademarre/binary-to-text-php
|
||||
* @author Andre DeMarre
|
||||
* @copyright 2009-2013 Andre DeMarre
|
||||
* @license http://opensource.org/licenses/MIT MIT
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class for binary-to-text encoding with a base of 2^n
|
||||
*
|
||||
* The Base2n class is for binary-to-text conversion. It employs a
|
||||
* generalization of the algorithms used by many encoding schemes that
|
||||
* use a fixed number of bits to encode each character. In other words,
|
||||
* the base is a power of 2.
|
||||
*
|
||||
* Earlier versions of this class were named
|
||||
* FixedBitNotation and FixedBitEncoding.
|
||||
*
|
||||
* @package binary-to-text-php
|
||||
*/
|
||||
class Model_Base2n
|
||||
{
|
||||
protected $_chars;
|
||||
protected $_bitsPerCharacter;
|
||||
protected $_radix;
|
||||
protected $_rightPadFinalBits;
|
||||
protected $_padFinalGroup;
|
||||
protected $_padCharacter;
|
||||
protected $_caseSensitive;
|
||||
protected $_charmap;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param integer $bitsPerCharacter Bits to use for each encoded character
|
||||
* @param string $chars Base character alphabet
|
||||
* @param boolean $caseSensitive To decode in a case-sensitive manner
|
||||
* @param boolean $rightPadFinalBits How to encode last character
|
||||
* @param boolean $padFinalGroup Add padding to end of encoded output
|
||||
* @param string $padCharacter Character to use for padding
|
||||
*
|
||||
* @throws InvalidArgumentException for incompatible parameters
|
||||
*/
|
||||
public function __construct(
|
||||
$bitsPerCharacter,
|
||||
$chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_',
|
||||
$caseSensitive = TRUE, $rightPadFinalBits = FALSE,
|
||||
$padFinalGroup = FALSE, $padCharacter = '=')
|
||||
{
|
||||
// Ensure validity of $chars
|
||||
if (!is_string($chars) || ($charLength = strlen($chars)) < 2) {
|
||||
throw new \InvalidArgumentException('$chars must be a string of at least two characters');
|
||||
}
|
||||
|
||||
// Ensure validity of $padCharacter
|
||||
if ($padFinalGroup) {
|
||||
if (!is_string($padCharacter) || !isset($padCharacter[0])) {
|
||||
throw new \InvalidArgumentException('$padCharacter must be a string of one character');
|
||||
}
|
||||
|
||||
if ($caseSensitive) {
|
||||
$padCharFound = strpos($chars, $padCharacter[0]);
|
||||
} else {
|
||||
$padCharFound = stripos($chars, $padCharacter[0]);
|
||||
}
|
||||
|
||||
if ($padCharFound !== FALSE) {
|
||||
throw new \InvalidArgumentException('$padCharacter can not be a member of $chars');
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure validity of $bitsPerCharacter
|
||||
if (!is_int($bitsPerCharacter)) {
|
||||
throw new \InvalidArgumentException('$bitsPerCharacter must be an integer');
|
||||
}
|
||||
|
||||
if ($bitsPerCharacter < 1) {
|
||||
// $bitsPerCharacter must be at least 1
|
||||
throw new \InvalidArgumentException('$bitsPerCharacter can not be less than 1');
|
||||
|
||||
} elseif ($charLength < 1 << $bitsPerCharacter) {
|
||||
// Character length of $chars is too small for $bitsPerCharacter
|
||||
// Find greatest acceptable value of $bitsPerCharacter
|
||||
$bitsPerCharacter = 1;
|
||||
$radix = 2;
|
||||
|
||||
while ($charLength >= ($radix <<= 1) && $bitsPerCharacter < 8) {
|
||||
$bitsPerCharacter++;
|
||||
}
|
||||
|
||||
$radix >>= 1;
|
||||
throw new \InvalidArgumentException(
|
||||
'$bitsPerCharacter can not be more than ' . $bitsPerCharacter
|
||||
. ' given $chars length of ' . $charLength
|
||||
. ' (max radix ' . $radix . ')');
|
||||
|
||||
} elseif ($bitsPerCharacter > 8) {
|
||||
// $bitsPerCharacter must not be greater than 8
|
||||
throw new \InvalidArgumentException('$bitsPerCharacter can not be greater than 8');
|
||||
|
||||
} else {
|
||||
$radix = 1 << $bitsPerCharacter;
|
||||
}
|
||||
|
||||
$this->_chars = $chars;
|
||||
$this->_bitsPerCharacter = $bitsPerCharacter;
|
||||
$this->_radix = $radix;
|
||||
$this->_rightPadFinalBits = $rightPadFinalBits;
|
||||
$this->_padFinalGroup = $padFinalGroup;
|
||||
$this->_padCharacter = $padCharacter[0];
|
||||
$this->_caseSensitive = $caseSensitive;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a string
|
||||
*
|
||||
* @param string $rawString Binary data to encode
|
||||
* @return string
|
||||
*/
|
||||
public function encode($rawString)
|
||||
{
|
||||
// Unpack string into an array of bytes
|
||||
$bytes = unpack('C*', $rawString);
|
||||
$byteCount = count($bytes);
|
||||
|
||||
$encodedString = '';
|
||||
$byte = array_shift($bytes);
|
||||
$bitsRead = 0;
|
||||
$oldBits = 0;
|
||||
|
||||
$chars = $this->_chars;
|
||||
$bitsPerCharacter = $this->_bitsPerCharacter;
|
||||
$rightPadFinalBits = $this->_rightPadFinalBits;
|
||||
$padFinalGroup = $this->_padFinalGroup;
|
||||
$padCharacter = $this->_padCharacter;
|
||||
|
||||
$charsPerByte = 8 / $bitsPerCharacter;
|
||||
$encodedLength = $byteCount * $charsPerByte;
|
||||
|
||||
// Generate encoded output; each loop produces one encoded character
|
||||
for ($c = 0; $c < $encodedLength; $c++) {
|
||||
|
||||
// Get the bits needed for this encoded character
|
||||
if ($bitsRead + $bitsPerCharacter > 8) {
|
||||
// Not enough bits remain in this byte for the current character
|
||||
// Save the remaining bits before getting the next byte
|
||||
$oldBitCount = 8 - $bitsRead;
|
||||
$oldBits = $byte ^ ($byte >> $oldBitCount << $oldBitCount);
|
||||
$newBitCount = $bitsPerCharacter - $oldBitCount;
|
||||
|
||||
if (!$bytes) {
|
||||
// Last bits; match final character and exit loop
|
||||
if ($rightPadFinalBits) $oldBits <<= $newBitCount;
|
||||
$encodedString .= $chars[$oldBits];
|
||||
|
||||
if ($padFinalGroup) {
|
||||
// Array of the lowest common multiples of $bitsPerCharacter and 8, divided by 8
|
||||
$lcmMap = array(1 => 1, 2 => 1, 3 => 3, 4 => 1, 5 => 5, 6 => 3, 7 => 7, 8 => 1);
|
||||
$bytesPerGroup = $lcmMap[$bitsPerCharacter];
|
||||
$pads = $bytesPerGroup * $charsPerByte - ceil((strlen($rawString) % $bytesPerGroup) * $charsPerByte);
|
||||
$encodedString .= str_repeat($padCharacter, $pads);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Get next byte
|
||||
$byte = array_shift($bytes);
|
||||
$bitsRead = 0;
|
||||
|
||||
} else {
|
||||
$oldBitCount = 0;
|
||||
$newBitCount = $bitsPerCharacter;
|
||||
}
|
||||
|
||||
// Read only the needed bits from this byte
|
||||
$bits = $byte >> 8 - ($bitsRead + ($newBitCount));
|
||||
$bits ^= $bits >> $newBitCount << $newBitCount;
|
||||
$bitsRead += $newBitCount;
|
||||
|
||||
if ($oldBitCount) {
|
||||
// Bits come from seperate bytes, add $oldBits to $bits
|
||||
$bits = ($oldBits << $newBitCount) | $bits;
|
||||
}
|
||||
|
||||
$encodedString .= $chars[$bits];
|
||||
}
|
||||
|
||||
return $encodedString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a string
|
||||
*
|
||||
* @param string $encodedString Data to decode
|
||||
* @param boolean $strict Returns NULL if $encodedString contains an undecodable character
|
||||
* @return string
|
||||
*/
|
||||
public function decode($encodedString, $strict = FALSE)
|
||||
{
|
||||
if (!$encodedString || !is_string($encodedString)) {
|
||||
// Empty string, nothing to decode
|
||||
return '';
|
||||
}
|
||||
|
||||
$chars = $this->_chars;
|
||||
$bitsPerCharacter = $this->_bitsPerCharacter;
|
||||
$radix = $this->_radix;
|
||||
$rightPadFinalBits = $this->_rightPadFinalBits;
|
||||
$padFinalGroup = $this->_padFinalGroup;
|
||||
$padCharacter = $this->_padCharacter;
|
||||
$caseSensitive = $this->_caseSensitive;
|
||||
|
||||
// Get index of encoded characters
|
||||
if ($this->_charmap) {
|
||||
$charmap = $this->_charmap;
|
||||
|
||||
} else {
|
||||
$charmap = array();
|
||||
|
||||
for ($i = 0; $i < $radix; $i++) {
|
||||
$charmap[$chars[$i]] = $i;
|
||||
}
|
||||
|
||||
$this->_charmap = $charmap;
|
||||
}
|
||||
|
||||
// The last encoded character is $encodedString[$lastNotatedIndex]
|
||||
$lastNotatedIndex = strlen($encodedString) - 1;
|
||||
|
||||
// Remove trailing padding characters
|
||||
if ($padFinalGroup) {
|
||||
while ($encodedString[$lastNotatedIndex] === $padCharacter) {
|
||||
$encodedString = substr($encodedString, 0, $lastNotatedIndex);
|
||||
$lastNotatedIndex--;
|
||||
}
|
||||
}
|
||||
|
||||
$rawString = '';
|
||||
$byte = 0;
|
||||
$bitsWritten = 0;
|
||||
|
||||
// Convert each encoded character to a series of unencoded bits
|
||||
for ($c = 0; $c <= $lastNotatedIndex; $c++) {
|
||||
|
||||
if (!$caseSensitive && !isset($charmap[$encodedString[$c]])) {
|
||||
// Encoded character was not found; try other case
|
||||
if (isset($charmap[$cUpper = strtoupper($encodedString[$c])])) {
|
||||
$charmap[$encodedString[$c]] = $charmap[$cUpper];
|
||||
|
||||
} elseif (isset($charmap[$cLower = strtolower($encodedString[$c])])) {
|
||||
$charmap[$encodedString[$c]] = $charmap[$cLower];
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($charmap[$encodedString[$c]])) {
|
||||
$bitsNeeded = 8 - $bitsWritten;
|
||||
$unusedBitCount = $bitsPerCharacter - $bitsNeeded;
|
||||
|
||||
// Get the new bits ready
|
||||
if ($bitsNeeded > $bitsPerCharacter) {
|
||||
// New bits aren't enough to complete a byte; shift them left into position
|
||||
$newBits = $charmap[$encodedString[$c]] << $bitsNeeded - $bitsPerCharacter;
|
||||
$bitsWritten += $bitsPerCharacter;
|
||||
|
||||
} elseif ($c !== $lastNotatedIndex || $rightPadFinalBits) {
|
||||
// Zero or more too many bits to complete a byte; shift right
|
||||
$newBits = $charmap[$encodedString[$c]] >> $unusedBitCount;
|
||||
$bitsWritten = 8; //$bitsWritten += $bitsNeeded;
|
||||
|
||||
} else {
|
||||
// Final bits don't need to be shifted
|
||||
$newBits = $charmap[$encodedString[$c]];
|
||||
$bitsWritten = 8;
|
||||
}
|
||||
|
||||
$byte |= $newBits;
|
||||
|
||||
if ($bitsWritten === 8 || $c === $lastNotatedIndex) {
|
||||
// Byte is ready to be written
|
||||
$rawString .= pack('C', $byte);
|
||||
|
||||
if ($c !== $lastNotatedIndex) {
|
||||
// Start the next byte
|
||||
$bitsWritten = $unusedBitCount;
|
||||
$byte = ($charmap[$encodedString[$c]] ^ ($newBits << $unusedBitCount)) << 8 - $bitsWritten;
|
||||
}
|
||||
}
|
||||
|
||||
} elseif ($strict) {
|
||||
// Unable to decode character; abort
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return $rawString;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\Crypto;
|
||||
|
||||
use WordfenceLS\Controller_Time;
|
||||
use WordfenceLS\Model_Crypto;
|
||||
|
||||
/**
|
||||
* Class Model_JWT
|
||||
* @package Wordfence2FA\Crypto
|
||||
* @property array $payload
|
||||
* @property int $expiration
|
||||
*/
|
||||
class Model_JWT {
|
||||
private $_payload;
|
||||
private $_expiration;
|
||||
|
||||
/**
|
||||
* Decodes and returns the payload of a JWT. This also validates the signature and expiration. Currently assumes HS256 JWTs.
|
||||
*
|
||||
* @param string $token
|
||||
* @return Model_JWT|bool The decoded JWT or false if the token is invalid or fails validation.
|
||||
*/
|
||||
public static function decode_jwt($token) {
|
||||
$components = explode('.', $token);
|
||||
if (count($components) != 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$key = Model_Crypto::shared_hash_secret();
|
||||
$body = $components[0] . '.' . $components[1];
|
||||
$signature = hash_hmac('sha256', $body, $key, true);
|
||||
$testSignature = self::base64url_decode($components[2]);
|
||||
if (!hash_equals($signature, $testSignature)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$json = self::base64url_decode($components[1]);
|
||||
$payload = @json_decode($json, true);
|
||||
$expiration = false;
|
||||
if (isset($payload['_exp'])) {
|
||||
$expiration = $payload['_exp'];
|
||||
|
||||
if ($payload['_exp'] < Controller_Time::time()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
unset($payload['_exp']);
|
||||
}
|
||||
|
||||
return new self($payload, $expiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model_JWT constructor.
|
||||
*
|
||||
* @param array $payload
|
||||
* @param bool|int $expiration
|
||||
*/
|
||||
public function __construct($payload, $expiration = false) {
|
||||
$this->_payload = $payload;
|
||||
$this->_expiration = $expiration;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
$payload = $this->_payload;
|
||||
if ($this->_expiration !== false) {
|
||||
$payload['_exp'] = $this->_expiration;
|
||||
}
|
||||
$key = Model_Crypto::shared_hash_secret();
|
||||
$header = '{"alg":"HS256","typ":"JWT"}';
|
||||
$body = self::base64url_encode($header) . '.' . self::base64url_encode(json_encode($payload));
|
||||
$signature = hash_hmac('sha256', $body, $key, true);
|
||||
return $body . '.' . self::base64url_encode($signature);
|
||||
}
|
||||
|
||||
public function __isset($key) {
|
||||
switch ($key) {
|
||||
case 'payload':
|
||||
case 'expiration':
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Invalid key: ' . $key);
|
||||
}
|
||||
|
||||
public function __get($key) {
|
||||
switch ($key) {
|
||||
case 'payload':
|
||||
return $this->_payload;
|
||||
case 'expiration':
|
||||
return $this->_expiration;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Invalid key: ' . $key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base64URL-encodes the given payload. This is identical to base64_encode except it substitutes characters
|
||||
* not safe for use in URLs.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return string
|
||||
*/
|
||||
public static function base64url_encode($payload) {
|
||||
return self::base64url_convert_to(base64_encode($payload));
|
||||
}
|
||||
|
||||
public static function base64url_convert_to($base64) {
|
||||
$intermediate = rtrim($base64, '=');
|
||||
$intermediate = str_replace('+', '-', $intermediate);
|
||||
$intermediate = str_replace('/', '_', $intermediate);
|
||||
return $intermediate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64URL-decodes the given payload. This is identical to base64_encode except it allows for the characters
|
||||
* substituted by base64url_encode.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return string
|
||||
*/
|
||||
public static function base64url_decode($payload) {
|
||||
return base64_decode(self::base64url_convert_from($payload));
|
||||
}
|
||||
|
||||
public static function base64url_convert_from($base64url) {
|
||||
$intermediate = str_replace('_', '/', $base64url);
|
||||
$intermediate = str_replace('-', '+', $intermediate);
|
||||
return $intermediate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\Crypto;
|
||||
|
||||
use WordfenceLS\Model_Crypto;
|
||||
|
||||
abstract class Model_Symmetric {
|
||||
/**
|
||||
* Returns $data encrypted with the shared symmetric key or false if unable to do so.
|
||||
*
|
||||
* @param string $data
|
||||
* @return bool|array
|
||||
*/
|
||||
public static function encrypt($data) {
|
||||
if (!Model_Crypto::has_required_crypto_functions()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$symmetricKey = Model_Crypto::shared_symmetric_secret();
|
||||
$iv = Model_Crypto::random_bytes(16);
|
||||
$encrypted = @openssl_encrypt($data, 'aes-256-cbc', $symmetricKey, OPENSSL_RAW_DATA, $iv);
|
||||
if ($encrypted) {
|
||||
return array('data' => base64_encode($encrypted), 'iv' => base64_encode($iv));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the decrypted value of a payload encrypted by Model_Symmetric::encrypt
|
||||
*
|
||||
* @param array $encrypted
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function decrypt($encrypted) {
|
||||
if (!Model_Crypto::has_required_crypto_functions()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($encrypted['data']) || !isset($encrypted['iv'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$symmetricKey = Model_Crypto::shared_symmetric_secret();
|
||||
$iv = base64_decode($encrypted['iv']);
|
||||
$encrypted = base64_decode($encrypted['data']);
|
||||
$data = @openssl_decrypt($encrypted, 'aes-256-cbc', $symmetricKey, OPENSSL_RAW_DATA, $iv);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_IP {
|
||||
/**
|
||||
* Returns the human-readable representation of a packed binary IP address.
|
||||
*
|
||||
* @param string $ip
|
||||
* @return bool|string
|
||||
*/
|
||||
public static function inet_ntop($ip) {
|
||||
if (Model_Crypto::strlen($ip) == 16 && Model_Crypto::substr($ip, 0, 12) == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff") {
|
||||
$ip = Model_Crypto::substr($ip, 12, 4);
|
||||
}
|
||||
|
||||
if (self::has_ipv6()) {
|
||||
return @inet_ntop($ip);
|
||||
}
|
||||
|
||||
// IPv4
|
||||
if (Model_Crypto::strlen($ip) === 4) {
|
||||
return ord($ip[0]) . '.' . ord($ip[1]) . '.' . ord($ip[2]) . '.' . ord($ip[3]);
|
||||
}
|
||||
|
||||
// IPv6
|
||||
if (Model_Crypto::strlen($ip) === 16) {
|
||||
// IPv4 mapped IPv6
|
||||
if (Model_Crypto::substr($ip, 0, 12) == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff") {
|
||||
return "::ffff:" . ord($ip[12]) . '.' . ord($ip[13]) . '.' . ord($ip[14]) . '.' . ord($ip[15]);
|
||||
}
|
||||
|
||||
$hex = bin2hex($ip);
|
||||
$groups = str_split($hex, 4);
|
||||
$in_collapse = false;
|
||||
$done_collapse = false;
|
||||
foreach ($groups as $index => $group) {
|
||||
if ($group == '0000' && !$done_collapse) {
|
||||
if ($in_collapse) {
|
||||
$groups[$index] = '';
|
||||
continue;
|
||||
}
|
||||
$groups[$index] = ':';
|
||||
$in_collapse = true;
|
||||
continue;
|
||||
}
|
||||
if ($in_collapse) {
|
||||
$done_collapse = true;
|
||||
}
|
||||
$groups[$index] = ltrim($groups[$index], '0');
|
||||
if (strlen($groups[$index]) === 0) {
|
||||
$groups[$index] = '0';
|
||||
}
|
||||
}
|
||||
$ip = join(':', array_filter($groups, 'strlen'));
|
||||
$ip = str_replace(':::', '::', $ip);
|
||||
return $ip == ':' ? '::' : $ip;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the packed binary representation of an IP address from the human readable version.
|
||||
*
|
||||
* @param string $ip
|
||||
* @return string
|
||||
*/
|
||||
public static function inet_pton($ip) {
|
||||
if (self::has_ipv6()) {
|
||||
$pton = @inet_pton($ip);
|
||||
if ($pton === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (preg_match('/^(?:\d{1,3}(?:\.|$)){4}/', $ip)) { // IPv4
|
||||
$octets = explode('.', $ip);
|
||||
$pton = chr($octets[0]) . chr($octets[1]) . chr($octets[2]) . chr($octets[3]);
|
||||
}
|
||||
else if (preg_match('/^((?:[\da-f]{1,4}(?::|)){0,8})(::)?((?:[\da-f]{1,4}(?::|)){0,8})$/i', $ip)) { // IPv6
|
||||
if ($ip === '::') {
|
||||
$pton = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
|
||||
}
|
||||
else {
|
||||
$colon_count = substr_count($ip, ':');
|
||||
$dbl_colon_pos = strpos($ip, '::');
|
||||
if ($dbl_colon_pos !== false) {
|
||||
$ip = str_replace('::', str_repeat(':0000', (($dbl_colon_pos === 0 || $dbl_colon_pos === strlen($ip) - 2) ? 9 : 8) - $colon_count) . ':', $ip);
|
||||
$ip = trim($ip, ':');
|
||||
}
|
||||
|
||||
$ip_groups = explode(':', $ip);
|
||||
$ipv6_bin = '';
|
||||
foreach ($ip_groups as $ip_group) {
|
||||
$ipv6_bin .= pack('H*', str_pad($ip_group, 4, '0', STR_PAD_LEFT));
|
||||
}
|
||||
|
||||
if (Model_Crypto::strlen($ipv6_bin) == 16) {
|
||||
$pton = $ipv6_bin;
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (preg_match('/^(?:\:(?:\:0{1,4}){0,4}\:|(?:0{1,4}\:){5})ffff\:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/i', $ip, $matches)) { // IPv4 mapped IPv6
|
||||
$octets = explode('.', $matches[1]);
|
||||
$pton = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff" . chr($octets[0]) . chr($octets[1]) . chr($octets[2]) . chr($octets[3]);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$pton = str_pad($pton, 16, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00", STR_PAD_LEFT);
|
||||
return $pton;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify PHP was compiled with IPv6 support.
|
||||
*
|
||||
* Some hosts appear to not have inet_ntop, and others appear to have inet_ntop but are unable to process IPv6 addresses.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_ipv6() {
|
||||
return defined('AF_INET6');
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands a compressed printable representation of an IPv6 address.
|
||||
*
|
||||
* @param string $ip
|
||||
* @return string
|
||||
*/
|
||||
public static function expand_ipv6_address($ip) {
|
||||
$hex = bin2hex(self::inet_pton($ip));
|
||||
$ip = substr(preg_replace("/([a-f0-9]{4})/i", "$1:", $hex), 0, -1);
|
||||
return $ip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the IP is a valid format.
|
||||
*
|
||||
* @param string $ip
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_valid_ip($ip) {
|
||||
return filter_var($ip, FILTER_VALIDATE_IP) !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the range is a valid CIDR range.
|
||||
*
|
||||
* @param string $range
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_valid_cidr_range($range) {
|
||||
$components = explode('/', $range);
|
||||
if (count($components) != 2) { return false; }
|
||||
|
||||
list($ip, $prefix) = $components;
|
||||
if (!self::is_valid_ip($ip)) { return false; }
|
||||
|
||||
if (!preg_match('/^\d+$/', $prefix)) { return false; }
|
||||
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
if ($prefix < 0 || $prefix > 32) { return false; }
|
||||
}
|
||||
else {
|
||||
if ($prefix < 1 || $prefix > 128) { return false; }
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the IP is in the IPv6-mapped-IPv4 format.
|
||||
*
|
||||
* @param string $ip
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_ipv6_mapped_ipv4($ip) {
|
||||
return preg_match('/^(?:\:(?:\:0{1,4}){0,4}\:|(?:0{1,4}\:){5})ffff\:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/i', $ip) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_Notice {
|
||||
const SEVERITY_CRITICAL = 'critical';
|
||||
const SEVERITY_WARNING = 'warning';
|
||||
const SEVERITY_INFO = 'info';
|
||||
|
||||
private $_id;
|
||||
private $_severity;
|
||||
private $_messageHTML;
|
||||
private $_category;
|
||||
|
||||
public function __construct($id, $severity, $messageHTML, $category) {
|
||||
$this->_id = $id;
|
||||
$this->_severity = $severity;
|
||||
$this->_messageHTML = $messageHTML;
|
||||
$this->_category = $category;
|
||||
}
|
||||
|
||||
public function display_notice() {
|
||||
$severityClass = 'notice-info';
|
||||
if ($this->_severity == self::SEVERITY_CRITICAL) {
|
||||
$severityClass = 'notice-error';
|
||||
}
|
||||
else if ($this->_severity == self::SEVERITY_WARNING) {
|
||||
$severityClass = 'notice-warning';
|
||||
}
|
||||
|
||||
echo '<div class="wfls-notice notice ' . $severityClass . '" data-notice-id="' . esc_attr($this->_id) . '" data-notice-type="' . esc_attr($this->_category) . '"><p>' . $this->_messageHTML . '</p><p>' . sprintf(__('<a class="wfls-btn wfls-btn-default wfls-btn-sm wfls-dismiss-link" href="#" onclick="GWFLS.dismiss_notice(\'%s\'); return false;">Dismiss</a>', 'wordfence-2fa'), esc_attr($this->_id)) . '</p></div>';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_Request {
|
||||
const IP_SOURCE_AUTOMATIC = '';
|
||||
const IP_SOURCE_REMOTE_ADDR = 'REMOTE_ADDR';
|
||||
const IP_SOURCE_X_FORWARDED_FOR = 'HTTP_X_FORWARDED_FOR';
|
||||
const IP_SOURCE_X_REAL_IP = 'HTTP_X_REAL_IP';
|
||||
|
||||
private $_cachedIP;
|
||||
|
||||
public static function current() {
|
||||
return new Model_Request();
|
||||
}
|
||||
|
||||
public function detected_ip_preview($source = null, $trusted_proxies = null) {
|
||||
if ($source === null) {
|
||||
$source = Controller_Settings::shared()->get(Controller_Settings::OPTION_IP_SOURCE);
|
||||
}
|
||||
|
||||
$record = $this->_ip($source);
|
||||
if (is_array($record)) {
|
||||
list($ip, $variable) = $record;
|
||||
if (isset($_SERVER[$variable]) && strpos($_SERVER[$variable], ',') !== false) {
|
||||
$items = preg_replace('/[\s,]/', '', explode(',', $_SERVER[$variable]));
|
||||
$output = '';
|
||||
foreach ($items as $i) {
|
||||
if ($ip == $i) {
|
||||
$output .= ', <strong>' . esc_html($i) . '</strong>';
|
||||
}
|
||||
else {
|
||||
$output .= ', ' . esc_html($i);
|
||||
}
|
||||
}
|
||||
|
||||
return substr($output, 2);
|
||||
}
|
||||
return '<strong>' . esc_html($ip) . '</strong>';
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function ip($refreshCache = false) {
|
||||
if (WORDFENCE_LS_FROM_CORE) {
|
||||
return \wfUtils::getIP($refreshCache);
|
||||
}
|
||||
|
||||
if (!isset($this->_cachedIP) || $refreshCache) {
|
||||
$this->_cachedIP = $this->_ip(Controller_Settings::shared()->get(Controller_Settings::OPTION_IP_SOURCE), Controller_Settings::shared()->trusted_proxies());
|
||||
}
|
||||
|
||||
return $this->_cachedIP[0]; //Format is array(<text IP>, <field found in>)
|
||||
}
|
||||
|
||||
public function ip_for_field($source, $trusted_proxies) {
|
||||
return $this->_ip($source, $trusted_proxies);
|
||||
}
|
||||
|
||||
protected function _ip($source = null, $trusted_proxies = null) {
|
||||
if ($source === null) {
|
||||
$source = Controller_Settings::shared()->get(Controller_Settings::OPTION_IP_SOURCE);
|
||||
}
|
||||
|
||||
$possible_ips = $this->_possible_ips($source);
|
||||
if ($trusted_proxies === null) { $trusted_proxies = array(); }
|
||||
return $this->_find_preferred_ip($possible_ips, $trusted_proxies);
|
||||
}
|
||||
|
||||
protected function _possible_ips($source = null) {
|
||||
$defaultIP = (is_array($_SERVER) && isset($_SERVER[self::IP_SOURCE_REMOTE_ADDR])) ? array($_SERVER[self::IP_SOURCE_REMOTE_ADDR], self::IP_SOURCE_REMOTE_ADDR) : array('127.0.0.1', self::IP_SOURCE_REMOTE_ADDR);
|
||||
|
||||
if ($source) {
|
||||
if ($source == self::IP_SOURCE_REMOTE_ADDR) {
|
||||
return array($defaultIP);
|
||||
}
|
||||
|
||||
$check = array(
|
||||
array((isset($_SERVER[$source]) ? $_SERVER[$source] : ''), $source),
|
||||
$defaultIP,
|
||||
);
|
||||
return $check;
|
||||
}
|
||||
|
||||
$check = array($defaultIP);
|
||||
if (isset($_SERVER[self::IP_SOURCE_X_FORWARDED_FOR])) {
|
||||
$check[] = array($_SERVER[self::IP_SOURCE_X_FORWARDED_FOR], self::IP_SOURCE_X_FORWARDED_FOR);
|
||||
}
|
||||
if (isset($_SERVER[self::IP_SOURCE_X_REAL_IP])) {
|
||||
$check[] = array($_SERVER[self::IP_SOURCE_X_REAL_IP], self::IP_SOURCE_X_REAL_IP);
|
||||
}
|
||||
return $check;
|
||||
}
|
||||
|
||||
protected function _find_preferred_ip($possible_ips, $trusted_proxies) {
|
||||
$privates = array();
|
||||
foreach ($possible_ips as $entry) {
|
||||
list($value, $var) = $entry;
|
||||
if (is_array($value)) { // An array of IPs
|
||||
foreach ($value as $index => $j) {
|
||||
if (!Model_IP::is_valid_ip($j)) {
|
||||
$j = preg_replace('/:\d+$/', '', $j); //Strip off port if present
|
||||
}
|
||||
|
||||
if (Model_IP::is_valid_ip($j)) {
|
||||
if (Model_IP::is_ipv6_mapped_ipv4($j)) {
|
||||
$j = Model_IP::inet_ntop(Model_IP::inet_pton($j));
|
||||
}
|
||||
|
||||
foreach ($trusted_proxies as $proxy) {
|
||||
if (!empty($proxy)) {
|
||||
if (Controller_Whitelist::shared()->ip_in_range($j, $proxy) && $index < count($value) - 1) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filter_var($j, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
$privates[] = array($j, $var);
|
||||
}
|
||||
else {
|
||||
return array($j, $var);
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$skipToNext = false;
|
||||
$separators = array(',', ' ', "\t");
|
||||
foreach ($separators as $char) { // A list of IPs separated by <separator>: 192.0.2.15,192.0.2.35,192.0.2.254
|
||||
if (strpos($value, $char) !== false) {
|
||||
$sp = explode($char, $value);
|
||||
$sp = array_reverse($sp);
|
||||
foreach ($sp as $index => $j) {
|
||||
$j = trim($j);
|
||||
if (!Model_IP::is_valid_ip($j)) {
|
||||
$j = preg_replace('/:\d+$/', '', $j); //Strip off port
|
||||
}
|
||||
|
||||
if (Model_IP::is_valid_ip($j)) {
|
||||
if (Model_IP::is_ipv6_mapped_ipv4($j)) {
|
||||
$j = Model_IP::inet_ntop(Model_IP::inet_pton($j));
|
||||
}
|
||||
|
||||
foreach ($trusted_proxies as $proxy) {
|
||||
if (!empty($proxy)) {
|
||||
if (Controller_Whitelist::shared()->ip_in_range($j, $proxy) && $index < count($sp) - 1) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filter_var($j, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
$privates[] = array($j, $var);
|
||||
}
|
||||
else {
|
||||
return array($j, $var);
|
||||
}
|
||||
}
|
||||
}
|
||||
$skipToNext = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($skipToNext) { continue; } //Skip to next item because this one had a comma/space/tab, but we didn't find a valid, non-private address
|
||||
|
||||
// A literal IP
|
||||
if (!Model_IP::is_valid_ip($value)) {
|
||||
$value = preg_replace('/:\d+$/', '', $value); //Strip off port
|
||||
}
|
||||
|
||||
if (Model_IP::is_valid_ip($value)) {
|
||||
if (Model_IP::is_ipv6_mapped_ipv4($value)) {
|
||||
$value = Model_IP::inet_ntop(Model_IP::inet_pton($value));
|
||||
}
|
||||
|
||||
if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE) === false) {
|
||||
$privates[] = array($value, $var);
|
||||
}
|
||||
else {
|
||||
return array($value, $var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($privates) > 0) {
|
||||
return $privates[0];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_Script extends Model_Asset {
|
||||
|
||||
public function enqueue() {
|
||||
if ($this->registered) {
|
||||
wp_enqueue_script($this->handle);
|
||||
}
|
||||
else {
|
||||
wp_enqueue_script($this->handle, $this->source, $this->dependencies, $this->version);
|
||||
}
|
||||
}
|
||||
|
||||
public function isEnqueued() {
|
||||
return wp_script_is($this->handle);
|
||||
}
|
||||
|
||||
public function renderInline() {
|
||||
if (empty($this->source))
|
||||
return;
|
||||
?>
|
||||
<script type="text/javascript" src="<?php echo esc_attr($this->getSourceUrl()) ?>"></script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function register() {
|
||||
wp_register_script($this->handle, $this->source, $this->dependencies, $this->version);
|
||||
return parent::register();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
abstract class Model_Settings {
|
||||
const AUTOLOAD_YES = 'yes';
|
||||
const AUTOLOAD_NO = 'no';
|
||||
|
||||
/**
|
||||
* Sets $value to $key.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param string $autoload Whether or not the key/value pair should autoload in storages that do that.
|
||||
* @param bool $allowOverwrite If false, only sets the value if key does not already exist.
|
||||
*/
|
||||
abstract public function set($key, $value, $autoload = self::AUTOLOAD_YES, $allowOverwrite = true);
|
||||
abstract public function set_multiple($values);
|
||||
abstract public function get($key, $default);
|
||||
abstract public function remove($key);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\Settings;
|
||||
|
||||
use WordfenceLS\Controller_DB;
|
||||
use WordfenceLS\Model_Settings;
|
||||
|
||||
class Model_DB extends Model_Settings {
|
||||
const AUTOLOAD_NO = 'no';
|
||||
const AUTOLOAD_YES = 'yes';
|
||||
|
||||
public function set($key, $value, $autoload = self::AUTOLOAD_YES, $allowOverwrite = true) {
|
||||
global $wpdb;
|
||||
$table = Controller_DB::shared()->settings;
|
||||
if (!$allowOverwrite) {
|
||||
if ($this->_has_cached($key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$row = $wpdb->get_row($wpdb->prepare("SELECT * FROM `{$table}` WHERE `name` = %s", $key), ARRAY_A);
|
||||
if (is_array($row)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ($wpdb->query($wpdb->prepare("INSERT INTO `{$table}` (`name`, `value`, `autoload`) VALUES (%s, %s, %s) ON DUPLICATE KEY UPDATE `value` = VALUES(`value`), `autoload` = VALUES(`autoload`)", $key, $value, $autoload)) !== false && $autoload != self::AUTOLOAD_NO) {
|
||||
$this->_update_cached($key, $value);
|
||||
do_action('wfls_settings_set', $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
public function set_multiple($values) {
|
||||
foreach ($values as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$this->set($key, $value['value'], $value['autoload'], $value['allowOverwrite']);
|
||||
}
|
||||
else {
|
||||
$this->set($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get($key, $default = false) {
|
||||
global $wpdb;
|
||||
|
||||
if ($this->_has_cached($key)) {
|
||||
return $this->_cached_value($key);
|
||||
}
|
||||
|
||||
$table = Controller_DB::shared()->settings;
|
||||
if (!($setting = $wpdb->get_row($wpdb->prepare("SELECT `name`, `value`, `autoload` FROM `{$table}` WHERE `name` = %s", $key)))) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if ($setting->autoload != self::AUTOLOAD_NO) {
|
||||
$this->_update_cached($key, $setting->value);
|
||||
}
|
||||
return $setting->value;
|
||||
}
|
||||
|
||||
public function remove($key) {
|
||||
global $wpdb;
|
||||
$table = Controller_DB::shared()->settings;
|
||||
$wpdb->query($wpdb->prepare("DELETE FROM `{$table}` WHERE `name` = %s", $key));
|
||||
$this->_remove_cached($key);
|
||||
}
|
||||
|
||||
private function _cached() {
|
||||
global $wpdb;
|
||||
|
||||
$settings = wp_cache_get('allsettings', 'wordfence-ls');
|
||||
if (!$settings) {
|
||||
$table = Controller_DB::shared()->settings;
|
||||
$suppress = $wpdb->suppress_errors();
|
||||
$raw = $wpdb->get_results("SELECT `name`, `value` FROM `{$table}` WHERE `autoload` = 'yes'");
|
||||
$wpdb->suppress_errors($suppress);
|
||||
$settings = array();
|
||||
foreach ((array) $raw as $o) {
|
||||
$settings[$o->name] = $o->value;
|
||||
}
|
||||
|
||||
wp_cache_add_non_persistent_groups('wordfence-ls');
|
||||
wp_cache_add('allsettings', $settings, 'wordfence-ls');
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
private function _update_cached($key, $value) {
|
||||
$settings = $this->_cached();
|
||||
$settings[$key] = $value;
|
||||
wp_cache_set('allsettings', $settings, 'wordfence-ls');
|
||||
}
|
||||
|
||||
private function _remove_cached($key) {
|
||||
$settings = $this->_cached();
|
||||
if (isset($settings[$key])) {
|
||||
unset($settings[$key]);
|
||||
wp_cache_set('allsettings', $settings, 'wordfence-ls');
|
||||
}
|
||||
}
|
||||
|
||||
private function _cached_value($key) {
|
||||
global $wpdb;
|
||||
|
||||
$settings = $this->_cached();
|
||||
if (isset($settings[$key])) {
|
||||
return $settings[$key];
|
||||
}
|
||||
|
||||
$table = Controller_DB::shared()->settings;
|
||||
$value = $wpdb->get_var($wpdb->prepare("SELECT `value` FROM `{$table}` WHERE name = %s", $key));
|
||||
if ($value !== null) {
|
||||
$settings[$key] = $value;
|
||||
wp_cache_set('allsettings', $settings, 'wordfence-ls');
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
public function _has_cached($key) {
|
||||
$settings = $this->_cached();
|
||||
return isset($settings[$key]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\Settings;
|
||||
|
||||
use WordfenceLS\Model_Settings;
|
||||
|
||||
class Model_WPOptions extends Model_Settings {
|
||||
protected $_prefix;
|
||||
|
||||
public function __construct($prefix = '') {
|
||||
$this->_prefix = $prefix;
|
||||
}
|
||||
|
||||
protected function _translate_key($key) {
|
||||
return strtolower(preg_replace('/[^a-z0-9]/i', '_', $key));
|
||||
}
|
||||
|
||||
public function set($key, $value, $autoload = self::AUTOLOAD_YES, $allowOverwrite = true) {
|
||||
$key = $this->_translate_key($this->_prefix . $key);
|
||||
if (!$allowOverwrite) {
|
||||
if (is_multisite()) {
|
||||
add_network_option(null, $key, $value);
|
||||
}
|
||||
else {
|
||||
add_option($key, $value, '', $autoload);
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (is_multisite()) {
|
||||
update_network_option(null, $key, $value);
|
||||
}
|
||||
else {
|
||||
update_option($key, $value, $autoload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function set_multiple($values) {
|
||||
foreach ($values as $key => $value) {
|
||||
if (is_array($value)) {
|
||||
$this->set($key, $value['value'], $value['autoload'], $value['allowOverwrite']);
|
||||
}
|
||||
else {
|
||||
$this->set($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function get($key, $default = false) {
|
||||
$key = $this->_translate_key($this->_prefix . $key);
|
||||
if (is_multisite()) {
|
||||
$value = get_network_option($key, $default);
|
||||
}
|
||||
else {
|
||||
$value = get_option($key, $default);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
public function remove($key) {
|
||||
$key = $this->_translate_key($this->_prefix . $key);
|
||||
if (is_multisite()) {
|
||||
delete_network_option(null, $key);
|
||||
}
|
||||
else {
|
||||
delete_option($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_Style extends Model_Asset {
|
||||
|
||||
public function enqueue() {
|
||||
if ($this->registered) {
|
||||
wp_enqueue_style($this->handle);
|
||||
}
|
||||
else {
|
||||
wp_enqueue_style($this->handle, $this->source, $this->dependencies, $this->version);
|
||||
}
|
||||
}
|
||||
|
||||
public function isEnqueued() {
|
||||
return wp_style_is($this->handle);
|
||||
}
|
||||
|
||||
public function renderInline() {
|
||||
if (empty($this->source))
|
||||
return;
|
||||
$url = esc_attr($this->getSourceUrl());
|
||||
$linkTag = "<link rel=\"stylesheet\" type=\"text/css\" href=\"{$url}\">";
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
jQuery('head').append(<?php echo json_encode($linkTag) ?>);
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
public function register() {
|
||||
wp_register_style($this->handle, $this->source, $this->dependencies, $this->version);
|
||||
return parent::register();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\Text;
|
||||
|
||||
/**
|
||||
* Represents text that is already HTML-safe and should not be encoded again.
|
||||
* @package Wordfence2FA\Text
|
||||
*/
|
||||
class Model_HTML {
|
||||
private $_html;
|
||||
|
||||
public static function esc_html($content) {
|
||||
if (is_object($content) && ($content instanceof Model_HTML)) {
|
||||
return (string) $content;
|
||||
}
|
||||
return esc_html($content);
|
||||
}
|
||||
|
||||
public function __construct($html) {
|
||||
$this->_html = $html;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->_html;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\Text;
|
||||
|
||||
/**
|
||||
* Represents text that is already JavaScript-safe and should not be encoded again.
|
||||
* @package Wordfence2FA\Text
|
||||
*/
|
||||
class Model_JavaScript {
|
||||
private $_javaScript;
|
||||
|
||||
/**
|
||||
* Returns a string escaped for use in JavaScript. This is almost identical in behavior to esc_js except that
|
||||
* we don't call _wp_specialchars and keep \r rather than stripping it.
|
||||
*
|
||||
* @param string|Model_JavaScript $content
|
||||
* @return string
|
||||
*/
|
||||
public static function esc_js($content) {
|
||||
if (is_object($content) && ($content instanceof Model_HTML)) {
|
||||
return (string) $content;
|
||||
}
|
||||
|
||||
$safe_text = wp_check_invalid_utf8($content);
|
||||
$safe_text = preg_replace('/&#(x)?0*(?(1)27|39);?/i', "'", stripslashes($safe_text));
|
||||
$safe_text = str_replace("\r", '\\r', $safe_text);
|
||||
$safe_text = str_replace("\n", '\\n', addslashes($safe_text));
|
||||
return apply_filters('js_escape', $safe_text, $content);
|
||||
}
|
||||
|
||||
public static function echo_string_literal($string) {
|
||||
echo "'" . self::esc_js($string) . "'";
|
||||
}
|
||||
|
||||
public function __construct($javaScript) {
|
||||
$this->_javaScript = $javaScript;
|
||||
}
|
||||
|
||||
public function __toString() {
|
||||
return $this->_javaScript;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_TokenBucket {
|
||||
/* Constants to map from tokens per unit to tokens per second */
|
||||
const MICROSECOND = 0.000001;
|
||||
const MILLISECOND = 0.001;
|
||||
const SECOND = 1;
|
||||
const MINUTE = 60;
|
||||
const HOUR = 3600;
|
||||
const DAY = 86400;
|
||||
const WEEK = 604800;
|
||||
const MONTH = 2629743.83;
|
||||
const YEAR = 31556926;
|
||||
|
||||
const BACKING_REDIS = 'redis';
|
||||
const BACKING_WP_OPTIONS = 'wpoptions';
|
||||
|
||||
private $_identifier;
|
||||
private $_bucketSize;
|
||||
private $_tokensPerSecond;
|
||||
|
||||
private $_backing;
|
||||
private $_redis;
|
||||
|
||||
/**
|
||||
* Model_TokenBucket constructor.
|
||||
*
|
||||
* @param string $identifier The identifier for the bucket record in the database
|
||||
* @param int $bucketSize The maximum capacity of the bucket.
|
||||
* @param double $tokensPerSecond The number of tokens per second added to the bucket.
|
||||
* @param string $backing The backing storage to use.
|
||||
*/
|
||||
public function __construct($identifier, $bucketSize, $tokensPerSecond, $backing = self::BACKING_WP_OPTIONS) {
|
||||
$this->_identifier = $identifier;
|
||||
$this->_bucketSize = $bucketSize;
|
||||
$this->_tokensPerSecond = $tokensPerSecond;
|
||||
$this->_backing = $backing;
|
||||
|
||||
if ($backing == self::BACKING_REDIS) {
|
||||
$this->_redis = new \Redis();
|
||||
$this->_redis->pconnect('127.0.0.1');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to acquire a lock for the bucket.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @return bool Whether or not the lock was acquired.
|
||||
*/
|
||||
private function _lock($timeout = 30) {
|
||||
if ($this->_backing == self::BACKING_WP_OPTIONS) {
|
||||
$start = microtime(true);
|
||||
while (!$this->_wp_options_create_lock($this->_identifier)) {
|
||||
if (microtime(true) - $start > $timeout) {
|
||||
return false;
|
||||
}
|
||||
usleep(5000); // 5 ms
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if ($this->_backing == self::BACKING_REDIS) {
|
||||
if ($this->_redis === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$start = microtime(true);
|
||||
while (!$this->_redis->setnx('lock:' . $this->_identifier, '1')) {
|
||||
if (microtime(true) - $start > $timeout) {
|
||||
return false;
|
||||
}
|
||||
usleep(5000); // 5 ms
|
||||
}
|
||||
$this->_redis->expire('lock:' . $this->_identifier, 30);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private function _unlock() {
|
||||
if ($this->_backing == self::BACKING_WP_OPTIONS) {
|
||||
$this->_wp_options_release_lock($this->_identifier);
|
||||
}
|
||||
else if ($this->_backing == self::BACKING_REDIS) {
|
||||
if ($this->_redis === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_redis->del('lock:' . $this->_identifier);
|
||||
}
|
||||
}
|
||||
|
||||
private function _wp_options_create_lock($name, $timeout = null) { //Our own version of WP_Upgrader::create_lock
|
||||
global $wpdb;
|
||||
|
||||
if (!$timeout) {
|
||||
$timeout = 3600;
|
||||
}
|
||||
|
||||
$lock_option = 'wfls_' . $name . '.lock';
|
||||
$lock_result = $wpdb->query($wpdb->prepare("INSERT IGNORE INTO `{$wpdb->options}` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, 'no')", $lock_option, time()));
|
||||
|
||||
if (!$lock_result) {
|
||||
$lock_result = get_option($lock_option);
|
||||
if (!$lock_result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($lock_result > (time() - $timeout)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_wp_options_release_lock($name);
|
||||
return $this->_wp_options_create_lock($name, $timeout);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function _wp_options_release_lock($name) {
|
||||
return delete_option('wfls_' . $name . '.lock');
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomically checks the available token count, creating the initial record if needed, and updates the available token count if the requested number of tokens is available.
|
||||
*
|
||||
* @param int $tokenCount
|
||||
* @return bool Whether or not there were enough tokens to satisfy the request.
|
||||
*/
|
||||
public function consume($tokenCount = 1) {
|
||||
if (!$this->_lock()) { return false; }
|
||||
|
||||
if ($this->_backing == self::BACKING_WP_OPTIONS) {
|
||||
$record = get_transient('wflsbucket:' . $this->_identifier);
|
||||
}
|
||||
else if ($this->_backing == self::BACKING_REDIS) {
|
||||
$record = $this->_redis->get('bucket:' . $this->_identifier);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($record === false) {
|
||||
if ($tokenCount > $this->_bucketSize) {
|
||||
$this->_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->_bootstrap($this->_bucketSize - $tokenCount);
|
||||
$this->_unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
$tokens = min($this->_secondsToTokens(microtime(true) - (float) $record), $this->_bucketSize);
|
||||
if ($tokenCount > $tokens) {
|
||||
$this->_unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->_backing == self::BACKING_WP_OPTIONS) {
|
||||
set_transient('wflsbucket:' . $this->_identifier, (string) (microtime(true) - $this->_tokensToSeconds($tokens - $tokenCount)), ceil($this->_tokensToSeconds($this->_bucketSize)));
|
||||
}
|
||||
else if ($this->_backing == self::BACKING_REDIS) {
|
||||
$this->_redis->set('bucket:' . $this->_identifier, (string) (microtime(true) - $this->_tokensToSeconds($tokens - $tokenCount)));
|
||||
}
|
||||
|
||||
$this->_unlock();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an initial record with the given number of tokens.
|
||||
*
|
||||
* @param int $initialTokens
|
||||
*/
|
||||
protected function _bootstrap($initialTokens) {
|
||||
$microtime = microtime(true) - $this->_tokensToSeconds($initialTokens);
|
||||
if ($this->_backing == self::BACKING_WP_OPTIONS) {
|
||||
set_transient('wflsbucket:' . $this->_identifier, (string) $microtime, ceil($this->_tokensToSeconds($this->_bucketSize)));
|
||||
}
|
||||
else if ($this->_backing == self::BACKING_REDIS) {
|
||||
$this->_redis->set('bucket:' . $this->_identifier, (string) $microtime);
|
||||
}
|
||||
}
|
||||
|
||||
protected function _tokensToSeconds($tokens) {
|
||||
return $tokens / $this->_tokensPerSecond;
|
||||
}
|
||||
|
||||
protected function _secondsToTokens($seconds) {
|
||||
return (int) $seconds * $this->_tokensPerSecond;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS;
|
||||
|
||||
class Model_View {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $path;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $file_extension = '.php';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data;
|
||||
|
||||
/**
|
||||
* Equivalent to the constructor but allows for call chaining.
|
||||
*
|
||||
* @param string $view
|
||||
* @param array $data
|
||||
* @return Model_View
|
||||
*/
|
||||
public static function create($view, $data = array()) {
|
||||
return new self($view, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $view
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct($view, $data = array()) {
|
||||
$this->path = WORDFENCE_LS_PATH . 'views';
|
||||
$this->view = $view;
|
||||
$this->data = $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws ViewNotFoundException
|
||||
*/
|
||||
public function render() {
|
||||
$view = preg_replace('/\.{2,}/', '.', $this->view);
|
||||
$path = $this->path . '/' . $view . $this->file_extension;
|
||||
if (!file_exists($path)) {
|
||||
throw new ViewNotFoundException('The view ' . $path . ' does not exist or is not readable.');
|
||||
}
|
||||
|
||||
extract($this->data, EXTR_SKIP);
|
||||
|
||||
ob_start();
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
include $path;
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
try {
|
||||
return $this->render();
|
||||
}
|
||||
catch (ViewNotFoundException $e) {
|
||||
return defined('WP_DEBUG') && WP_DEBUG ? $e->getMessage() : 'The view could not be loaded.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $data
|
||||
* @return $this
|
||||
*/
|
||||
public function addData($data) {
|
||||
$this->data = array_merge($data, $this->data);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getData() {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @return $this
|
||||
*/
|
||||
public function setData($data) {
|
||||
$this->data = $data;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getView() {
|
||||
return $this->view;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $view
|
||||
* @return $this
|
||||
*/
|
||||
public function setView($view) {
|
||||
$this->view = $view;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent POP
|
||||
*/
|
||||
public function __wakeup() {
|
||||
$this->path = WORDFENCE_LS_PATH . 'views';
|
||||
$this->view = null;
|
||||
$this->data = array();
|
||||
$this->file_extension = '.php';
|
||||
}
|
||||
}
|
||||
|
||||
class ViewNotFoundException extends \Exception { }
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\View;
|
||||
|
||||
/**
|
||||
* Represents a tab in the UI.
|
||||
*
|
||||
* @package Wordfence2FA\View
|
||||
* @property string $id
|
||||
* @property string $a
|
||||
* @property string $tabTitle
|
||||
* @property string $pageTitle
|
||||
* @property bool $active
|
||||
*/
|
||||
class Model_Tab {
|
||||
protected $_id;
|
||||
protected $_a;
|
||||
protected $_tabTitle;
|
||||
protected $_pageTitle;
|
||||
protected $_active;
|
||||
|
||||
public function __construct($id, $a, $tabTitle, $pageTitle, $active = false) {
|
||||
$this->_id = $id;
|
||||
$this->_a = $a;
|
||||
$this->_tabTitle = $tabTitle;
|
||||
$this->_pageTitle = $pageTitle;
|
||||
$this->_active = $active;
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
switch ($name) {
|
||||
case 'id':
|
||||
return $this->_id;
|
||||
case 'a':
|
||||
return $this->_a;
|
||||
case 'tabTitle':
|
||||
return $this->_tabTitle;
|
||||
case 'pageTitle':
|
||||
return $this->_pageTitle;
|
||||
case 'active':
|
||||
return $this->_active;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Invalid key: ' . $name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace WordfenceLS\View;
|
||||
|
||||
/**
|
||||
* Class Model_Title
|
||||
* @package Wordfence2FA\Page
|
||||
* @var string $id A valid DOM ID for the title.
|
||||
* @var string|Model_HTML $title The title text or HTML.
|
||||
* @var string $helpURL The help URL.
|
||||
* @var string|Model_HTML $helpLink The text/HTML of the help link.
|
||||
*/
|
||||
class Model_Title {
|
||||
private $_id;
|
||||
private $_title;
|
||||
private $_helpURL;
|
||||
private $_helpLink;
|
||||
|
||||
public function __construct($id, $title, $helpURL = null, $helpLink = null) {
|
||||
$this->_id = $id;
|
||||
$this->_title = $title;
|
||||
$this->_helpURL = $helpURL;
|
||||
$this->_helpLink = $helpLink;
|
||||
}
|
||||
|
||||
public function __get($name) {
|
||||
switch ($name) {
|
||||
case 'id':
|
||||
return $this->_id;
|
||||
case 'title':
|
||||
return $this->_title;
|
||||
case 'helpURL':
|
||||
return $this->_helpURL;
|
||||
case 'helpLink':
|
||||
return $this->_helpLink;
|
||||
}
|
||||
|
||||
throw new \OutOfBoundsException('Invalid key: ' . $name);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user