Download libs/Authenticator/TwoFactorAuthenticator.php from Leon4gr45/matomo: direct link, hf CLI and curl.
- Browser
- Download file 7.32 kB
-
https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/libs/Authenticator/TwoFactorAuthenticator.php
- Command line
-
hf download hf://spaces/Leon4gr45/matomo/libs/Authenticator/TwoFactorAuthenticator.php
-
curl -L -o TwoFactorAuthenticator.php https://huggingface.co/spaces/Leon4gr45/matomo/resolve/main/libs/Authenticator/TwoFactorAuthenticator.php
7.32 kB
| /** | |
| * PHP Class for handling Google Authenticator 2-factor authentication | |
| * | |
| * @author Michael Kliewe | |
| * @copyright 2012 Michael Kliewe | |
| * @license http://www.opensource.org/licenses/bsd-license.php BSD License | |
| * @link http://www.phpgangsta.de/ | |
| * | |
| * small adjustments by matomo.org | |
| * - renamed class | |
| * - removed method getQRCodeGoogleUrl | |
| * - use better random secret generator | |
| * - apply proper type hints | |
| */ | |
| class TwoFactorAuthenticator | |
| { | |
| protected $_codeLength = 6; | |
| /** | |
| * Create new secret. | |
| * 16 characters, randomly chosen from the allowed base32 characters. | |
| * | |
| * @param int $secretLength | |
| * @return string | |
| */ | |
| public function createSecret(int $secretLength = 16): string | |
| { | |
| $validChars = $this->_getBase32LookupTable(); | |
| unset($validChars[32]); | |
| // modified by matomo.org | |
| return \Piwik\Common::getRandomString($secretLength, implode('', $validChars)); | |
| } | |
| /** | |
| * Calculate the code, with given secret and point in time | |
| * | |
| * @param string $secret | |
| * @param int|null $timeSlice | |
| * @return string | |
| */ | |
| public function getCode(string $secret, ?int $timeSlice = null) | |
| { | |
| if ($timeSlice === null) { | |
| $timeSlice = floor(time() / 30); | |
| } | |
| $secretkey = $this->_base32Decode($secret); | |
| // Pack time into binary string | |
| $time = chr(0).chr(0).chr(0).chr(0).pack('N*', $timeSlice); | |
| // Hash it with users secret key | |
| $hm = hash_hmac('SHA1', $time, $secretkey, true); | |
| // Use last nipple of result as index/offset | |
| $offset = ord(substr($hm, -1)) & 0x0F; | |
| // grab 4 bytes of the result | |
| $hashpart = substr($hm, $offset, 4); | |
| // Unpak binary value | |
| $value = unpack('N', $hashpart); | |
| $value = $value[1]; | |
| // Only 32 bits | |
| $value = $value & 0x7FFFFFFF; | |
| $modulo = pow(10, $this->_codeLength); | |
| return str_pad($value % $modulo, $this->_codeLength, '0', STR_PAD_LEFT); | |
| } | |
| /** | |
| * Check if the code is correct. This will accept codes starting from $discrepancy*30sec ago to $discrepancy*30sec from now | |
| * | |
| * @param string $secret | |
| * @param string $code | |
| * @param int $discrepancy This is the allowed time drift in 30 second units (8 means 4 minutes before or after) | |
| * @param int|null $currentTimeSlice time slice if we want use other that time() | |
| * @return bool | |
| */ | |
| public function verifyCode(string $secret, string $code, int $discrepancy = 1, ?int $currentTimeSlice = null): bool | |
| { | |
| if ($currentTimeSlice === null) { | |
| $currentTimeSlice = floor(time() / 30); | |
| } | |
| for ($i = -$discrepancy; $i <= $discrepancy; $i++) { | |
| $calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); | |
| if ($this->timingSafeEquals($calculatedCode, $code)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| /** | |
| * Set the code length, should be >=6 | |
| * | |
| * @param int $length | |
| * @return self | |
| */ | |
| public function setCodeLength(int $length) | |
| { | |
| $this->_codeLength = $length; | |
| return $this; | |
| } | |
| /** | |
| * Helper class to decode base32 | |
| * | |
| * @param string $secret | |
| * @return string | |
| */ | |
| protected function _base32Decode(string $secret): string | |
| { | |
| if (empty($secret)) return ''; | |
| $base32chars = $this->_getBase32LookupTable(); | |
| $base32charsFlipped = array_flip($base32chars); | |
| $paddingCharCount = substr_count($secret, $base32chars[32]); | |
| $allowedValues = array(6, 4, 3, 1, 0); | |
| if (!in_array($paddingCharCount, $allowedValues)) return false; | |
| for ($i = 0; $i < 4; $i++){ | |
| if ($paddingCharCount == $allowedValues[$i] && | |
| substr($secret, -($allowedValues[$i])) != str_repeat($base32chars[32], $allowedValues[$i])) return false; | |
| } | |
| $secret = str_replace('=','', $secret); | |
| $secret = str_split($secret); | |
| $binaryString = ""; | |
| for ($i = 0; $i < count($secret); $i = $i+8) { | |
| $x = ""; | |
| if (!in_array($secret[$i], $base32chars)) return false; | |
| for ($j = 0; $j < 8; $j++) { | |
| $x .= str_pad(base_convert($base32charsFlipped[@$secret[$i + $j]] ?? '', 10, 2), 5, '0', STR_PAD_LEFT); | |
| } | |
| $eightBits = str_split($x, 8); | |
| for ($z = 0; $z < count($eightBits); $z++) { | |
| $binaryString .= ( ($y = chr(base_convert($eightBits[$z], 2, 10))) || ord($y) == 48 ) ? $y:""; | |
| } | |
| } | |
| return $binaryString; | |
| } | |
| /** | |
| * Helper class to encode base32 | |
| * | |
| * @param string $secret | |
| * @param bool $padding | |
| * @return string | |
| */ | |
| protected function _base32Encode(string $secret, bool $padding = true): string | |
| { | |
| if (empty($secret)) return ''; | |
| $base32chars = $this->_getBase32LookupTable(); | |
| $secret = str_split($secret); | |
| $binaryString = ""; | |
| for ($i = 0; $i < count($secret); $i++) { | |
| $binaryString .= str_pad(base_convert(ord($secret[$i]), 10, 2), 8, '0', STR_PAD_LEFT); | |
| } | |
| $fiveBitBinaryArray = str_split($binaryString, 5); | |
| $base32 = ""; | |
| $i = 0; | |
| while ($i < count($fiveBitBinaryArray)) { | |
| $base32 .= $base32chars[base_convert(str_pad($fiveBitBinaryArray[$i], 5, '0'), 2, 10)]; | |
| $i++; | |
| } | |
| if ($padding && ($x = strlen($binaryString) % 40) != 0) { | |
| if ($x == 8) $base32 .= str_repeat($base32chars[32], 6); | |
| elseif ($x == 16) $base32 .= str_repeat($base32chars[32], 4); | |
| elseif ($x == 24) $base32 .= str_repeat($base32chars[32], 3); | |
| elseif ($x == 32) $base32 .= $base32chars[32]; | |
| } | |
| return $base32; | |
| } | |
| /** | |
| * Get array with all 32 characters for decoding from/encoding to base32 | |
| * | |
| * @return array<string> | |
| */ | |
| protected function _getBase32LookupTable(): array | |
| { | |
| return array( | |
| 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', // 7 | |
| 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', // 15 | |
| 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', // 23 | |
| 'Y', 'Z', '2', '3', '4', '5', '6', '7', // 31 | |
| '=' // padding char | |
| ); | |
| } | |
| /** | |
| * A timing safe equals comparison | |
| * more info here: https://blog.ircmaxell.com/2014/11/its-all-about-time.html. | |
| * | |
| * @param string $safeString The internal (safe) value to be checked | |
| * @param string $userString The user submitted (unsafe) value | |
| * | |
| * @return bool True if the two strings are identical | |
| */ | |
| private function timingSafeEquals(string $safeString, string $userString): bool | |
| { | |
| if (function_exists('hash_equals')) { | |
| return hash_equals($safeString, $userString); | |
| } | |
| $safeLen = strlen($safeString); | |
| $userLen = strlen($userString); | |
| if ($userLen != $safeLen) { | |
| return false; | |
| } | |
| $result = 0; | |
| for ($i = 0; $i < $userLen; ++$i) { | |
| $result |= (ord($safeString[$i]) ^ ord($userString[$i])); | |
| } | |
| // They are only identical strings if $result is exactly 0... | |
| return $result === 0; | |
| } | |
| } | |