Improve 2FA
This commit is contained in:
parent
811254ba93
commit
e62779e267
|
|
@ -40,7 +40,7 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
*/
|
||||
public function getName()
|
||||
{
|
||||
return 'Time-based One-time Password Algorithm';
|
||||
return t('Time-based One-time Password Algorithm');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -55,6 +55,16 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
return $otp->checkTotp(Base32::decode($this->secret), $this->code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called before to prompt the user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeCode()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Set validation code
|
||||
*
|
||||
|
|
@ -66,6 +76,18 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
$this->code = $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate secret
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecret()
|
||||
{
|
||||
$this->secret = GoogleAuthenticator::generateRandom();
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set secret token
|
||||
*
|
||||
|
|
@ -85,10 +107,6 @@ class TotpAuth extends Base implements PostAuthenticationProviderInterface
|
|||
*/
|
||||
public function getSecret()
|
||||
{
|
||||
if (empty($this->secret)) {
|
||||
$this->secret = GoogleAuthenticator::generateRandom();
|
||||
}
|
||||
|
||||
return $this->secret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ class Twofactor extends User
|
|||
}
|
||||
|
||||
/**
|
||||
* Index
|
||||
* Show form to disable/enable 2FA
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
|
|
@ -31,54 +31,45 @@ class Twofactor extends User
|
|||
{
|
||||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$label = $user['email'] ?: $user['username'];
|
||||
|
||||
$provider->setSecret($user['twofactor_secret']);
|
||||
unset($this->sessionStorage->twoFactorSecret);
|
||||
|
||||
$this->response->html($this->layout('twofactor/index', array(
|
||||
'user' => $user,
|
||||
'qrcode_url' => $user['twofactor_activated'] == 1 ? $provider->getQrCodeUrl($label) : '',
|
||||
'key_url' => $user['twofactor_activated'] == 1 ? $provider->getKeyUrl($label) : '',
|
||||
'provider' => $this->authenticationManager->getPostAuthenticationProvider()->getName(),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable 2FA
|
||||
* Show page with secret and test form
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function save()
|
||||
public function show()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$values = $this->request->getValues();
|
||||
$label = $user['email'] ?: $user['username'];
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
|
||||
if (isset($values['twofactor_activated']) && $values['twofactor_activated'] == 1) {
|
||||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 1,
|
||||
'twofactor_secret' => $this->authenticationManager->getPostAuthenticationProvider()->getSecret(),
|
||||
));
|
||||
if (! isset($this->sessionStorage->twoFactorSecret)) {
|
||||
$provider->generateSecret();
|
||||
$provider->beforeCode();
|
||||
$this->sessionStorage->twoFactorSecret = $provider->getSecret();
|
||||
} else {
|
||||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 0,
|
||||
'twofactor_secret' => '',
|
||||
));
|
||||
$provider->setSecret($this->sessionStorage->twoFactorSecret);
|
||||
}
|
||||
|
||||
// Allow the user to test or disable the feature
|
||||
$this->userSession->disablePostAuthentication();
|
||||
|
||||
$this->flash->success(t('User updated successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
|
||||
$this->response->html($this->layout('twofactor/show', array(
|
||||
'user' => $user,
|
||||
'secret' => $this->sessionStorage->twoFactorSecret,
|
||||
'qrcode_url' => $provider->getQrCodeUrl($label),
|
||||
'key_url' => $provider->getKeyUrl($label),
|
||||
)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test code
|
||||
* Test code and save secret
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
|
|
@ -91,14 +82,47 @@ class Twofactor extends User
|
|||
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$provider->setCode(empty($values['code']) ? '' : $values['code']);
|
||||
$provider->setSecret($user['twofactor_secret']);
|
||||
$provider->setSecret($this->sessionStorage->twoFactorSecret);
|
||||
|
||||
if ($provider->authenticate()) {
|
||||
$this->flash->success(t('The two factor authentication code is valid.'));
|
||||
|
||||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 1,
|
||||
'twofactor_secret' => $this->authenticationManager->getPostAuthenticationProvider()->getSecret(),
|
||||
));
|
||||
|
||||
unset($this->sessionStorage->twoFactorSecret);
|
||||
$this->userSession->disablePostAuthentication();
|
||||
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
|
||||
} else {
|
||||
$this->flash->failure(t('The two factor authentication code is not valid.'));
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'show', array('user_id' => $user['id'])));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable 2FA for the current user
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function deactivate()
|
||||
{
|
||||
$user = $this->getUser();
|
||||
$this->checkCurrentUser($user);
|
||||
|
||||
$this->user->update(array(
|
||||
'id' => $user['id'],
|
||||
'twofactor_activated' => 0,
|
||||
'twofactor_secret' => '',
|
||||
));
|
||||
|
||||
// Allow the user to test or disable the feature
|
||||
$this->userSession->disablePostAuthentication();
|
||||
|
||||
$this->flash->success(t('User updated successfully.'));
|
||||
$this->response->redirect($this->helper->url->to('twofactor', 'index', array('user_id' => $user['id'])));
|
||||
}
|
||||
|
||||
|
|
@ -135,6 +159,12 @@ class Twofactor extends User
|
|||
*/
|
||||
public function code()
|
||||
{
|
||||
if (! isset($this->sessionStorage->twoFactorBeforeCodeCalled)) {
|
||||
$provider = $this->authenticationManager->getPostAuthenticationProvider();
|
||||
$provider->beforeCode();
|
||||
$this->sessionStorage->twoFactorBeforeCodeCalled = true;
|
||||
}
|
||||
|
||||
$this->response->html($this->template->layout('twofactor/check', array(
|
||||
'title' => t('Check two factor authentication code'),
|
||||
)));
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ namespace Kanboard\Core\Security;
|
|||
*/
|
||||
interface PostAuthenticationProviderInterface extends AuthenticationProviderInterface
|
||||
{
|
||||
/**
|
||||
* Called only one time before to prompt the user for pin code
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function beforeCode();
|
||||
|
||||
/**
|
||||
* Set user pin-code
|
||||
*
|
||||
|
|
@ -18,6 +25,14 @@ interface PostAuthenticationProviderInterface extends AuthenticationProviderInte
|
|||
*/
|
||||
public function setCode($code);
|
||||
|
||||
/**
|
||||
* Generate secret if necessary
|
||||
*
|
||||
* @access public
|
||||
* @return string
|
||||
*/
|
||||
public function generateSecret();
|
||||
|
||||
/**
|
||||
* Set secret token (fetched from user profile)
|
||||
*
|
||||
|
|
|
|||
|
|
@ -21,15 +21,7 @@ class Token extends Base
|
|||
*/
|
||||
public static function getToken()
|
||||
{
|
||||
if (function_exists('random_bytes')) {
|
||||
return bin2hex(random_bytes(30));
|
||||
} elseif (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return bin2hex(openssl_random_pseudo_bytes(30));
|
||||
} elseif (ini_get('open_basedir') === '' && strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') {
|
||||
return hash('sha256', file_get_contents('/dev/urandom', false, null, 0, 30));
|
||||
}
|
||||
|
||||
return hash('sha256', uniqid(mt_rand(), true));
|
||||
return bin2hex(random_bytes(30));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -2,42 +2,14 @@
|
|||
<h2><?= t('Two factor authentication') ?></h2>
|
||||
</div>
|
||||
|
||||
<form method="post" action="<?= $this->url->href('twofactor', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off">
|
||||
|
||||
<form method="post" action="<?= $this->url->href('twofactor', $user['twofactor_activated'] == 1 ? 'deactivate' : 'show', array('user_id' => $user['id'])) ?>" autocomplete="off">
|
||||
<?= $this->form->csrf() ?>
|
||||
<?= $this->form->checkbox('twofactor_activated', t('Enable/disable two factor authentication'), 1, isset($user['twofactor_activated']) && $user['twofactor_activated'] == 1) ?>
|
||||
|
||||
<p><?= t('Two-Factor Provider: ') ?><strong><?= $this->e($provider) ?></strong></p>
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<?php if ($user['twofactor_activated'] == 1): ?>
|
||||
<div class="listing">
|
||||
<p><?= t('Secret key: ') ?><strong><?= $this->e($user['twofactor_secret']) ?></strong> (base32)</p>
|
||||
|
||||
<?php if (! empty($qrcode_url)): ?>
|
||||
<p><br/><img src="<?= $qrcode_url ?>"/><br/><br/></p>
|
||||
<?php endif ?>
|
||||
|
||||
<p>
|
||||
<?php if (! empty($key_url)): ?>
|
||||
<?= t('This QR code contains the key URI: ') ?><strong><?= $this->e($key_url) ?></strong>
|
||||
<br/><br/>
|
||||
<?php if ($user['twofactor_activated'] == 1): ?>
|
||||
<input type="submit" value="<?= t('Disable two-factor authentication') ?>" class="btn btn-red"/>
|
||||
<?php else: ?>
|
||||
<input type="submit" value="<?= t('Enable two-factor authentication') ?>" class="btn btn-blue"/>
|
||||
<?php endif ?>
|
||||
<?= t('Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).') ?>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<h3><?= t('Test your device') ?></h3>
|
||||
<form method="post" action="<?= $this->url->href('twofactor', 'test', array('user_id' => $user['id'])) ?>" autocomplete="off">
|
||||
|
||||
<?= $this->form->csrf() ?>
|
||||
<?= $this->form->label(t('Code'), 'code') ?>
|
||||
<?= $this->form->text('code', array(), array(), array('placeholder="123456"'), 'form-numeric') ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Check my code') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
</form>
|
||||
<?php endif ?>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
<div class="page-header">
|
||||
<h2><?= t('Two factor authentication') ?></h2>
|
||||
</div>
|
||||
|
||||
<?php if (! empty($secret) || ! empty($qrcode_url) || ! empty($key_url)): ?>
|
||||
<div class="listing">
|
||||
<?php if (! empty($secret)): ?>
|
||||
<p><?= t('Secret key: ') ?><strong><?= $this->e($secret) ?></strong></p>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! empty($qrcode_url)): ?>
|
||||
<p><br/><img src="<?= $qrcode_url ?>"/><br/><br/></p>
|
||||
<?php endif ?>
|
||||
|
||||
<?php if (! empty($key_url)): ?>
|
||||
<p><?= t('This QR code contains the key URI: ') ?><a href="<?= $this->e($key_url) ?>"><?= $this->e($key_url) ?></a></p>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
|
||||
<h3><?= t('Test your device') ?></h3>
|
||||
<form method="post" action="<?= $this->url->href('twofactor', 'test', array('user_id' => $user['id'])) ?>" autocomplete="off">
|
||||
|
||||
<?= $this->form->csrf() ?>
|
||||
<?= $this->form->label(t('Code'), 'code') ?>
|
||||
<?= $this->form->text('code', array(), array(), array('placeholder="123456"', 'autofocus'), 'form-numeric') ?>
|
||||
|
||||
<div class="form-actions">
|
||||
<input type="submit" value="<?= t('Check my code') ?>" class="btn btn-blue"/>
|
||||
</div>
|
||||
</form>
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
"fguillot/simpleLogger" : "1.0.0",
|
||||
"fguillot/simple-validator" : "1.0.0",
|
||||
"league/html-to-markdown" : "~4.0",
|
||||
"paragonie/random_compat": "@stable",
|
||||
"pimple/pimple" : "~3.0",
|
||||
"ramsey/array_column": "@stable",
|
||||
"swiftmailer/swiftmailer" : "~5.4",
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"hash": "061fd1874ea29264f1aeee3d9394f441",
|
||||
"content-hash": "85eaeb7f843881473efd22773a1e97d4",
|
||||
"hash": "6fdfe5fd711d763418007b9f7b2bf5d4",
|
||||
"content-hash": "c90bee5db71739e8c759b5c8f5032699",
|
||||
"packages": [
|
||||
{
|
||||
"name": "christian-riesen/base32",
|
||||
|
|
@ -459,6 +459,54 @@
|
|||
],
|
||||
"time": "2015-11-20 16:20:25"
|
||||
},
|
||||
{
|
||||
"name": "paragonie/random_compat",
|
||||
"version": "1.1.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/paragonie/random_compat.git",
|
||||
"reference": "d762ee5b099a29044603cd4649851e81aa66cb47"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/paragonie/random_compat/zipball/d762ee5b099a29044603cd4649851e81aa66cb47",
|
||||
"reference": "d762ee5b099a29044603cd4649851e81aa66cb47",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": ">=5.2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "4.*|5.*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"files": [
|
||||
"lib/random.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Paragon Initiative Enterprises",
|
||||
"email": "security@paragonie.com",
|
||||
"homepage": "https://paragonie.com"
|
||||
}
|
||||
],
|
||||
"description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
|
||||
"keywords": [
|
||||
"csprng",
|
||||
"pseudorandom",
|
||||
"random"
|
||||
],
|
||||
"time": "2015-12-10 14:48:13"
|
||||
},
|
||||
{
|
||||
"name": "pimple/pimple",
|
||||
"version": "v3.0.2",
|
||||
|
|
@ -876,6 +924,7 @@
|
|||
"minimum-stability": "stable",
|
||||
"stability-flags": {
|
||||
"fguillot/picodb": 20,
|
||||
"paragonie/random_compat": 0,
|
||||
"ramsey/array_column": 0
|
||||
},
|
||||
"prefer-stable": false,
|
||||
|
|
|
|||
|
|
@ -15,6 +15,9 @@ class TotpAuthTest extends Base
|
|||
public function testGetSecret()
|
||||
{
|
||||
$provider = new TotpAuth($this->container);
|
||||
$this->assertEmpty($provider->getSecret());
|
||||
|
||||
$provider->generateSecret();
|
||||
$secret = $provider->getSecret();
|
||||
|
||||
$this->assertNotEmpty($secret);
|
||||
|
|
@ -48,7 +51,7 @@ class TotpAuthTest extends Base
|
|||
{
|
||||
$provider = new TotpAuth($this->container);
|
||||
|
||||
$secret = $provider->getSecret();
|
||||
$secret = $provider->generateSecret();
|
||||
$this->assertNotEmpty($secret);
|
||||
|
||||
$provider->setCode('1234');
|
||||
|
|
|
|||
Loading…
Reference in New Issue