看板初始化提交

This commit is contained in:
zephyr
2026-06-01 21:23:12 -07:00
commit 54a842f4ab
2104 changed files with 241695 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2013-2014 Christian Riesen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+168
View File
@@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
namespace Base32;
/**
* Base32 encoder and decoder.
*
* RFC 4648 compliant
*
* @see http://www.ietf.org/rfc/rfc4648.txt
* Some groundwork based on this class
* https://github.com/NTICompass/PHP-Base32
*
* @author Christian Riesen <chris.riesen@gmail.com>
* @author Sam Williams <sam@badcow.co>
*
* @see http://christianriesen.com
*
* @license MIT License see LICENSE file
*/
class Base32
{
/**
* Alphabet for encoding and decoding base32.
*
* @var string
*/
protected const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567=';
protected const BASE32HEX_PATTERN = '/[^A-Z2-7]/';
/**
* Maps the Base32 character to its corresponding bit value.
*/
protected const MAPPING = [
'=' => 0b00000,
'A' => 0b00000,
'B' => 0b00001,
'C' => 0b00010,
'D' => 0b00011,
'E' => 0b00100,
'F' => 0b00101,
'G' => 0b00110,
'H' => 0b00111,
'I' => 0b01000,
'J' => 0b01001,
'K' => 0b01010,
'L' => 0b01011,
'M' => 0b01100,
'N' => 0b01101,
'O' => 0b01110,
'P' => 0b01111,
'Q' => 0b10000,
'R' => 0b10001,
'S' => 0b10010,
'T' => 0b10011,
'U' => 0b10100,
'V' => 0b10101,
'W' => 0b10110,
'X' => 0b10111,
'Y' => 0b11000,
'Z' => 0b11001,
'2' => 0b11010,
'3' => 0b11011,
'4' => 0b11100,
'5' => 0b11101,
'6' => 0b11110,
'7' => 0b11111,
];
/**
* Encodes into base32.
*
* @param string $string Clear text string
*
* @return string Base32 encoded string
*/
public static function encode(string $string): string
{
// Empty string results in empty string
if ('' === $string) {
return '';
}
$encoded = '';
//Set the initial values
$n = $bitLen = $val = 0;
$len = \strlen($string);
//Pad the end of the string - this ensures that there are enough zeros
$string .= \str_repeat(\chr(0), 4);
//Explode string into integers
$chars = (array) \unpack('C*', $string, 0);
while ($n < $len || 0 !== $bitLen) {
//If the bit length has fallen below 5, shift left 8 and add the next character.
if ($bitLen < 5) {
$val = $val << 8;
$bitLen += 8;
$n++;
$val += $chars[$n];
}
$shift = $bitLen - 5;
$encoded .= ($n - (int)($bitLen > 8) > $len && 0 == $val) ? '=' : static::ALPHABET[$val >> $shift];
$val = $val & ((1 << $shift) - 1);
$bitLen -= 5;
}
return $encoded;
}
/**
* Decodes base32.
*
* @param string $base32String Base32 encoded string
*
* @return string Clear text string
*/
public static function decode(string $base32String): string
{
// Only work in upper cases
$base32String = \strtoupper($base32String);
// Remove anything that is not base32 alphabet
$base32String = \preg_replace(static::BASE32HEX_PATTERN, '', $base32String);
// Empty string results in empty string
if ('' === $base32String || null === $base32String) {
return '';
}
$decoded = '';
//Set the initial values
$len = \strlen($base32String);
$n = 0;
$bitLen = 5;
$val = static::MAPPING[$base32String[0]];
while ($n < $len) {
//If the bit length has fallen below 8, shift left 5 and add the next pentet.
if ($bitLen < 8) {
$val = $val << 5;
$bitLen += 5;
$n++;
$pentet = $base32String[$n] ?? '=';
//If the new pentet is padding, make this the last iteration.
if ('=' === $pentet) {
$n = $len;
}
$val += static::MAPPING[$pentet];
continue;
}
$shift = $bitLen - 8;
$decoded .= \chr($val >> $shift);
$val = $val & ((1 << $shift) - 1);
$bitLen -= 8;
}
return $decoded;
}
}
+68
View File
@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
namespace Base32;
/**
* Base32Hex encoder and decoder.
*
* RFC 4648 compliant
* @see http://www.ietf.org/rfc/rfc4648.txt
*
* @author Sam Williams <sam@badcow.co>
*
* @see http://christianriesen.com
*
* @license MIT License see LICENSE file
*/
class Base32Hex extends Base32
{
/**
* Alphabet for encoding and decoding base32 extended hex.
*
* @var string
*/
protected const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUV=';
protected const BASE32HEX_PATTERN = '/[^0-9A-V]/';
/**
* Maps the Base32 character to its corresponding bit value.
*/
protected const MAPPING = [
'=' => 0b00000,
'0' => 0b00000,
'1' => 0b00001,
'2' => 0b00010,
'3' => 0b00011,
'4' => 0b00100,
'5' => 0b00101,
'6' => 0b00110,
'7' => 0b00111,
'8' => 0b01000,
'9' => 0b01001,
'A' => 0b01010,
'B' => 0b01011,
'C' => 0b01100,
'D' => 0b01101,
'E' => 0b01110,
'F' => 0b01111,
'G' => 0b10000,
'H' => 0b10001,
'I' => 0b10010,
'J' => 0b10011,
'K' => 0b10100,
'L' => 0b10101,
'M' => 0b10110,
'N' => 0b10111,
'O' => 0b11000,
'P' => 0b11001,
'Q' => 0b11010,
'R' => 0b11011,
'S' => 0b11100,
'T' => 0b11101,
'U' => 0b11110,
'V' => 0b11111,
];
}
+11
View File
@@ -0,0 +1,11 @@
language: php
php:
- 5.3
- 5.4
- 5.5
- 5.6
- 7.0
script: phpunit
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) Christian Riesen http://christianriesen.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+100
View File
@@ -0,0 +1,100 @@
<?php
session_start(); // using it as storage temporary
require_once __DIR__ . '/../vendor/autoload.php';
use Otp\Otp;
use Otp\GoogleAuthenticator;
use Base32\Base32;
// Getting a secret, either by generating or from storage
// DON'T use sessions as storage for this in production!!!
$secret = 0;
if (isset($_SESSION['otpsecret'])) {
$secret = $_SESSION['otpsecret'];
}
if (strlen($secret) != 16) {
$secret = GoogleAuthenticator::generateRandom();
$_SESSION['otpsecret'] = $secret;
}
// The secret is now an easy stored Base32 string.
// To use it in totp though we need to decode it into the original
$otp = new Otp();
$currentTotp = $otp->totp(Base32::decode($secret));
$qrCode = GoogleAuthenticator::getQrCodeUrl('totp', 'otpsample@cr', $secret);
$keyUri = GoogleAuthenticator::getKeyUri('totp', 'otpsample@cr', $secret);
?><html>
<head>
<title>One Time Passwords Example</title>
</head>
<body>
<h1>One Time Passwords Example</h1>
Secret is <?php echo $secret; ?>. This is saved with the users credentials.
<br />
<br />
<hr />
QR Code for totp:<br />
<img src="<?php echo $qrCode; ?>" />
<br />
This QR Code contains the Key URI: <?php echo $keyUri; ?>
<br />
<hr />
Current totp would be <?php echo $currentTotp; ?><br />
<br />
<hr />
Because of timedrift, you could technically enter a code before or after it
would actually be used. This form uses the checkTotp function. To test this,
open this page, wait until the key changes once or twice (not more) on your
Google Authenticator, then hit submit. Even though the key is "wrong" because of
small time differences, you can still use it.
<form action="" method="post">
<input type="text" name="otpkey" value="<?php echo $currentTotp; ?>" /><br />
<input type="submit">
</form>
<br />
Output:<br />
<br />
<?php
if (isset($_POST['otpkey'])) {
// Sanatizing, this should take care of it
$key = preg_replace('/[^0-9]/', '', $_POST['otpkey']);
// Standard is 6 for keys, but can be changed with setDigits on $otp
if (strlen($key) == 6) {
// Remember that the secret is a base32 string that needs decoding
// to use it here!
if ($otp->checkTotp(Base32::decode($secret), $key)) {
echo 'Key correct!';
// Add here something that makes note of this key and will not allow
// the use of it, for this user for the next 2 minutes. This way you
// prevent a replay attack. Otherwise your OTP is missing one of the
// key features it can bring in security to your application!
} else {
echo 'Wrong key!';
}
} else {
echo 'Key not the correct size';
}
}
?>
</body>
</html>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit colors="true">
<testsuites>
<testsuite name="Otp Test Suite">
<directory>tests/Otp/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory suffix=".php">src/Otp/</directory>
</whitelist>
</filter>
</phpunit>
@@ -0,0 +1,189 @@
<?php
namespace Otp;
/**
* Google Authenticator
*
* Last update: 2014-08-19
*
* Can be easy used with Google Authenticator
* @link https://code.google.com/p/google-authenticator/
*
* @author Christian Riesen <chris.riesen@gmail.com>
* @link http://christianriesen.com
* @license MIT License see LICENSE file
*/
class GoogleAuthenticator
{
protected static $allowedTypes = array('hotp', 'totp');
protected static $height = 200;
protected static $width = 200;
/**
* Returns the Key URI
*
* Format of encoded url is here:
* https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
* Should be done in a better fashion
*
* @param string $type totp or hotp
* @param string $label Label to display this as to the user
* @param string $secret Base32 encoded secret
* @param integer $counter Required by hotp, otherwise ignored
* @param array $options Optional fields that will be set if present
*
* @return string Key URI
*/
public static function getKeyUri($type, $label, $secret, $counter = null, $options = array())
{
// two types only..
if (!in_array($type, self::$allowedTypes)) {
throw new \InvalidArgumentException('Type has to be of allowed types list');
}
// Label can't be empty
$label = trim($label);
if (strlen($label) < 1) {
throw new \InvalidArgumentException('Label has to be one or more printable characters');
}
if (substr_count($label, ':') > 2) {
throw new \InvalidArgumentException('Account name contains illegal colon characters');
}
// Secret needs to be here
if (strlen($secret) < 1) {
throw new \InvalidArgumentException('No secret present');
}
// check for counter on hotp
if ($type == 'hotp' && is_null($counter)) {
throw new \InvalidArgumentException('Counter required for hotp');
}
// This is the base, these are at least required
$otpauth = 'otpauth://' . $type . '/' . str_replace(array(':', ' '), array('%3A', '%20'), $label) . '?secret=' . rawurlencode($secret);
if ($type == 'hotp' && !is_null($counter)) {
$otpauth .= '&counter=' . intval($counter);
}
// Now check the options array
// algorithm (currently ignored by Authenticator)
// Defaults to SHA1
if (array_key_exists('algorithm', $options)) {
$otpauth .= '&algorithm=' . rawurlencode($options['algorithm']);
}
// digits (currently ignored by Authenticator)
// Defaults to 6
if (array_key_exists('digits', $options) && intval($options['digits']) !== 6 && intval($options['digits']) !== 8) {
throw new \InvalidArgumentException('Digits can only have the values 6 or 8, ' . $options['digits'] . ' given');
} elseif (array_key_exists('digits', $options)) {
$otpauth .= '&digits=' . intval($options['digits']);
}
// period, only for totp (currently ignored by Authenticator)
// Defaults to 30
if ($type == 'totp' && array_key_exists('period', $options)) {
$otpauth .= '&period=' . rawurlencode($options['period']);
}
// issuer
// Defaults to none
if (array_key_exists('issuer', $options)) {
$otpauth .= '&issuer=' . rawurlencode($options['issuer']);
}
return $otpauth;
}
/**
* Returns the QR code url
*
* Format of encoded url is here:
* https://code.google.com/p/google-authenticator/wiki/KeyUriFormat
* Should be done in a better fashion
*
* @param string $type totp or hotp
* @param string $label Label to display this as to the user
* @param string $secret Base32 encoded secret
* @param integer $counter Required by hotp, otherwise ignored
* @param array $options Optional fields that will be set if present
*
* @return string URL to the QR code
*/
public static function getQrCodeUrl($type, $label, $secret, $counter = null, $options = array())
{
// Width and height can be overwritten
$width = self::$width;
if (array_key_exists('width', $options) && is_numeric($options['width'])) {
$width = $options['width'];
}
$height = self::$height;
if (array_key_exists('height', $options) && is_numeric($options['height'])) {
$height = $options['height'];
}
$otpauth = self::getKeyUri($type, $label, $secret, $counter, $options);
$url = 'https://chart.googleapis.com/chart?chs=' . $width . 'x'
. $height . '&cht=qr&chld=M|0&chl=' . urlencode($otpauth);
return $url;
}
/**
* Creates a pseudo random Base32 string
*
* This could decode into anything. It's located here as a small helper
* where code that might need base32 usually also needs something like this.
*
* @param integer $length Exact length of output string
* @return string Base32 encoded random
*/
public static function generateRandom($length = 16)
{
$keys = array_merge(range('A','Z'), range(2,7)); // No padding char
$string = '';
for ($i = 0; $i < $length; $i++) {
$string .= $keys[self::getRand()];
}
return $string;
}
/**
* Get random number
*
* @return int Random number between 0 and 31 (including)
*/
private static function getRand()
{
if (function_exists('random_int')) {
// Uses either the PHP7 internal function or the polyfill if present
return random_int(0, 31);
} elseif (function_exists('openssl_random_pseudo_bytes')) {
$bytes = openssl_random_pseudo_bytes(2);
$number = hexdec(bin2hex($bytes));
if ($number > 31) {
$number = $number % 32;
}
return $number;
} else {
return mt_rand(0, 31);
}
}
}
+310
View File
@@ -0,0 +1,310 @@
<?php
namespace Otp;
/**
* One Time Passwords
*
* Last update: 2012-06-16
*
* Implements HOTP and TOTP
*
* HMAC-Based One-time Password(HOTP) algorithm specified in RFC 4226
* @link https://tools.ietf.org/html/rfc4226
*
* Time-based One-time Password (TOTP) algorithm specified in RFC 6238
* @link https://tools.ietf.org/html/rfc6238
*
* As a note: This code is NOT 2038 proof! The min concern is the function
* getBinaryCounter that uses the pack function which can't handle 64bit yet.
*
* Can be easy used with Google Authenticator
* @link https://code.google.com/p/google-authenticator/
*
* @author Christian Riesen <chris.riesen@gmail.com>
* @link http://christianriesen.com
* @license MIT License see LICENSE file
*/
class Otp implements OtpInterface
{
/**
* The digits the code can have
*
* Either 6 or 8.
* Authenticator does only support 6.
*
* @var integer
*/
protected $digits = 6;
/**
* Time in seconds one counter period is long
*
* @var integer
*/
protected $period = 30;
/**
* Possible algorithms
*
* @var array
*/
protected $allowedAlgorithms = array('sha1', 'sha256', 'sha512');
/**
* Currently used algorithm
*
* @var string
*/
protected $algorithm = 'sha1';
/* (non-PHPdoc)
* @see Otp.OtpInterface::hotp()
*/
public function hotp($secret, $counter)
{
if (!is_numeric($counter)) {
throw new \InvalidArgumentException('Counter must be integer');
}
$hash = hash_hmac(
$this->algorithm,
$this->getBinaryCounter($counter),
$secret,
true
);
return str_pad($this->truncate($hash), $this->digits, '0', STR_PAD_LEFT);
}
/* (non-PHPdoc)
* @see Otp.OtpInterface::totp()
*/
public function totp($secret, $timecounter = null)
{
if (is_null($timecounter)) {
$timecounter = $this->getTimecounter();
}
return $this->hotp($secret, $timecounter);
}
/* (non-PHPdoc)
* @see Otp.OtpInterface::checkHotp()
*/
public function checkHotp($secret, $counter, $key)
{
return $this->safeCompare($this->hotp($secret, $counter), $key);
}
/* (non-PHPdoc)
* @see Otp.OtpInterface::checkTotp()
*/
public function checkTotp($secret, $key, $timedrift = 1)
{
if (!is_numeric($timedrift) || $timedrift < 0) {
throw new \InvalidArgumentException('Invalid timedrift supplied');
}
// Counter comes from time now
// Also we check the current timestamp as well as previous and future ones
// according to $timerange
$timecounter = $this->getTimecounter();
$start = $timecounter - ($timedrift);
$end = $timecounter + ($timedrift);
// We first try the current, as it is the most likely to work
if ($this->safeCompare($this->totp($secret, $timecounter), $key)) {
return true;
} elseif ($timedrift == 0) {
// When timedrift is 0, this is the end of the checks
return false;
}
// Well, that didn't work, so try the others
for ($t = $start; $t <= $end; $t = $t + 1) {
if ($t == $timecounter) {
// Already tried that one
continue;
}
if ($this->safeCompare($this->totp($secret, $t), $key)) {
return true;
}
}
// if none worked, then return false
return false;
}
/**
* Changing the used algorithm for hashing
*
* Can only be one of the algorithms in the allowedAlgorithms property.
*
* @param string $algorithm
* @throws \InvalidArgumentException
* @return \Otp\Otp
*/
/*
* This has been disabled since it does not bring the expected results
* according to the RFC test vectors for sha256 or sha512.
* Until that is fixed, the algorithm simply stays at sha1.
* Google Authenticator does not support sha256 and sha512 at the moment.
*
public function setAlgorithm($algorithm)
{
if (!in_array($algorithm, $this->allowedAlgorithms)) {
throw new \InvalidArgumentException('Not an allowed algorithm: ' . $algorithm);
}
$this->algorithm = $algorithm;
return $this;
}
// */
/**
* Get the algorithms name (lowercase)
*
* @return string
*/
public function getAlgorithm()
{
return $this->algorithm;
}
/**
* Setting period lenght for totp
*
* @param integer $period
* @throws \InvalidArgumentException
* @return \Otp\Otp
*/
public function setPeriod($period)
{
if (!is_int($period)) {
throw new \InvalidArgumentException('Period must be an integer');
}
$this->period = $period;
return $this;
}
/**
* Returns the set period value
*
* @return integer
*/
public function getPeriod()
{
return $this->period;
}
/**
* Setting number of otp digits
*
* @param integer $digits Number of digits for the otp (6 or 8)
* @throws \InvalidArgumentException
* @return \Otp\Otp
*/
public function setDigits($digits)
{
if (!in_array($digits, array(6, 8))) {
throw new \InvalidArgumentException('Digits must be 6 or 8');
}
$this->digits = $digits;
return $this;
}
/**
* Returns number of digits in the otp
*
* @return integer
*/
public function getDigits()
{
return $this->digits;
}
/**
* Generates a binary counter for hashing
*
* Warning: Not 2038 safe. Maybe until then pack supports 64bit.
*
* @param integer $counter Counter in integer form
* @return string Binary string
*/
protected function getBinaryCounter($counter)
{
return pack('N*', 0) . pack('N*', $counter);
}
/**
* Generating time counter
*
* This is the time divided by 30 by default.
*
* @return integer Time counter
*/
protected function getTimecounter()
{
return floor(time() / $this->period);
}
/**
* Creates the basic number for otp from hash
*
* This number is left padded with zeros to the required length by the
* calling function.
*
* @param string $hash hmac hash
* @return number
*/
protected function truncate($hash)
{
$offset = ord($hash[19]) & 0xf;
return (
((ord($hash[$offset+0]) & 0x7f) << 24 ) |
((ord($hash[$offset+1]) & 0xff) << 16 ) |
((ord($hash[$offset+2]) & 0xff) << 8 ) |
(ord($hash[$offset+3]) & 0xff)
) % pow(10, $this->digits);
}
/**
* Safely compares two inputs
*
* Assumed inputs are numbers and strings.
* Compares them in a time linear manner. No matter how much you guess
* correct of the partial content, it does not change the time it takes to
* run the entire comparison.
*
* @param mixed $a
* @param mixed $b
* @return boolean
*/
protected function safeCompare($a, $b)
{
$sha1a = sha1($a);
$sha1b = sha1($b);
// Now the compare is always the same length. Even considering minute
// time differences in sha1 creation, all you know is that a longer
// input takes longer to hash, not how long the actual compared value is
$result = 0;
for ($i = 0; $i < 40; $i++) {
$result |= ord($sha1a[$i]) ^ ord($sha1b[$i]);
}
return $result == 0;
}
}
+65
View File
@@ -0,0 +1,65 @@
<?php
namespace Otp;
/**
* Interface for HOTP and TOTP
*
* Last update: 2012-06-16
*
* HMAC-Based One-time Password(HOTP) algorithm specified in RFC 4226
* @link https://tools.ietf.org/html/rfc4226
*
* Time-based One-time Password (TOTP) algorithm specified in RFC 6238
* @link https://tools.ietf.org/html/rfc6238
*
* @author Christian Riesen <chris.riesen@gmail.com>
* @link http://christianriesen.com
* @license MIT License see LICENSE file
*/
interface OtpInterface
{
/**
* Returns OTP using the HOTP algorithm
*
* @param string $secret
* @param integer $counter
* @return string One Time Password
*/
function hotp($secret, $counter);
/**
* Returns OTP using the TOTP algorithm
*
* @param string $secret
* @param integer $timecounter Optional: Uses current time if null
* @return string One Time Password
*/
function totp($secret, $timecounter = null);
/**
* Checks Hotp against a key
*
* This is a helper function, but is here to ensure the Totp can be checked
* in the same manner.
*
* @param string $secret
* @param integer $counter
* @param string $key
*
* @return boolean If key is correct
*/
function checkHotp($secret, $counter, $key);
/**
* Checks Totp agains a key
*
*
* @param string $secret
* @param integer $key
* @param integer $timedrift
*
* @return boolean If key is correct
*/
function checkTotp($secret, $key, $timedrift = 1);
}