forked from typecho-fans/plugins
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge commit '137d802c18fc11cb5dd4dbe6806e52ddd8a1e5f6' as 'GAuthenti…
…cator'
- Loading branch information
Showing
5 changed files
with
516 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?php | ||
/** | ||
* GAuthenticator Plugin | ||
* | ||
* @copyright Copyright (c) 2018 WeiCN (https://cuojue.org) | ||
* @license GNU General Public License 2.0 | ||
* | ||
*/ | ||
class GAuthenticator_Action extends Typecho_Widget implements Widget_Interface_Do | ||
{ | ||
|
||
public function __construct($request, $response, $params = NULL) | ||
{ | ||
parent::__construct($request, $response, $params); | ||
} | ||
/** | ||
* 验证GAuthenticator POST | ||
* | ||
*/ | ||
public function auth(){ | ||
if(intval($this->request->get('otp'))>0){ | ||
//获取到CODE | ||
if (isset($_SESSION['GAuthenticator'])&&$_SESSION['GAuthenticator']) return;//如果SESSION匹配则直接返回 | ||
$config = Helper::options()->plugin('GAuthenticator'); | ||
require_once 'GoogleAuthenticator.php'; | ||
$referer = $this->request->getReferer(); | ||
$Authenticator = new PHPGangsta_GoogleAuthenticator();//初始化生成类 | ||
$oneCode = intval($this->request->get('otp'));//手机端生成的一次性代码 | ||
if($Authenticator->verifyCode($config->SecretKey, $oneCode, $config->SecretTime)){//验证一次性代码 | ||
$expire = 1 == $this->request->get('remember') ? Helper::options()->time + Helper::options()->timezone + 30*24*3600 : 0; | ||
$_SESSION['GAuthenticator'] = true;//session保存 | ||
Typecho_Cookie::set('__typecho_GAuthenticator',md5($config->SecretKey.Typecho_Cookie::getPrefix().Typecho_Widget::widget('Widget_User')->uid),$expire);//cookie保存 | ||
}else{ | ||
Typecho_Widget::widget('Widget_Notice')->set(_t('两步验证失败'), 'error'); | ||
} | ||
Typecho_Response::redirect($referer); | ||
} | ||
} | ||
|
||
public function action(){ | ||
$this->widget('Widget_User')->pass('administrator'); | ||
$this->on($this->request->is('otp'))->auth(); | ||
} | ||
} | ||
?> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,208 @@ | ||
<?php | ||
|
||
/** | ||
* 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/ | ||
*/ | ||
|
||
class PHPGangsta_GoogleAuthenticator | ||
{ | ||
protected $_codeLength = 6; | ||
|
||
/** | ||
* Create new secret. | ||
* 16 characters, randomly chosen from the allowed base32 characters. | ||
* | ||
* @param int $secretLength | ||
* @return string | ||
*/ | ||
public function createSecret($secretLength = 16) | ||
{ | ||
$validChars = $this->_getBase32LookupTable(); | ||
unset($validChars[32]); | ||
|
||
$secret = ''; | ||
for ($i = 0; $i < $secretLength; $i++) { | ||
$secret .= $validChars[array_rand($validChars)]; | ||
} | ||
return $secret; | ||
} | ||
|
||
/** | ||
* Calculate the code, with given secret and point in time | ||
* | ||
* @param string $secret | ||
* @param int|null $timeSlice | ||
* @return string | ||
*/ | ||
public function getCode($secret, $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); | ||
} | ||
|
||
/** | ||
* Get QR-Code URL for image, from google charts | ||
* | ||
* @param string $name | ||
* @param string $secret | ||
* @param string $title | ||
* @return string | ||
*/ | ||
public function getQRCodeGoogleUrl($name, $secret, $title = null) { | ||
$urlencoded = urlencode('otpauth://totp/'.$name.'?secret='.$secret.''); | ||
if(isset($title)) { | ||
$urlencoded .= urlencode('&issuer='.urlencode($title)); | ||
} | ||
return 'https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl='.$urlencoded.''; | ||
} | ||
|
||
/** | ||
* 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($secret, $code, $discrepancy = 1, $currentTimeSlice = null) | ||
{ | ||
if ($currentTimeSlice === null) { | ||
$currentTimeSlice = floor(time() / 30); | ||
} | ||
|
||
for ($i = -$discrepancy; $i <= $discrepancy; $i++) { | ||
$calculatedCode = $this->getCode($secret, $currentTimeSlice + $i); | ||
if ($calculatedCode == $code ) { | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/** | ||
* Set the code length, should be >=6 | ||
* | ||
* @param int $length | ||
* @return PHPGangsta_GoogleAuthenticator | ||
*/ | ||
public function setCodeLength($length) | ||
{ | ||
$this->_codeLength = $length; | ||
return $this; | ||
} | ||
|
||
/** | ||
* Helper class to decode base32 | ||
* | ||
* @param $secret | ||
* @return bool|string | ||
*/ | ||
protected function _base32Decode($secret) | ||
{ | ||
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($secret, $padding = true) | ||
{ | ||
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 | ||
*/ | ||
protected function _getBase32LookupTable() | ||
{ | ||
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 | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
<?php | ||
if (!defined('__TYPECHO_ROOT_DIR__')) exit; | ||
/** | ||
* Google Authenticator for Typecho | ||
* | ||
* @package GAuthenticator | ||
* @author WeiCN | ||
* @version 0.0.4 | ||
* @link https://cuojue.org/read/Typecho_Google_Authenticator_02.html | ||
*/ | ||
class GAuthenticator_Plugin implements Typecho_Plugin_Interface | ||
{ | ||
private static $pluginName = 'GAuthenticator'; | ||
/** | ||
* 激活插件方法,如果激活失败,直接抛出异常 | ||
* | ||
* @access public | ||
* @return void | ||
* @throws Typecho_Plugin_Exception | ||
*/ | ||
public static function activate() | ||
{ | ||
Helper::addRoute('GAuthenticator', '/GAuthenticator', 'GAuthenticator_Action', 'Action'); | ||
Typecho_Plugin::factory('admin/menu.php')->navBar = array(__CLASS__, 'Authenticator_safe'); | ||
Typecho_Plugin::factory('admin/common.php')->begin = array(__CLASS__, 'Authenticator_verification'); | ||
return _t('当前两步验证还未启用,请进行<a href="options-plugin.php?config=' . self::$pluginName . '">初始化设置</a>'); | ||
} | ||
|
||
/** | ||
* 禁用插件方法,如果禁用失败,直接抛出异常 | ||
* | ||
* @static | ||
* @access public | ||
* @return void | ||
* @throws Typecho_Plugin_Exception | ||
*/ | ||
public static function deactivate(){ | ||
Helper::removeRoute('GAuthenticator'); | ||
} | ||
|
||
/** | ||
* 获取插件配置面板 | ||
* | ||
* @access public | ||
* @param Typecho_Widget_Helper_Form $form 配置面板 | ||
* @return void' | ||
*/ | ||
public static function config(Typecho_Widget_Helper_Form $form) | ||
{ | ||
$qrurl = 'http://qr.liantu.com/api.php?text='.urlencode('otpauth://totp/'.urlencode(Helper::options()->title.':'.Typecho_Widget::widget('Widget_User')->mail).'?secret='.Helper::options()->plugin(self::$pluginName)->SecretKey);//生成安全密钥的二维码网址 | ||
$element = new Typecho_Widget_Helper_Form_Element_Text('SecretKey', NULL, '', _t('SecretKey'), '安装的时候自动计算密钥,手动修改无效,如需要修改请卸载重新安装或者手动修改数据库<br><span style="font-weight: bold; color: #000; text-align: center; display: block;padding: 30px 0 30px 0;font-size: 24px;">请扫描下面的二维码绑定<br><img style="padding-top: 20px;" src="'.$qrurl.'"></span>'); | ||
$form->addInput($element); | ||
$element = new Typecho_Widget_Helper_Form_Element_Text('SecretQRurl', NULL, '', _t('二维码的网址'), '本选项已过时,保留只是为了向下兼容。和上面图片的地址是相同的'); | ||
$form->addInput($element); | ||
$element = new Typecho_Widget_Helper_Form_Element_Text('SecretTime', NULL, 2, _t('容差时间'), '允许的容差时间,单位为30秒的倍数,如果这里是2 那么就是 2* 30 sec 一分钟.'); | ||
$form->addInput($element); | ||
$element = new Typecho_Widget_Helper_Form_Element_Text('SecretCode', NULL, '', _t('客户端代码'), '输入你APP或者其他什么鬼上面显示的六位数字。<br>用兼容的APP上面的扫描二维码或者手动输入第一行的SecretKey即可生成'); | ||
$form->addInput($element); | ||
$element = new Typecho_Widget_Helper_Form_Element_Radio('SecretOn', array('1' => '开启','0' => '关闭'), 0, _t('插件开关'), '这里关掉了,就不需要验证即可登录。'); | ||
$form->addInput($element); | ||
} | ||
/** | ||
* 手动保存配置面板 | ||
* @param $config array 插件配置 | ||
* @param $is_init bool 是否初始化 | ||
*/ | ||
public static function configHandle($config, $is_init) | ||
{ | ||
if ($is_init) {//如果是第一次初始化插件 | ||
require_once 'GoogleAuthenticator.php'; | ||
$Authenticator = new PHPGangsta_GoogleAuthenticator();//初始化生成类 | ||
$config['SecretKey'] = $Authenticator->createSecret();//生成一个随机安全密钥 | ||
$config['SecretQRurl'] = 'http://qr.liantu.com/api.php?text='.urlencode('otpauth://totp/'.urlencode(Helper::options()->title.':'.Typecho_Widget::widget('Widget_User')->mail).'?secret='.$config['SecretKey']);//生成安全密钥的二维码网址 | ||
}else{ | ||
$config_old = Helper::options()->plugin(self::$pluginName); | ||
if(($config['SecretCode']!='' && $config['SecretOn']==1) || $config['SecretOn']==1){//如果启用,并且验证码不为空 | ||
require_once 'GoogleAuthenticator.php'; | ||
$Authenticator = new PHPGangsta_GoogleAuthenticator(); | ||
if($Authenticator->verifyCode($config['SecretKey'], $config['SecretCode'], $config['SecretTime'])){ | ||
$config['SecretOn'] = 1;//如果匹配,则启用 | ||
}else{ | ||
throw new Typecho_Plugin_Exception('两步验证代码校验失败,请重试或选择关闭'); | ||
} | ||
} | ||
$config['SecretKey'] = $config_old->SecretKey;//保持初始化SecretKey不被修改 | ||
$config['SecretQRurl'] = $config_old->SecretQRurl;//保持初始化SecretQRurl不被修改 过时选项 兼容保留 | ||
} | ||
$config['SecretCode'] = '';//每次保存不保存验证码 | ||
Helper::configPlugin(self::$pluginName, $config);//保存插件配置 | ||
} | ||
/** | ||
* 个人用户的配置面板 | ||
* | ||
* @access public | ||
* @param Typecho_Widget_Helper_Form $form | ||
* @return void | ||
*/ | ||
public static function personalConfig(Typecho_Widget_Helper_Form $form){} | ||
|
||
/** | ||
* 插件实现方法 | ||
* | ||
* @access public | ||
* @return void | ||
*/ | ||
public static function Authenticator_safe() | ||
{ | ||
$config = Helper::options()->plugin(self::$pluginName); | ||
if($config->SecretOn==1){ | ||
echo '<span class="message success">'.htmlspecialchars('已启用 Authenticator 验证').'</span>'; | ||
}else{ | ||
echo '<span class="message error">'.htmlspecialchars('未启用 Authenticator 验证').'</span>'; | ||
} | ||
} | ||
|
||
public static function Authenticator_verification() | ||
{ | ||
if(isset($Authenticator_init))return; | ||
$Authenticator_init = true; | ||
if (!Typecho_Widget::widget('Widget_User')->hasLogin()){ | ||
return;//如果没登录则直接返回 | ||
}else{ | ||
//已经登录就验证 | ||
$config = Helper::options()->plugin(self::$pluginName); | ||
if (isset($_SESSION['GAuthenticator'])&&$_SESSION['GAuthenticator']) return;//如果SESSION匹配则直接返回 | ||
if (Typecho_Cookie::get('__typecho_GAuthenticator') == md5($config->SecretKey.Typecho_Cookie::getPrefix().Typecho_Widget::widget('Widget_User')->uid)) return;//如果COOKIE匹配则直接返回 | ||
if($config->SecretOn==1){ | ||
$options = Helper::options(); | ||
$request = new Typecho_Request(); | ||
require_once 'verification.php'; | ||
}else{ | ||
return;//如果未开启插件则直接返回 | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.