Add two factor authentication

This commit is contained in:
Frederic Guillot
2015-03-31 22:48:14 -04:00
parent 5d393ed996
commit abeeba7167
32 changed files with 615 additions and 22 deletions

View File

@@ -176,6 +176,7 @@ abstract class Base
if (! $this->acl->isPublicAction($controller, $action)) {
$this->handleAuthentication();
$this->handle2FA($controller, $action);
$this->handleAuthorization($controller, $action);
$this->session['has_subtask_inprogress'] = $this->subtask->hasSubtaskInProgress($this->userSession->getId());
@@ -199,6 +200,26 @@ abstract class Base
}
}
/**
* Check 2FA
*
* @access public
*/
public function handle2FA($controller, $action)
{
$controllers = array('twofactor', 'user');
$actions = array('code', 'check', 'logout');
if ($this->userSession->has2FA() && ! $this->userSession->check2FA() && ! in_array($controller, $controllers) && ! in_array($action, $actions)) {
if ($this->request->isAjax()) {
$this->response->text('Not Authorized', 401);
}
$this->response->redirect($this->helper->url('twofactor', 'code', array('user_id' => $user['id'])));
}
}
/**
* Check page access and authorization
*

View File

@@ -0,0 +1,137 @@
<?php
namespace Controller;
use Otp\Otp;
use Otp\GoogleAuthenticator;
use Base32\Base32;
/**
* Two Factor Auth controller
*
* @package controller
* @author Frederic Guillot
*/
class Twofactor extends User
{
/**
* Only the current user can access to 2FA settings
*
* @access private
*/
private function checkCurrentUser(array $user)
{
if ($user['id'] != $this->userSession->getId()) {
$this->forbidden();
}
}
/**
* Index
*
* @access public
*/
public function index()
{
$user = $this->getUser();
$this->checkCurrentUser($user);
$label = $user['email'] ?: $user['username'];
$this->response->html($this->layout('twofactor/index', array(
'user' => $user,
'qrcode_url' => $user['twofactor_activated'] == 1 ? GoogleAuthenticator::getQrCodeUrl('totp', $label, $user['twofactor_secret']) : '',
'key_url' => $user['twofactor_activated'] == 1 ? GoogleAuthenticator::getKeyUri('totp', $label, $user['twofactor_secret']) : '',
)));
}
/**
* Enable/disable 2FA
*
* @access public
*/
public function save()
{
$user = $this->getUser();
$this->checkCurrentUser($user);
$values = $this->request->getValues();
if (isset($values['twofactor_activated']) && $values['twofactor_activated'] == 1) {
$this->user->update(array(
'id' => $user['id'],
'twofactor_activated' => 1,
'twofactor_secret' => GoogleAuthenticator::generateRandom(),
));
}
else {
$this->user->update(array(
'id' => $user['id'],
'twofactor_activated' => 0,
'twofactor_secret' => '',
));
}
$this->session->flash(t('User updated successfully.'));
$this->response->redirect($this->helper->url('twofactor', 'index', array('user_id' => $user['id'])));
}
/**
* Test 2FA
*
* @access public
*/
public function test()
{
$user = $this->getUser();
$this->checkCurrentUser($user);
$otp = new Otp;
$values = $this->request->getValues();
if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) {
$this->session->flash(t('The two factor authentication code is valid.'));
}
else {
$this->session->flashError(t('The two factor authentication code is not valid.'));
}
$this->response->redirect($this->helper->url('twofactor', 'index', array('user_id' => $user['id'])));
}
/**
* Check 2FA
*
* @access public
*/
public function check()
{
$user = $this->getUser();
$this->checkCurrentUser($user);
$otp = new Otp;
$values = $this->request->getValues();
if (! empty($values['code']) && $otp->checkTotp(Base32::decode($user['twofactor_secret']), $values['code'])) {
$this->session['2fa_validated'] = true;
$this->session->flash(t('The two factor authentication code is valid.'));
$this->response->redirect($this->helper->url('app', 'index'));
}
else {
$this->session->flashError(t('The two factor authentication code is not valid.'));
$this->response->redirect($this->helper->url('twofactor', 'code'));
}
}
/**
* Ask the 2FA code
*
* @access public
*/
public function code()
{
$this->response->html($this->template->layout('twofactor/check', array(
'title' => t('Check two factor authentication code'),
)));
}
}

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -836,4 +836,15 @@ return array(
'Help on Hipchat integration' => 'Aide sur l\'intégration avec Hipchat',
'Enable Gravatar images' => 'Activer les images Gravatar',
'Information' => 'Informations',
'Check two factor authentication code' => 'Vérification du code pour l\'authentification à deux-facteurs',
'The two factor authentication code is not valid.' => 'Le code pour l\'authentification à deux-facteurs n\'est pas valide.',
'The two factor authentication code is valid.' => 'Le code pour l\'authentification à deux-facteurs est valide.',
'Code' => 'Code',
'Two factor authentication' => 'Authentification à deux-facteurs',
'Enable/disable two factor authentication' => 'Activer/désactiver l\'authentification à deux-facteurs',
'This QR Ccde contains the key URI: ' => 'Ce code QR contient l\'url de la clé : ',
'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => 'Sauvegardez cette clé secrete dans votre logiciel TOTP (par exemple Google Authenticator ou FreeOTP).',
'Check my code' => 'Vérifier mon code',
'Secret key: ' => 'Clé secrète : ',
'Test your device' => 'Testez votre appareil',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -834,4 +834,15 @@ return array(
// 'Help on Hipchat integration' => '',
// 'Enable Gravatar images' => '',
// 'Information' => '',
// 'Check two factor authentication code' => '',
// 'The two factor authentication code is not valid.' => '',
// 'The two factor authentication code is valid.' => '',
// 'Code' => '',
// 'Two factor authentication' => '',
// 'Enable/disable two factor authentication' => '',
// 'This QR Ccde contains the key URI: ' => '',
// 'Save the secret key in your TOTP software (by example Google Authenticator or FreeOTP).' => '',
// 'Check my code' => '',
// 'Secret key: ' => '',
// 'Test your device' => '',
);

View File

@@ -60,7 +60,8 @@ class User extends Base
'is_ldap_user',
'notifications_enabled',
'google_id',
'github_id'
'github_id',
'twofactor_activated'
);
}

View File

@@ -28,14 +28,41 @@ class UserSession extends Base
unset($user['password']);
}
if (isset($user['twofactor_secret'])) {
unset($user['twofactor_secret']);
}
$user['id'] = (int) $user['id'];
$user['default_project_id'] = (int) $user['default_project_id'];
$user['is_admin'] = (bool) $user['is_admin'];
$user['is_ldap_user'] = (bool) $user['is_ldap_user'];
$user['twofactor_activated'] = (bool) $user['twofactor_activated'];
$this->session['user'] = $user;
}
/**
* Return true if the user has validated the 2FA key
*
* @access public
* @return bool
*/
public function check2FA()
{
return isset($this->session['2fa_validated']) && $this->session['2fa_validated'] === true;
}
/**
* Return true if the user has 2FA enabled
*
* @access public
* @return bool
*/
public function has2FA()
{
return isset($this->session['user']['twofactor_activated']) && $this->session['user']['twofactor_activated'] === true;
}
/**
* Return true if the logged user is admin
*

View File

@@ -6,7 +6,13 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 60;
const VERSION = 61;
function version_61($pdo)
{
$pdo->exec('ALTER TABLE users ADD COLUMN twofactor_activated TINYINT(1) DEFAULT 0');
$pdo->exec('ALTER TABLE users ADD COLUMN twofactor_secret CHAR(16)');
}
function version_60($pdo)
{

View File

@@ -6,7 +6,13 @@ use PDO;
use Core\Security;
use Model\Link;
const VERSION = 40;
const VERSION = 42;
function version_42($pdo)
{
$pdo->exec('ALTER TABLE users ADD COLUMN twofactor_activated BOOLEAN DEFAULT \'0\'');
$pdo->exec('ALTER TABLE users ADD COLUMN twofactor_secret CHAR(16)');
}
function version_41($pdo)
{

View File

@@ -6,7 +6,13 @@ use Core\Security;
use PDO;
use Model\Link;
const VERSION = 59;
const VERSION = 60;
function version_60($pdo)
{
$pdo->exec('ALTER TABLE users ADD COLUMN twofactor_activated INTEGER DEFAULT 0');
$pdo->exec('ALTER TABLE users ADD COLUMN twofactor_secret TEXT');
}
function version_59($pdo)
{

View File

@@ -0,0 +1,10 @@
<form method="post" action="<?= $this->u('twofactor', 'check', array('user_id' => $this->userSession->getId())) ?>" autocomplete="off">
<?= $this->formCsrf() ?>
<?= $this->formLabel(t('Code'), 'code') ?>
<?= $this->formText('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>

View File

@@ -0,0 +1,37 @@
<div class="page-header">
<h2><?= t('Two factor authentication') ?></h2>
</div>
<form method="post" action="<?= $this->u('twofactor', 'save', array('user_id' => $user['id'])) ?>" autocomplete="off">
<?= $this->formCsrf() ?>
<?= $this->formCheckbox('twofactor_activated', t('Enable/disable two factor authentication'), 1, isset($user['twofactor_activated']) && $user['twofactor_activated'] == 1) ?>
<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>
<p><br/><img src="<?= $qrcode_url ?>"/><br/><br/></p>
<p>
<?= t('This QR Ccde contains the key URI: ') ?><strong><?= $this->e($key_url) ?></strong>
<br/><br/>
<?= 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->u('twofactor', 'test', array('user_id' => $user['id'])) ?>" autocomplete="off">
<?= $this->formCsrf() ?>
<?= $this->formLabel(t('Code'), 'code') ?>
<?= $this->formText('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 ?>

View File

@@ -17,6 +17,7 @@
<th><?= $paginator->order(t('Name'), 'name') ?></th>
<th><?= $paginator->order(t('Email'), 'email') ?></th>
<th><?= $paginator->order(t('Administrator'), 'is_admin') ?></th>
<th><?= $paginator->order(t('Two factor authentication'), 'twofactor_activated') ?></th>
<th><?= $paginator->order(t('Default project'), 'default_project_id') ?></th>
<th><?= $paginator->order(t('Notifications'), 'notifications_enabled') ?></th>
<th><?= t('External accounts') ?></th>
@@ -39,6 +40,9 @@
<td>
<?= $user['is_admin'] ? t('Yes') : t('No') ?>
</td>
<td>
<?= $user['twofactor_activated'] ? t('Yes') : t('No') ?>
</td>
<td>
<?= (isset($user['default_project_id']) && isset($projects[$user['default_project_id']])) ? $this->e($projects[$user['default_project_id']]) : t('None'); ?>
</td>

View File

@@ -1,10 +1,32 @@
<div class="sidebar">
<h2><?= t('Actions') ?></h2>
<h2><?= t('Information') ?></h2>
<ul>
<li>
<?= $this->a(t('Summary'), 'user', 'show', array('user_id' => $user['id'])) ?>
</li>
<?php if ($this->userSession->isAdmin()): ?>
<li>
<?= $this->a(t('User dashboard'), 'app', 'dashboard', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('User calendar'), 'user', 'calendar', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
<?php if ($this->userSession->isAdmin() || $this->userSession->isCurrentUser($user['id'])): ?>
<li>
<?= $this->a(t('Time tracking'), 'user', 'timesheet', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('Last logins'), 'user', 'last', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('Persistent connections'), 'user', 'sessions', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
</ul>
<h2><?= t('Actions') ?></h2>
<ul>
<?php if ($this->userSession->isAdmin() || $this->userSession->isCurrentUser($user['id'])): ?>
<li>
<?= $this->a(t('Edit profile'), 'user', 'edit', array('user_id' => $user['id'])) ?>
@@ -16,30 +38,21 @@
</li>
<?php endif ?>
<?php if ($this->userSession->isCurrentUser($user['id'])): ?>
<li>
<?= $this->a(t('Two factor authentication'), 'twofactor', 'index', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
<li>
<?= $this->a(t('Email notifications'), 'user', 'notifications', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('External accounts'), 'user', 'external', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('Last logins'), 'user', 'last', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('Persistent connections'), 'user', 'sessions', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('Time tracking'), 'user', 'timesheet', array('user_id' => $user['id'])) ?>
</li>
<?php endif ?>
<?php if ($this->userSession->isAdmin()): ?>
<li>
<?= $this->a(t('User dashboard'), 'app', 'dashboard', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('User calendar'), 'user', 'calendar', array('user_id' => $user['id'])) ?>
</li>
<li>
<?= $this->a(t('Hourly rates'), 'hourlyrate', 'index', array('user_id' => $user['id'])) ?>
</li>