chg: [security] allow creation of TOTP token

pull/9085/head
Christophe Vandeplas 2023-05-19 20:56:52 +02:00
parent 6caccac94d
commit 61573392ea
5 changed files with 91 additions and 3 deletions

View File

@ -746,6 +746,7 @@ class ACLComponent extends Component
'edit' => array('self_management_enabled'),
'email_otp' => array('*'),
'totp' => array('*'),
'totp_new' => array('*'),
'searchGpgKey' => array('*'),
'fetchGpgKey' => array('*'),
'histogram' => array('*'),

View File

@ -1207,6 +1207,10 @@ class UsersController extends AppController
return $this->redirect('totp');
}
}
// FIXME chri - implement a way for the user to login the first time when TOTP is required for all users,
// - redirect to TOTP generation page just after login (or any page requested)
// - for new account creations
// - for when the org admin wants to allow the user to generate a new TOTP
}
}
// if instance requires email OTP
@ -1782,7 +1786,51 @@ class UsersController extends AppController
} else {
// GET Request, just show the form
}
}
public function totp_new($id = null)
{
// FIXME chri - reuse $id to load user if (org)site admin
$user = $this->Auth->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['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('totp_secret'); // Reload secret from session.
if ($secret) {
$otp = \OTPHP\TOTP::create($secret);
} else {
$otp = \OTPHP\TOTP::create();
$secret = $otp->getSecret();
$this->Session->write('totp_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'])) {
$otp_now = $otp->now();
if (trim($this->request->data['User']['otp']) == $otp_now) {
// we know the user can generate TOTP tokens, save the new TOTP to the database
$this->User->id = $user['id'];
$this->User->saveField('totp', $secret);
$this->Flash->info(__('The OTP is correct and now active for your account.'));
$this->redirect(array('controller' => 'events', 'action'=> 'index'));
} 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);
$qrcode = $writer->writeString('otpauth://totp/' . Configure::read('MISP.org') . ' MISP (' . $user['email'] . ')?secret=' . $secret);
$writer = preg_replace('/^.+\n/', '', $qrcode); // ignore first <?xml version line
$this->set('qrcode', $qrcode);
$this->set('secret', $secret);
}
public function email_otp()

View File

@ -0,0 +1,35 @@
<?php echo $this->Flash->render(); ?>
<div class="actions sideMenu">
<div style="padding: 10px;">
<p><?php echo __("Generate a new TOTP token to login. (Time-Based One-Time Password)");?></p>
<p><?php echo __("Please scan the following QR code with your TOTP application.");?></p>
</div>
</div>
<div>
<?php
// FIXME chri - make it visually attractive
echo $qrcode;
?>
<p>Alternatively you can enter the following secret in your TOTP application: <pre><?php echo $secret; ?></pre>
<?php
echo $this->element('/genericElements/Form/genericForm', array(
"form" => $this->Form,
"data" => array(
"title" => __("Validate your One Time Password"),
"fields" => array(
array(
"field" => "otp",
"label" => __("One Time Password"),
"type" => "text",
"placeholder" => __("Enter your OTP code here"),
)
),
"submit" => array (
"action" => "totp",
),
)));
?>
</div>

View File

@ -23,7 +23,9 @@ $boolean = sprintf(
'<span class="%s">%s</span>',
$isTotp ? 'label label-success label-padding' : 'label label-important label-padding',
$isTotp ? __('Yes') : __('No'));
$totpHtml = $boolean . ''. __('Generate TOTP'); // FIXME chri - create link to generate a new TOTP, only save in DB after validation
$totpHtml = $boolean;
$totpHtml .= ($isTotp ? '' : $this->Html->link(__('Generate'), array('action' => 'totp_new', $user['User']['id'])));
$totpHtml .= ($admin_view && $isTotp ? ' ' . __('Delete') : ''); // FIXME chri - only allow delete for (org)admin
$table_data = [

View File

@ -12,7 +12,8 @@
"kamisama/cake-resque": "4.1.2",
"pear/crypt_gpg": "1.6.7",
"monolog/monolog": "1.24.0",
"spomky-labs/otphp": "^10.0"
"spomky-labs/otphp": "^10.0",
"bacon/bacon-qr-code": "^2.0"
},
"require-dev": {
"phpunit/phpunit": "^8",
@ -40,7 +41,8 @@
"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",
"spomky-labs/otphp": "Required for strong authentication with TOTP"
"spomky-labs/otphp": "Required for strong authentication with TOTP",
"bacon/bacon-qr-code": "Required for strong authentication with TOTP"
},
"config": {
"vendor-dir": "Vendor",