mirror of https://github.com/MISP/MISP
Merge branch 'totp' into develop
commit
1dee4a760d
|
@ -310,7 +310,7 @@ class AppController extends Controller
|
|||
$this->__accessMonitor($user);
|
||||
|
||||
} else {
|
||||
$preAuthActions = array('login', 'register', 'getGpgPublicKey', 'logout401');
|
||||
$preAuthActions = array('login', 'register', 'getGpgPublicKey', 'logout401', 'otp');
|
||||
if (!empty(Configure::read('Security.email_otp_enabled'))) {
|
||||
$preAuthActions[] = 'email_otp';
|
||||
}
|
||||
|
@ -601,6 +601,12 @@ class AppController extends Controller
|
|||
return true;
|
||||
}
|
||||
|
||||
// Check if user must create TOTP secret, force them to be on that page as long as needed.
|
||||
if (empty($user['totp']) && Configure::read('Security.otp_required') && !$this->_isControllerAction(['users' => ['terms', 'change_pw', 'logout', 'login', 'totp_new']])) { // TOTP is mandatory for users, prevent login until the user has configured their TOTP
|
||||
$this->redirect(array('controller' => 'users', 'action' => 'totp_new', 'admin' => false));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if user accepted terms and conditions
|
||||
if (!$user['termsaccepted'] && !empty(Configure::read('MISP.terms_file')) && !$this->_isControllerAction(['users' => ['terms', 'logout', 'login', 'downloadTerms']])) {
|
||||
//if ($this->_isRest()) throw new MethodNotAllowedException('You have not accepted the terms of use yet, please log in via the web interface and accept them.');
|
||||
|
|
|
@ -745,6 +745,10 @@ class ACLComponent extends Component
|
|||
'downloadTerms' => array('*'),
|
||||
'edit' => array('self_management_enabled'),
|
||||
'email_otp' => array('*'),
|
||||
'otp' => array('*'),
|
||||
'hotp' => array('*'),
|
||||
'totp_new' => array('*'),
|
||||
'totp_delete' => array('perm_site_admin'),
|
||||
'searchGpgKey' => array('*'),
|
||||
'fetchGpgKey' => array('*'),
|
||||
'histogram' => array('*'),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller');
|
||||
App::uses('AppController', 'Controller', 'OTPHP\TOTP');
|
||||
|
||||
/**
|
||||
* @property User $User
|
||||
|
@ -29,7 +29,7 @@ class UsersController extends AppController
|
|||
parent::beforeFilter();
|
||||
|
||||
// what pages are allowed for non-logged-in users
|
||||
$allowedActions = array('login', 'logout', 'getGpgPublicKey', 'logout401');
|
||||
$allowedActions = array('login', 'logout', 'getGpgPublicKey', 'logout401', 'otp');
|
||||
if(!empty(Configure::read('Security.email_otp_enabled'))) {
|
||||
$allowedActions[] = 'email_otp';
|
||||
}
|
||||
|
@ -88,6 +88,7 @@ class UsersController extends AppController
|
|||
unset($user['User']['authkey']);
|
||||
}
|
||||
$user['User']['password'] = '*****';
|
||||
$user['User']['totp'] = '*****';
|
||||
$temp = [];
|
||||
$objectsToInclude = array('User', 'Role', 'UserSetting', 'Organisation');
|
||||
foreach ($objectsToInclude as $objectToInclude) {
|
||||
|
@ -594,17 +595,7 @@ class UsersController extends AppController
|
|||
unset($user['User']['authkey']);
|
||||
}
|
||||
if ($this->_isRest()) {
|
||||
$user['User']['password'] = '*****';
|
||||
$temp = array();
|
||||
foreach ($user['UserSetting'] as $v) {
|
||||
$temp[$v['setting']] = $v['value'];
|
||||
}
|
||||
$user['UserSetting'] = $temp;
|
||||
return $this->RestResponse->viewData(array(
|
||||
'User' => $user['User'],
|
||||
'Role' => $user['Role'],
|
||||
'UserSetting' => $user['UserSetting']
|
||||
), $this->response->type());
|
||||
return $this->RestResponse->viewData($this->__massageUserObject($user), $this->response->type());
|
||||
}
|
||||
$this->set('user', $user);
|
||||
|
||||
|
@ -1192,18 +1183,30 @@ class UsersController extends AppController
|
|||
throw new ForbiddenException('You have reached the maximum number of login attempts. Please wait ' . $expire . ' seconds and try again.');
|
||||
}
|
||||
}
|
||||
// Check the length of the user's authkey match old format. This can be removed in future.
|
||||
$userPass = $this->User->find('first', [
|
||||
$unauth_user = $this->User->find('first', [
|
||||
'conditions' => ['User.email' => $this->request->data['User']['email']],
|
||||
'fields' => ['User.password'],
|
||||
'fields' => ['User.password', 'User.totp', 'User.hotp_counter'],
|
||||
'recursive' => -1,
|
||||
]);
|
||||
if (!empty($userPass) && strlen($userPass['User']['password']) === 40) {
|
||||
$oldHash = true;
|
||||
unset($this->Auth->authenticate['Form']['passwordHasher']); // use default password hasher
|
||||
$this->Auth->constructAuthenticate();
|
||||
if ($unauth_user) {
|
||||
// Check the length of the user's authkey match old format. This can be removed in future.
|
||||
$userPass = $unauth_user['User']['password'];
|
||||
if (!empty($userPass) && strlen($userPass) === 40) {
|
||||
$oldHash = true;
|
||||
unset($this->Auth->authenticate['Form']['passwordHasher']); // use default password hasher
|
||||
$this->Auth->constructAuthenticate();
|
||||
}
|
||||
// user has TOTP token, check creds and redirect to TOTP validation
|
||||
if (!empty($unauth_user['User']['totp']) && !$unauth_user['User']['disabled'] && class_exists('\OTPHP\TOTP')) {
|
||||
$user = $this->Auth->identify($this->request, $this->response);
|
||||
if ($user && !$user['disabled']) {
|
||||
$this->Session->write('otp_user', $user);
|
||||
return $this->redirect('otp');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// if instance requires email OTP
|
||||
if ($this->request->is('post') && Configure::read('Security.email_otp_enabled')) {
|
||||
$user = $this->Auth->identify($this->request, $this->response);
|
||||
if ($user && !$user['disabled']) {
|
||||
|
@ -1754,6 +1757,167 @@ class UsersController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
public function otp()
|
||||
{
|
||||
$user = $this->Session->read('otp_user');
|
||||
if (empty($user)) {
|
||||
$this->redirect('login');
|
||||
}
|
||||
if ($this->request->is('post') && isset($this->request->data['User']['otp'])) {
|
||||
$this->Bruteforce = ClassRegistry::init('Bruteforce');
|
||||
if ($this->Bruteforce->isBlocklisted($user['email'])) {
|
||||
$expire = Configure::check('SecureAuth.expire') ? Configure::read('SecureAuth.expire') : 300;
|
||||
throw new ForbiddenException('You have reached the maximum number of login attempts. Please wait ' . $expire . ' seconds and try again.');
|
||||
}
|
||||
$secret = $user['totp'];
|
||||
$totp = \OTPHP\TOTP::create($secret);
|
||||
$hotp = \OTPHP\HOTP::create($secret);
|
||||
if ($totp->verify(trim($this->request->data['User']['otp']))) {
|
||||
// OTP is correct, we login the user with CakePHP
|
||||
$this->Auth->login($user);
|
||||
$this->_postlogin();
|
||||
} elseif (isset($user['hotp_counter']) && $hotp->verify(trim($this->request->data['User']['otp']), $user['hotp_counter'])) {
|
||||
// HOTP is correct, update the counter and login
|
||||
$this->User->id = $user['id'];
|
||||
$this->User->saveField('hotp_counter', $user['hotp_counter']+1);
|
||||
$this->Auth->login($user);
|
||||
$this->_postlogin();
|
||||
} else {
|
||||
$this->Flash->error(__("The OTP is incorrect or has expired"));
|
||||
$fieldsDescrStr = 'User (' . $user['id'] . '): ' . $user['email']. ' wrong OTP token';
|
||||
$this->User->extralog($user, "login_fail", $fieldsDescrStr, '');
|
||||
$this->Bruteforce->insert($user['email']);
|
||||
}
|
||||
}
|
||||
// GET Request or wrong OTP, just show the form
|
||||
$this->set('totp', $user['totp']? true : false);
|
||||
$this->set('hotp_counter', $user['hotp_counter']);
|
||||
}
|
||||
|
||||
public function hotp()
|
||||
{
|
||||
if (!class_exists('\OTPHP\HOTP')) {
|
||||
$this->Flash->error(__("The required PHP libraries to support OTP are not installed. Please contact your administrator to address this."));
|
||||
$this->redirect($this->referer());
|
||||
}
|
||||
|
||||
$user = $this->User->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('User.id' => $this->Auth->user('id')),
|
||||
'fields' => array(
|
||||
'totp', 'email', 'id', 'hotp_counter'
|
||||
)
|
||||
));
|
||||
$hotp = \OTPHP\HOTP::create($user['User']['totp'], $user['User']['hotp_counter']);
|
||||
$hotp_codes = [];
|
||||
for ($i=$user['User']['hotp_counter']; $i < $user['User']['hotp_counter']+50 ; $i++) {
|
||||
$hotp_codes[$i] = $hotp->at($i);
|
||||
}
|
||||
$this->set('hotp_codes', $hotp_codes);
|
||||
}
|
||||
|
||||
public function totp_new()
|
||||
{
|
||||
if (Configure::read('LinOTPAuth.enabled')) {
|
||||
$this->Flash->error(__("LinOTP is enabled for this instance. Build-in TOTP should not be used."));
|
||||
$this->redirect($this->referer());
|
||||
}
|
||||
if (!class_exists('\OTPHP\TOTP') || !class_exists('\BaconQrCode\Writer')) {
|
||||
$this->Flash->error(__("The required PHP libraries to support TOTP are not installed. Please contact your administrator to address this."));
|
||||
$this->redirect($this->referer());
|
||||
}
|
||||
// only allow the users themselves to generate a TOTP secret.
|
||||
// If TOTP is enforced they will be invited to generate it at first login
|
||||
$user = $this->User->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('User.id' => $this->Auth->user('id')),
|
||||
'fields' => array(
|
||||
'totp', 'email', 'id'
|
||||
)
|
||||
));
|
||||
if (empty($user)) {
|
||||
throw new NotFoundException(__('Invalid user'));
|
||||
}
|
||||
// do not allow this page to be accessed if the current already has a TOTP. Just redirect to the users details page with a Flash->error()
|
||||
if ($user['User']['totp']) {
|
||||
$this->Flash->error(__("Your account already has an TOTP. Please contact your organisational administrator to change or delete it."));
|
||||
$this->redirect($this->referer());
|
||||
}
|
||||
|
||||
$secret = $this->Session->read('otp_secret'); // Reload secret from session.
|
||||
if ($secret) {
|
||||
$totp = \OTPHP\TOTP::create($secret);
|
||||
} else {
|
||||
$totp = \OTPHP\TOTP::create();
|
||||
$secret = $totp->getSecret();
|
||||
$this->Session->write('otp_secret', $secret); // Store in session, this is to keep the same QR code even if the page refreshes.
|
||||
}
|
||||
if ($this->request->is('post') && isset($this->request->data['User']['otp'])) {
|
||||
if ($totp->verify(trim($this->request->data['User']['otp']))) {
|
||||
// we know the user can generate TOTP tokens, save the new TOTP to the database
|
||||
$this->User->id = $user['User']['id'];
|
||||
$this->User->saveField('totp', $secret);
|
||||
$this->User->saveField('hotp_counter', 0);
|
||||
$this->_refreshAuth();
|
||||
$this->Flash->info(__('The OTP is correct and now active for your account.'));
|
||||
$fieldsDescrStr = 'User (' . $user['User']['id'] . '): ' . $user['User']['email']. ' TOTP token created';
|
||||
$this->User->extralog($this->Auth->user(), "update", $fieldsDescrStr, '');
|
||||
// redirect to a page that gives the next 50 HOTP
|
||||
$this->redirect(array('controller' => 'users', 'action'=> 'hotp'));
|
||||
} else {
|
||||
$this->Flash->error(__("The OTP is incorrect or has expired."));
|
||||
}
|
||||
} else {
|
||||
// GET Request, just show the form
|
||||
}
|
||||
// generate QR code with the secret
|
||||
$renderer = new \BaconQrCode\Renderer\ImageRenderer(
|
||||
new \BaconQrCode\Renderer\RendererStyle\RendererStyle(200),
|
||||
new \BaconQrCode\Renderer\Image\SvgImageBackEnd()
|
||||
);
|
||||
$writer = new \BaconQrCode\Writer($renderer);
|
||||
$totp->setLabel($user['User']['email']);
|
||||
$totp->setIssuer(Configure::read('MISP.org') . ' MISP');
|
||||
$qrcode = $writer->writeString($totp->getProvisioningUri());
|
||||
$qrcode = preg_replace('/^.+\n/', '', $qrcode); // ignore first <?xml version line
|
||||
|
||||
$this->set('qrcode', $qrcode);
|
||||
$this->set('secret', $secret);
|
||||
}
|
||||
|
||||
public function totp_delete($id) {
|
||||
if ($this->request->is('post') || $this->request->is('delete')) {
|
||||
$user = $this->User->find('first', array(
|
||||
'conditions' => $this->__adminFetchConditions($id),
|
||||
'recursive' => -1
|
||||
));
|
||||
if (empty($user)) {
|
||||
throw new NotFoundException(__('Invalid user'));
|
||||
}
|
||||
$this->User->id = $id;
|
||||
if ($this->User->saveField('totp', null)) {
|
||||
$fieldsDescrStr = 'User (' . $id . '): ' . $user['User']['email'] . ' TOTP deleted';
|
||||
$this->User->extralog($this->Auth->user(), "update", $fieldsDescrStr, '');
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->saveSuccessResponse('User', 'admin_totp_delete', $id, $this->response->type(), 'User TOTP deleted.');
|
||||
} else {
|
||||
$this->Flash->success(__('User TOTP deleted'));
|
||||
$this->redirect('/admin/users/index');
|
||||
}
|
||||
}
|
||||
$this->Flash->error(__('User TOTP was not deleted'));
|
||||
$this->redirect('/admin/users/index');
|
||||
} else {
|
||||
$this->set(
|
||||
'question',
|
||||
__('Are you sure you want to delete the TOTP of the user?.')
|
||||
);
|
||||
$this->set('title', __('Delete user TOTP'));
|
||||
$this->set('actionName', 'Delete');
|
||||
$this->render('/genericTemplates/confirm');
|
||||
}
|
||||
}
|
||||
|
||||
public function email_otp()
|
||||
{
|
||||
$user = $this->Session->read('email_otp_user');
|
||||
|
@ -1773,6 +1937,8 @@ class UsersController extends AppController
|
|||
$this->_postlogin();
|
||||
} else {
|
||||
$this->Flash->error(__("The OTP is incorrect or has expired"));
|
||||
$fieldsDescrStr = 'User (' . $user['id'] . '): ' . $user['email']. ' wrong email OTP token';
|
||||
$this->User->extralog($user, "login_fail", $fieldsDescrStr, '');
|
||||
}
|
||||
} else {
|
||||
// GET Request
|
||||
|
|
|
@ -84,7 +84,7 @@ class AppModel extends Model
|
|||
87 => false, 88 => false, 89 => false, 90 => false, 91 => false, 92 => false,
|
||||
93 => false, 94 => false, 95 => true, 96 => false, 97 => true, 98 => false,
|
||||
99 => false, 100 => false, 101 => false, 102 => false, 103 => false, 104 => false,
|
||||
105 => false, 106 => false, 107 => false, 108 => false, 109 => false
|
||||
105 => false, 106 => false, 107 => false, 108 => false, 109 => false, 110 => false
|
||||
);
|
||||
|
||||
const ADVANCED_UPDATES_DESCRIPTION = array(
|
||||
|
@ -1952,6 +1952,9 @@ class AppModel extends Model
|
|||
break;
|
||||
case 109:
|
||||
$sqlArray[] = "UPDATE `over_correlating_values` SET `value` = LOWER(`value`) COLLATE utf8mb4_unicode_ci;";
|
||||
case 110:
|
||||
$sqlArray[] = "ALTER TABLE `users` ADD `totp` varchar(255) DEFAULT NULL;";
|
||||
$sqlArray[] = "ALTER TABLE `users` ADD `hotp_counter` int(11) DEFAULT NULL;";
|
||||
break;
|
||||
case 'fixNonEmptySharingGroupID':
|
||||
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
|
||||
|
|
|
@ -2154,7 +2154,7 @@ class Server extends AppModel
|
|||
return true;
|
||||
}
|
||||
|
||||
public function otpBeforeHook($setting, $value)
|
||||
public function email_otpBeforeHook($setting, $value)
|
||||
{
|
||||
if ($value && !empty(Configure::read('MISP.disable_emailing'))) {
|
||||
return __('Emailing is currently disabled. Enabling OTP without e-mailing being configured would lock all users out.');
|
||||
|
@ -2162,6 +2162,17 @@ class Server extends AppModel
|
|||
return true;
|
||||
}
|
||||
|
||||
public function otpBeforeHook($setting, $value)
|
||||
{
|
||||
if ($value && (!class_exists('\OTPHP\TOTP') || !class_exists('\BaconQrCode\Writer'))) {
|
||||
return __('The TOTP and QR code generation libraries are not installed. Enabling OTP without those libraries installed would lock all users out.');
|
||||
}
|
||||
if ($value && Configure::read('LinOTPAuth.enabled')) {
|
||||
return __('The TOTP and LinOTPAuth should not be used at the same time.');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function testForRPZSerial($value)
|
||||
{
|
||||
if ($this->testForEmpty($value) !== true) {
|
||||
|
@ -6386,12 +6397,21 @@ class Server extends AppModel
|
|||
'type' => 'boolean',
|
||||
'null' => true,
|
||||
],
|
||||
'otp_required' => array(
|
||||
'level' => 2,
|
||||
'description' => __('Require authentication with OTP. Users that do not have (T/H)OTP configured will be forced to create a token at first login. You cannot use it in combination with external authentication plugins.'),
|
||||
'value' => false,
|
||||
'test' => 'testBool',
|
||||
'beforeHook' => 'otpBeforeHook',
|
||||
'type' => 'boolean',
|
||||
'null' => true
|
||||
),
|
||||
'email_otp_enabled' => array(
|
||||
'level' => 2,
|
||||
'description' => __('Enable two step authentication with a OTP sent by email. Requires e-mailing to be enabled. Warning: You cannot use it in combination with external authentication plugins.'),
|
||||
'value' => false,
|
||||
'test' => 'testBool',
|
||||
'beforeHook' => 'otpBeforeHook',
|
||||
'beforeHook' => 'email_otpBeforeHook',
|
||||
'type' => 'boolean',
|
||||
'null' => true
|
||||
),
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
<?php
|
||||
// Beware, this field type does NOT protect against injections.
|
||||
// So please ensure all data is safe or use h() on the variables before sending them here
|
||||
echo $fieldData['html'];
|
||||
|
|
@ -143,6 +143,16 @@
|
|||
'privacy' => 1,
|
||||
'requirement' => empty(Configure::read('Security.advanced_authkeys'))
|
||||
),
|
||||
array(
|
||||
'name' => '',
|
||||
'header_title' => __('TOTP'),
|
||||
'icon' => 'mobile',
|
||||
'element' => 'boolean',
|
||||
'sort' => 'User.totp',
|
||||
'class' => 'short',
|
||||
'data_path' => 'User.totp',
|
||||
'colors' => true,
|
||||
),
|
||||
array(
|
||||
'name' => '',
|
||||
'header_title' => __('Contact alert'),
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
<div class="users form">
|
||||
<h2><?php echo __('Paper based Single Use Tokens');?></h2>
|
||||
<p><?php echo __('The following list contains the next tokens in case you do not have your phone/software. <br />Make sure you print these out.');?></p>
|
||||
<pre><?php
|
||||
$count = count($hotp_codes);
|
||||
$rows = round($count / 5); // 5 rows
|
||||
$i = 1;
|
||||
foreach ($hotp_codes as $key => $value) {
|
||||
if ($key < 10) print(" ");
|
||||
print("$key: $value");
|
||||
if ($i == 5) {
|
||||
print("\n");
|
||||
$i = 1;
|
||||
} else {
|
||||
print(" ");
|
||||
$i++;
|
||||
}
|
||||
|
||||
}
|
||||
?>
|
||||
</pre>
|
||||
</div>
|
||||
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'globalActions', 'menuItem' => 'view'));
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php echo $this->Flash->render(); ?>
|
||||
|
||||
<div class="actions sideMenu">
|
||||
<div style="padding: 10px;">
|
||||
<p><?php echo __("Your account requires an OTP token to login. (One-Time Password)");?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
$label = __("Enter either your TOTP or paper based Single Use Token number ") . $hotp_counter;
|
||||
|
||||
echo $this->element('/genericElements/Form/genericForm', array(
|
||||
"form" => $this->Form,
|
||||
"data" => array(
|
||||
"title" => __("Validate your One Time Password"),
|
||||
"fields" => array(
|
||||
array(
|
||||
"field" => "otp",
|
||||
"label" => $label,
|
||||
"type" => "text",
|
||||
"placeholder" => __("Enter your OTP here"),
|
||||
)
|
||||
),
|
||||
"submit" => array (
|
||||
"action" => "otp",
|
||||
),
|
||||
)));
|
||||
?>
|
|
@ -0,0 +1,38 @@
|
|||
<?php echo $this->Flash->render(); ?>
|
||||
<?php
|
||||
$detailsHtml = __("To enable TOTP for your account, scan the following QR code with your TOTP application and validate the token.");;
|
||||
$secretHtml = __("Alternatively you can enter the following secret in your TOTP application: ") . "<pre>" . $secret . "</pre>";
|
||||
|
||||
echo $this->element('/genericElements/Form/genericForm', array(
|
||||
"form" => $this->Form,
|
||||
"data" => array(
|
||||
"title" => __("Validate your One Time Password"),
|
||||
"fields" => array(
|
||||
array(
|
||||
"type" => 'html',
|
||||
"field" => "html",
|
||||
"html" => $detailsHtml
|
||||
),
|
||||
array(
|
||||
"type" => 'html',
|
||||
"field" => 'qrcode',
|
||||
"html" => $qrcode
|
||||
),
|
||||
array(
|
||||
"type" => 'html',
|
||||
"field" => "secret",
|
||||
"html" => $secretHtml
|
||||
),
|
||||
array(
|
||||
"field" => "otp",
|
||||
"label" => __("One Time Password verification"),
|
||||
"type" => "text",
|
||||
"placeholder" => __("Enter your OTP code here"),
|
||||
)
|
||||
),
|
||||
"submit" => array (
|
||||
"action" => "totp",
|
||||
),
|
||||
)));
|
||||
?>
|
||||
</div>
|
|
@ -18,6 +18,23 @@ foreach ($notificationTypes as $notificationType => $description) {
|
|||
}
|
||||
$notificationsHtml .= '</table>';
|
||||
|
||||
$isTotp = isset($user['User']['totp']) ? true : false;
|
||||
$boolean = sprintf(
|
||||
'<span class="%s">%s</span>',
|
||||
$isTotp ? 'label label-success label-padding' : 'label label-important label-padding',
|
||||
$isTotp ? __('Yes') : __('No'));
|
||||
$totpHtml = $boolean;
|
||||
$totpHtml .= (!$isTotp && !$admin_view ? $this->Html->link(__('Generate'), array('action' => 'totp_new')) : '');
|
||||
$totpHtml .= ($isTotp && !$admin_view ? $this->Html->link(__('View paper tokens'), array('action' => 'hotp', $user['User']['id'])): '');
|
||||
|
||||
if ($admin_view && $isSiteAdmin && $isTotp) {
|
||||
$totpHtml .= sprintf(
|
||||
'<a href="#" onClick="openGenericModal(\'%s/users/totp_delete/%s\')">%s</a>',
|
||||
h($baseurl),
|
||||
h($user['User']['id']),
|
||||
__('Delete')
|
||||
);
|
||||
}
|
||||
$table_data = [
|
||||
array('key' => __('ID'), 'value' => $user['User']['id']),
|
||||
array(
|
||||
|
@ -37,6 +54,11 @@ $notificationsHtml .= '</table>';
|
|||
'key' => __('Role'),
|
||||
'html' => $this->Html->link($user['Role']['name'], array('controller' => 'roles', 'action' => 'view', $user['Role']['id'])),
|
||||
),
|
||||
// array('key' => __('TOTP'), 'boolean' => isset($user['User']['totp']) ? true : false),
|
||||
array(
|
||||
'key' => __('TOTP'),
|
||||
'html' => $totpHtml
|
||||
),
|
||||
array(
|
||||
'key' => __('Email notifications'),
|
||||
'html' => $notificationsHtml,
|
||||
|
|
|
@ -11,7 +11,9 @@
|
|||
"ext-pcre": "*",
|
||||
"kamisama/cake-resque": "4.1.2",
|
||||
"pear/crypt_gpg": "1.6.7",
|
||||
"monolog/monolog": "1.24.0"
|
||||
"monolog/monolog": "1.24.0",
|
||||
"spomky-labs/otphp": "^10.0",
|
||||
"bacon/bacon-qr-code": "^2.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8",
|
||||
|
@ -38,7 +40,9 @@
|
|||
"supervisorphp/supervisor": "For managing background jobs",
|
||||
"lstrojny/fxmlrpc": "Required for supervisorphp/supervisor XML-RPC requests",
|
||||
"guzzlehttp/guzzle": "Required for supervisorphp/supervisor XML-RPC requests",
|
||||
"php-http/message": "Required for supervisorphp/supervisor XML-RPC requests"
|
||||
"php-http/message": "Required for supervisorphp/supervisor XML-RPC requests",
|
||||
"spomky-labs/otphp": "Required for strong authentication with TOTP",
|
||||
"bacon/bacon-qr-code": "Required for strong authentication with TOTP"
|
||||
},
|
||||
"config": {
|
||||
"vendor-dir": "Vendor",
|
||||
|
|
Loading…
Reference in New Issue