diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index 2ea20b247..94582d5a4 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -747,6 +747,7 @@ class ACLComponent extends Component 'email_otp' => array('*'), 'totp' => array('*'), 'totp_new' => array('*'), + 'totp_delete' => array('perm_admin'), 'searchGpgKey' => array('*'), 'fetchGpgKey' => array('*'), 'histogram' => array('*'), diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index 882130421..e77851482 100644 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -67,6 +67,7 @@ class UsersController extends AppController $user['User']['pgp_status'] = isset($pgpDetails[2]) ? $pgpDetails[2] : 'OK'; $user['User']['fingerprint'] = !empty($pgpDetails[4]) ? $pgpDetails[4] : 'N/A'; } + // FIXME chri - show warning for TOTP if LinOTP is enabled if ($this->_isRest()) { return $this->RestResponse->viewData($this->__massageUserObject($user), $this->response->type()); } else { @@ -88,6 +89,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) { @@ -588,17 +590,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); @@ -1788,12 +1780,22 @@ class UsersController extends AppController } } - public function totp_new($id = null) + public function totp_new() { - // FIXME chri - reuse $id to load user if (org)site admin - $user = $this->Auth->user(); + // 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['totp']) { + 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()); } @@ -1810,7 +1812,7 @@ class UsersController extends AppController $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->id = $user['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')); @@ -1826,13 +1828,46 @@ class UsersController extends AppController new \BaconQrCode\Renderer\Image\SvgImageBackEnd() ); $writer = new \BaconQrCode\Writer($renderer); - $qrcode = $writer->writeString('otpauth://totp/' . Configure::read('MISP.org') . ' MISP (' . $user['email'] . ')?secret=' . $secret); + $qrcode = $writer->writeString('otpauth://totp/' . Configure::read('MISP.org') . ' MISP (' . $user['User']['email'] . ')?secret=' . $secret); $writer = preg_replace('/^.+\n/', '', $qrcode); // ignore first 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'); diff --git a/app/View/Users/admin_index.ctp b/app/View/Users/admin_index.ctp index 8205f269e..fa51b49f8 100755 --- a/app/View/Users/admin_index.ctp +++ b/app/View/Users/admin_index.ctp @@ -138,6 +138,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'), diff --git a/app/View/Users/view.ctp b/app/View/Users/view.ctp index 4ee4dbfa8..c4d32e6d0 100755 --- a/app/View/Users/view.ctp +++ b/app/View/Users/view.ctp @@ -25,7 +25,8 @@ $boolean = sprintf( $isTotp ? __('Yes') : __('No')); $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 + +$totpHtml .= ($admin_view && $isTotp ? ' ' . $this->Form->postLink(__('Delete'), array('action' => 'totp_delete', $user['User']['id'])) : ''); // FIXME chri - only allow delete for (org)admin $table_data = [