From e62779e26781c849bdc24f40e94330bec97f8069 Mon Sep 17 00:00:00 2001 From: Frederic Guillot Date: Tue, 5 Jan 2016 20:31:15 -0500 Subject: Improve 2FA --- app/Auth/TotpAuth.php | 28 +++++-- app/Controller/Twofactor.php | 88 +++++++++++++++------- .../PostAuthenticationProviderInterface.php | 15 ++++ app/Core/Security/Token.php | 10 +-- app/Template/twofactor/index.php | 40 ++-------- app/Template/twofactor/show.php | 31 ++++++++ 6 files changed, 135 insertions(+), 77 deletions(-) create mode 100644 app/Template/twofactor/show.php (limited to 'app') diff --git a/app/Auth/TotpAuth.php b/app/Auth/TotpAuth.php index f41fabd8..e1cc712c 100644 --- a/app/Auth/TotpAuth.php +++ b/app/Auth/TotpAuth.php @@ -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; } diff --git a/app/Controller/Twofactor.php b/app/Controller/Twofactor.php index aeb13acc..8dbfcf66 100644 --- a/app/Controller/Twofactor.php +++ b/app/Controller/Twofactor.php @@ -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'), ))); diff --git a/app/Core/Security/PostAuthenticationProviderInterface.php b/app/Core/Security/PostAuthenticationProviderInterface.php index 88fc2fe5..3f628bb0 100644 --- a/app/Core/Security/PostAuthenticationProviderInterface.php +++ b/app/Core/Security/PostAuthenticationProviderInterface.php @@ -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) * diff --git a/app/Core/Security/Token.php b/app/Core/Security/Token.php index 9fd2d02b..cbd784a8 100644 --- a/app/Core/Security/Token.php +++ b/app/Core/Security/Token.php @@ -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)); } /** diff --git a/app/Template/twofactor/index.php b/app/Template/twofactor/index.php index 4c4ca088..b9ee4b49 100644 --- a/app/Template/twofactor/index.php +++ b/app/Template/twofactor/index.php @@ -2,42 +2,14 @@

-
- + form->csrf() ?> - form->checkbox('twofactor_activated', t('Enable/disable two factor authentication'), 1, isset($user['twofactor_activated']) && $user['twofactor_activated'] == 1) ?> - +

e($provider) ?>

- -
-
- - -
-

e($user['twofactor_secret']) ?> (base32)

- - -




- - -

- - e($key_url) ?> -

+ + + + - -

-
- -

-
- - form->csrf() ?> - form->label(t('Code'), 'code') ?> - form->text('code', array(), array(), array('placeholder="123456"'), 'form-numeric') ?> - -
-
- diff --git a/app/Template/twofactor/show.php b/app/Template/twofactor/show.php new file mode 100644 index 00000000..dd72965a --- /dev/null +++ b/app/Template/twofactor/show.php @@ -0,0 +1,31 @@ + + + +
+ +

e($secret) ?>

+ + + +




+ + + +

e($key_url) ?>

+ +
+ + +

+
+ + form->csrf() ?> + form->label(t('Code'), 'code') ?> + form->text('code', array(), array(), array('placeholder="123456"', 'autofocus'), 'form-numeric') ?> + +
+ +
+
\ No newline at end of file -- cgit v1.2.3