mirror of https://github.com/MISP/MISP
Compare commits
9 Commits
3d3a207d4d
...
902c99ac82
Author | SHA1 | Date |
---|---|---|
Jakub Onderka | 902c99ac82 | |
Jakub Onderka | bbb5ee96ab | |
Jakub Onderka | 34c85cfe7e | |
Jakub Onderka | 9ea64750bc | |
Jakub Onderka | 97e6224755 | |
Jakub Onderka | b5100dcedd | |
Jakub Onderka | 0ca6a47ef8 | |
Jakub Onderka | d5ba5af530 | |
Jakub Onderka | 79f6124bd2 |
|
@ -1557,7 +1557,7 @@ class AppController extends Controller
|
|||
* Close session without writing changes to them and return current user.
|
||||
* @return array
|
||||
*/
|
||||
protected function _closeSession()
|
||||
protected function _closeSession($saveSession = false)
|
||||
{
|
||||
$user = $this->Auth->user();
|
||||
|
||||
|
@ -1566,7 +1566,11 @@ class AppController extends Controller
|
|||
AuthComponent::$sessionKey = null;
|
||||
$this->Auth->login($user);
|
||||
|
||||
session_abort();
|
||||
if ($saveSession) {
|
||||
@session_write_close();
|
||||
} else {
|
||||
session_abort();
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
|
|
|
@ -792,12 +792,12 @@ class ACLComponent extends Component
|
|||
'discardRegistrations' => array(),
|
||||
'downloadTerms' => array('*'),
|
||||
'edit' => array('self_management_enabled'),
|
||||
'email_otp' => array('*'),
|
||||
'forgot' => array('*'),
|
||||
'otp' => array('*'),
|
||||
'hotp' => array('*'),
|
||||
'totp_new' => array('*'),
|
||||
'totp_delete' => array('perm_admin'),
|
||||
'email_otp' => array('otp_enabled'),
|
||||
'forgot' => ['AND' => ['password_forgotten_enabled', 'password_change_enabled']],
|
||||
'otp' => ['otp_enabled'],
|
||||
'hotp' => ['otp_enabled'],
|
||||
'totp_new' => ['otp_enabled'],
|
||||
'totp_delete' => ['AND' => ['perm_admin', 'otp_enabled']],
|
||||
'searchGpgKey' => array('*'),
|
||||
'fetchGpgKey' => array('*'),
|
||||
'histogram' => array('*'),
|
||||
|
@ -806,7 +806,7 @@ class ACLComponent extends Component
|
|||
'logout' => array('*'),
|
||||
'logout401' => array('*'),
|
||||
'notificationSettings' => ['*'],
|
||||
'password_reset' => array('*'),
|
||||
'password_reset' => ['AND' => ['password_forgotten_enabled', 'password_change_enabled']],
|
||||
'register' => array('*'),
|
||||
'registrations' => array(),
|
||||
'resetAllSyncAuthKeys' => array(),
|
||||
|
@ -913,19 +913,31 @@ class ACLComponent extends Component
|
|||
};
|
||||
$this->dynamicChecks['self_management_enabled'] = function (array $user) {
|
||||
if (Configure::read('MISP.disableUserSelfManagement') && !$user['Role']['perm_admin']) {
|
||||
throw new MethodNotAllowedException('User self-management has been disabled on this instance.');
|
||||
throw new ForbiddenException('User self-management has been disabled on this instance.');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
$this->dynamicChecks['password_change_enabled'] = function (array $user) {
|
||||
if (Configure::read('MISP.disable_user_password_change')) {
|
||||
throw new MethodNotAllowedException('User password change has been disabled on this instance.');
|
||||
throw new ForbiddenException('User password change has been disabled on this instance.');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
$this->dynamicChecks['otp_enabled'] = function (array $user) {
|
||||
if (Configure::read('Security.otp_disabled')) {
|
||||
throw new ForbiddenException('OTP has been disabled on this instance.');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
$this->dynamicChecks['password_forgotten_enabled'] = function (array $user) {
|
||||
if (empty(Configure::read('Security.allow_password_forgotten'))) {
|
||||
throw new ForbiddenException('Password reset has been disabled on this instance.');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
$this->dynamicChecks['add_user_enabled'] = function (array $user) {
|
||||
if (Configure::read('MISP.disable_user_add')) {
|
||||
throw new MethodNotAllowedException('Adding users has been disabled on this instance.');
|
||||
throw new ForbiddenException('Adding users has been disabled on this instance.');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
@ -1125,6 +1137,7 @@ class ACLComponent extends Component
|
|||
*
|
||||
* @param array $user
|
||||
* @param array $analystData
|
||||
* @param string $modelType
|
||||
* @return bool
|
||||
*/
|
||||
public function canEditAnalystData(array $user, array $analystData, $modelType): bool
|
||||
|
@ -1239,7 +1252,7 @@ class ACLComponent extends Component
|
|||
$this->checkAccess($user, $controller, $action, false);
|
||||
} catch (NotFoundException $e) {
|
||||
throw new RuntimeException("Invalid controller '$controller' specified.", 0, $e);
|
||||
} catch (MethodNotAllowedException $e) {
|
||||
} catch (ForbiddenException $e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1259,7 +1272,7 @@ class ACLComponent extends Component
|
|||
* @param bool $checkLoggedActions
|
||||
* @return true
|
||||
* @throws NotFoundException
|
||||
* @throws MethodNotAllowedException
|
||||
* @throws ForbiddenException
|
||||
*/
|
||||
public function checkAccess($user, $controller, $action, $checkLoggedActions = true)
|
||||
{
|
||||
|
@ -1268,9 +1281,6 @@ class ACLComponent extends Component
|
|||
if ($checkLoggedActions) {
|
||||
$this->__checkLoggedActions($user, $controller, $action);
|
||||
}
|
||||
if ($user && $user['Role']['perm_site_admin']) {
|
||||
return true;
|
||||
}
|
||||
if (!isset(self::ACL_LIST[$controller])) {
|
||||
throw new NotFoundException('Invalid controller.');
|
||||
}
|
||||
|
@ -1316,7 +1326,12 @@ class ACLComponent extends Component
|
|||
return true;
|
||||
}
|
||||
}
|
||||
throw new MethodNotAllowedException('You do not have permission to use this functionality.');
|
||||
// Dynamic checks can raise forbidden exception even for site admins, so we have to check permission for site
|
||||
// admin as last thing.
|
||||
if ($user && $user['Role']['perm_site_admin']) {
|
||||
return true;
|
||||
}
|
||||
throw new ForbiddenException('You do not have permission to use this functionality.');
|
||||
}
|
||||
|
||||
private function __findAllFunctions()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller', 'OTPHP\TOTP');
|
||||
App::uses('AppController', 'Controller');
|
||||
|
||||
/**
|
||||
* @property User $User
|
||||
|
@ -1811,6 +1811,7 @@ class UsersController extends AppController
|
|||
$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(
|
||||
|
@ -1880,8 +1881,9 @@ class UsersController extends AppController
|
|||
$this->set('secret', $secret);
|
||||
}
|
||||
|
||||
public function totp_delete($id) {
|
||||
if ($this->request->is('post') || $this->request->is('delete')) {
|
||||
public function totp_delete($id)
|
||||
{
|
||||
if ($this->request->is(['post', 'delete'])) {
|
||||
$user = $this->User->find('first', array(
|
||||
'conditions' => $this->__adminFetchConditions($id),
|
||||
'recursive' => -1,
|
||||
|
@ -1988,8 +1990,7 @@ class UsersController extends AppController
|
|||
// shows some statistics about the instance
|
||||
public function statistics($page = 'data')
|
||||
{
|
||||
$user = $this->Auth->user();
|
||||
@session_write_close(); // loading this page can take long time, so we can close session
|
||||
$user = $this->_closeSession(true); // loading this page can take long time, so we can close session
|
||||
|
||||
if (!$this->_isRest()) {
|
||||
$pages = [
|
||||
|
@ -3187,10 +3188,6 @@ class UsersController extends AppController
|
|||
|
||||
public function forgot()
|
||||
{
|
||||
if (empty(Configure::read('Security.allow_password_forgotten'))) {
|
||||
$this->Flash->error(__('This feature is disabled.'));
|
||||
$this->redirect('/');
|
||||
}
|
||||
if (!empty($this->Auth->user()) && !$this->_isRest()) {
|
||||
$this->Flash->info(__('You are already logged in, no need to ask for a password reset. Log out first.'));
|
||||
$this->redirect('/');
|
||||
|
@ -3216,10 +3213,6 @@ class UsersController extends AppController
|
|||
|
||||
public function password_reset($token)
|
||||
{
|
||||
if (empty(Configure::read('Security.allow_password_forgotten'))) {
|
||||
$this->Flash->error(__('This feature is disabled.'));
|
||||
$this->redirect('/');
|
||||
}
|
||||
$this->loadModel('Server');
|
||||
$this->set('complexity', !empty(Configure::read('Security.password_policy_complexity')) ? Configure::read('Security.password_policy_complexity') : $this->Server->serverSettings['Security']['password_policy_complexity']['value']);
|
||||
$this->set('length', !empty(Configure::read('Security.password_policy_length')) ? Configure::read('Security.password_policy_length') : $this->Server->serverSettings['Security']['password_policy_length']['value']);
|
||||
|
@ -3236,7 +3229,7 @@ class UsersController extends AppController
|
|||
$this->redirect('/');
|
||||
}
|
||||
}
|
||||
if ($this->request->is('post') || $this->request->is('put')) {
|
||||
if ($this->request->is(['post', 'put'])) {
|
||||
$abortPost = false;
|
||||
return $this->__pw_change(['User' => $user], 'password_reset', $abortPost, $token, true);
|
||||
}
|
||||
|
|
|
@ -6636,6 +6636,14 @@ class Server extends AppModel
|
|||
'type' => 'boolean',
|
||||
'null' => true,
|
||||
],
|
||||
'otp_disabled' => [
|
||||
'level' => self::SETTING_OPTIONAL,
|
||||
'description' => __('Disable TOTP on this instance.'),
|
||||
'value' => false,
|
||||
'test' => 'testBool',
|
||||
'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.'),
|
||||
|
|
|
@ -2124,11 +2124,11 @@ class User extends AppModel
|
|||
|
||||
return true;
|
||||
} else {
|
||||
return $this->forgot($email);
|
||||
return $this->forgot($email, $ip);
|
||||
}
|
||||
}
|
||||
|
||||
public function forgot($email, $ip, $jobId = null)
|
||||
public function forgot($email, $ip)
|
||||
{
|
||||
$user = $this->find('first', [
|
||||
'recursive' => -1,
|
||||
|
@ -2140,9 +2140,8 @@ class User extends AppModel
|
|||
if (empty($user)) {
|
||||
return false;
|
||||
}
|
||||
$redis = $this->setupRedis();
|
||||
$token = RandomTool::random_str(true, 40, '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ');
|
||||
$redis->set('misp:forgot:' . $token, $user['User']['id'], ['nx', 'ex' => 600]);
|
||||
RedisTool::init()->set('misp:forgot:' . $token, $user['User']['id'], ['nx', 'ex' => 600]);
|
||||
$baseurl = Configure::check('MISP.external_baseurl') ? Configure::read('MISP.external_baseurl') : Configure::read('MISP.baseurl');
|
||||
$body = __(
|
||||
"Dear MISP user,\n\nyou have requested a password reset on the MISP instance at %s. Click the link below to change your password.\n\n%s\n\nThe link above is only valid for 10 minutes, feel free to request a new one if it has expired.\n\nIf you haven't requested a password reset, reach out to your admin team and let them know that someone has attempted it in your stead.\n\nMake sure you keep the contents of this e-mail confidential, do NOT ever forward it as it contains a reset token that is equivalent of a password if acted upon. The IP used to trigger the request was: %s\n\nBest regards,\nYour MISP admin team",
|
||||
|
|
|
@ -302,7 +302,7 @@ class Oidc
|
|||
$providerUrl = $this->getConfig('provider_url');
|
||||
$clientId = $this->getConfig('client_id');
|
||||
$clientSecret = $this->getConfig('client_secret');
|
||||
$issuer = $this->getConfig('issuer', $providerUrl);
|
||||
$issuer = $this->getConfig('issuer', null, false);
|
||||
|
||||
if (class_exists("\JakubOnderka\OpenIDConnectClient")) {
|
||||
$oidc = new \JakubOnderka\OpenIDConnectClient($providerUrl, $clientId, $clientSecret, $issuer);
|
||||
|
@ -503,13 +503,15 @@ class Oidc
|
|||
/**
|
||||
* @param string $config
|
||||
* @param mixed|null $default
|
||||
* @param bool $required When true and variable is not set, RuntimeException will be thrown
|
||||
* @return mixed
|
||||
* @throws RuntimeException when config option is not set
|
||||
*/
|
||||
private function getConfig($config, $default = null)
|
||||
private function getConfig($config, $default = null, $required = true)
|
||||
{
|
||||
$value = Configure::read("OidcAuth.$config");
|
||||
if ($value === null) {
|
||||
if ($default === null) {
|
||||
if ($default === null && $required) {
|
||||
throw new RuntimeException("Config option `OidcAuth.$config` is not set.");
|
||||
}
|
||||
return $default;
|
||||
|
|
|
@ -152,6 +152,7 @@
|
|||
'class' => 'short',
|
||||
'data_path' => 'User.totp',
|
||||
'colors' => true,
|
||||
'requirement' => empty(Configure::read('Security.otp_disabled')),
|
||||
),
|
||||
array(
|
||||
'name' => '',
|
||||
|
|
|
@ -18,211 +18,215 @@ 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'])): '');
|
||||
$tableData = [
|
||||
array('key' => __('ID'), 'value' => $user['User']['id']),
|
||||
array(
|
||||
'key' => __('Email'),
|
||||
'html' => h($user['User']['email']) . ($admin_view ? sprintf(
|
||||
' <a class="fas fa-envelope" style="color: #333" href="%s/admin/users/quickEmail/%s" title="%s"></a>',
|
||||
$baseurl,
|
||||
h($user['User']['id']),
|
||||
__('Send email to user')
|
||||
) : ''),
|
||||
),
|
||||
array(
|
||||
'key' => __('Organisation'),
|
||||
'html' => $this->OrgImg->getNameWithImg($user),
|
||||
),
|
||||
array(
|
||||
'key' => __('Role'),
|
||||
'html' => $this->Html->link($user['Role']['name'], array('controller' => 'roles', 'action' => 'view', $user['Role']['id'])),
|
||||
),
|
||||
];
|
||||
|
||||
if ($isAdmin && $isTotp) {
|
||||
$totpHtml .= sprintf(
|
||||
'<a href="#" onClick="openGenericModal(\'%s/users/totp_delete/%s\')">%s</a>',
|
||||
h($baseurl),
|
||||
h($user['User']['id']),
|
||||
__($isTotp && !$admin_view ? ' Delete' : 'Delete')
|
||||
if (empty(Configure::read('Security.otp_disabled'))) {
|
||||
$isTotp = isset($user['User']['totp']);
|
||||
$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 ($isAdmin && $isTotp) {
|
||||
$totpHtml .= sprintf(
|
||||
'<a href="#" onClick="openGenericModal(\'%s/users/totp_delete/%s\')">%s</a>',
|
||||
h($baseurl),
|
||||
h($user['User']['id']),
|
||||
__($isTotp && !$admin_view ? ' Delete' : 'Delete')
|
||||
);
|
||||
}
|
||||
|
||||
$tableData[] = [
|
||||
'key' => __('TOTP'),
|
||||
'html' => $totpHtml
|
||||
];
|
||||
}
|
||||
|
||||
$tableData[] = array(
|
||||
'key' => __('Email notifications'),
|
||||
'html' => $notificationsHtml,
|
||||
);
|
||||
$tableData[] = array('key' => __('Contact alert enabled'), 'boolean' => $user['User']['contactalert']);
|
||||
|
||||
if (!$admin_view && !$user['Role']['perm_auth']) {
|
||||
$tableData[] = array(
|
||||
'key' => __('Auth key'),
|
||||
'html' => sprintf('<a onclick="requestAPIAccess();" class="useCursorPointer">%s</a>', __('Request API access')),
|
||||
);
|
||||
}
|
||||
$table_data = [
|
||||
array('key' => __('ID'), 'value' => $user['User']['id']),
|
||||
array(
|
||||
'key' => __('Email'),
|
||||
'html' => h($user['User']['email']) . ($admin_view ? sprintf(
|
||||
' <a class="fas fa-envelope" style="color: #333" href="%s/admin/users/quickEmail/%s" title="%s"></a>',
|
||||
$baseurl,
|
||||
h($user['User']['id']),
|
||||
__('Send email to user')
|
||||
) : ''),
|
||||
),
|
||||
array(
|
||||
'key' => __('Organisation'),
|
||||
'html' => $this->OrgImg->getNameWithImg($user),
|
||||
),
|
||||
array(
|
||||
'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,
|
||||
),
|
||||
array('key' => __('Contact alert enabled'), 'boolean' => $user['User']['contactalert'])
|
||||
];
|
||||
|
||||
if (!$admin_view && !$user['Role']['perm_auth']) {
|
||||
$table_data[] = array(
|
||||
'key' => __('Auth key'),
|
||||
'html' => sprintf('<a onclick="requestAPIAccess();" class="useCursorPointer">%s</a>', __('Request API access')),
|
||||
);
|
||||
}
|
||||
if (empty(Configure::read('Security.advanced_authkeys')) && $user['Role']['perm_auth']) {
|
||||
$authkey_data = sprintf(
|
||||
'<span class="privacy-value quickSelect authkey" data-hidden-value="%s">****************************************</span> <i class="privacy-toggle fas fa-eye useCursorPointer" title="%s"></i>%s',
|
||||
h($user['User']['authkey']),
|
||||
__('Reveal hidden value'),
|
||||
sprintf(
|
||||
' (%s)',
|
||||
$this->Form->postLink(__('reset'), array('action' => 'resetauthkey', $user['User']['id']))
|
||||
)
|
||||
);
|
||||
$tableData[] = array(
|
||||
'key' => __('Auth key'),
|
||||
'html' => $authkey_data
|
||||
);
|
||||
}
|
||||
|
||||
if (empty(Configure::read('Security.advanced_authkeys')) && $user['Role']['perm_auth']) {
|
||||
$authkey_data = sprintf(
|
||||
'<span class="privacy-value quickSelect authkey" data-hidden-value="%s">****************************************</span> <i class="privacy-toggle fas fa-eye useCursorPointer" title="%s"></i>%s',
|
||||
h($user['User']['authkey']),
|
||||
__('Reveal hidden value'),
|
||||
sprintf(
|
||||
' (%s)',
|
||||
$this->Form->postLink(__('reset'), array('action' => 'resetauthkey', $user['User']['id']))
|
||||
)
|
||||
);
|
||||
$table_data[] = array(
|
||||
'key' => __('Auth key'),
|
||||
'html' => $authkey_data
|
||||
);
|
||||
}
|
||||
|
||||
if (Configure::read('Plugin.CustomAuth_enable') && !empty($user['User']['external_auth_key'])) {
|
||||
$header = Configure::read('Plugin.CustomAuth_header') ?: 'AUTHORIZATION';
|
||||
$table_data[] = array(
|
||||
'key' => __('Customauth header'),
|
||||
'html' => sprintf(
|
||||
'%s: <span class="green bold">%s</span>',
|
||||
h($header),
|
||||
h($user['User']['external_auth_key'])
|
||||
)
|
||||
);
|
||||
}
|
||||
$table_data[] = array(
|
||||
'key' => __('Invited By'),
|
||||
'html' => empty($invitedBy['User']['email']) ? 'N/A' : sprintf('<a href="%s/admin/users/view/%s">%s</a>', $baseurl, h($invitedBy['User']['id']), h($invitedBy['User']['email'])),
|
||||
if (Configure::read('Plugin.CustomAuth_enable') && !empty($user['User']['external_auth_key'])) {
|
||||
$header = Configure::read('Plugin.CustomAuth_header') ?: 'AUTHORIZATION';
|
||||
$tableData[] = array(
|
||||
'key' => __('Customauth header'),
|
||||
'html' => sprintf(
|
||||
'%s: <span class="green bold">%s</span>',
|
||||
h($header),
|
||||
h($user['User']['external_auth_key'])
|
||||
)
|
||||
);
|
||||
$org_admin_data = array();
|
||||
if ($admin_view) {
|
||||
foreach ($user['User']['orgAdmins'] as $orgAdminId => $orgAdminEmail) {
|
||||
$org_admin_data[] = sprintf(
|
||||
'<a href="%s/admin/users/view/%s">%s</a> <a class="fas fa-envelope" style="color: #333" href="%s/admin/users/quickEmail/%s" title="%s"></a>',
|
||||
$baseurl,
|
||||
h($orgAdminId),
|
||||
h($orgAdminEmail),
|
||||
$baseurl,
|
||||
h($orgAdminId),
|
||||
__('Send email to user')
|
||||
);
|
||||
}
|
||||
$table_data[] = array('key' => __('Org admin'), 'html' => implode('<br>', $org_admin_data));
|
||||
}
|
||||
$table_data[] = array('key' => __('NIDS Start SID'), 'value' => $user['User']['nids_sid']);
|
||||
if ($admin_view) {
|
||||
$table_data[] = array('key' => __('Terms accepted'), 'boolean' => $user['User']['termsaccepted']);
|
||||
$table_data[] = array('key' => __('Must change password'), 'boolean' => $user['User']['change_pw']);
|
||||
}
|
||||
if (!empty($user['User']['gpgkey'])) {
|
||||
$table_data[] = array(
|
||||
'key' => __('PGP key'),
|
||||
'element' => 'genericElements/key',
|
||||
'element_params' => array('key' => $user['User']['gpgkey']),
|
||||
);
|
||||
$table_data[] = array(
|
||||
'key' => __('PGP key fingerprint'),
|
||||
'value_class' => 'quickSelect',
|
||||
'value' => $user['User']['fingerprint'] ? chunk_split($user['User']['fingerprint'], 4, ' ') : 'N/A'
|
||||
);
|
||||
$table_data[] = array(
|
||||
'key' => __('PGP key status'),
|
||||
'value_class' => (empty($user['User']['pgp_status']) || $user['User']['pgp_status'] !== 'OK') ? 'red': '',
|
||||
'value' => !empty($user['User']['pgp_status']) ? $user['User']['pgp_status'] : 'N/A'
|
||||
);
|
||||
} else {
|
||||
$table_data[] = array(
|
||||
'key' => __('PGP key'),
|
||||
'boolean' => false,
|
||||
}
|
||||
$tableData[] = array(
|
||||
'key' => __('Invited By'),
|
||||
'html' => empty($invitedBy['User']['email']) ? 'N/A' : sprintf('<a href="%s/admin/users/view/%s">%s</a>', $baseurl, h($invitedBy['User']['id']), h($invitedBy['User']['email'])),
|
||||
);
|
||||
$org_admin_data = array();
|
||||
if ($admin_view) {
|
||||
foreach ($user['User']['orgAdmins'] as $orgAdminId => $orgAdminEmail) {
|
||||
$org_admin_data[] = sprintf(
|
||||
'<a href="%s/admin/users/view/%s">%s</a> <a class="fas fa-envelope" style="color: #333" href="%s/admin/users/quickEmail/%s" title="%s"></a>',
|
||||
$baseurl,
|
||||
h($orgAdminId),
|
||||
h($orgAdminEmail),
|
||||
$baseurl,
|
||||
h($orgAdminId),
|
||||
__('Send email to user')
|
||||
);
|
||||
}
|
||||
if (Configure::read('SMIME.enabled')) {
|
||||
$table_data[] = array(
|
||||
'key' => __('S/MIME Public certificate'),
|
||||
'element' => 'genericElements/key',
|
||||
'element_params' => array('key' => $user['User']['certif_public']),
|
||||
);
|
||||
}
|
||||
$table_data[] = array(
|
||||
'key' => __('Created'),
|
||||
'html' => $user['User']['date_created'] ? $this->Time->time($user['User']['date_created']) : __('N/A')
|
||||
$tableData[] = array('key' => __('Org admin'), 'html' => implode('<br>', $org_admin_data));
|
||||
}
|
||||
$tableData[] = array('key' => __('NIDS Start SID'), 'value' => $user['User']['nids_sid']);
|
||||
if ($admin_view) {
|
||||
$tableData[] = array('key' => __('Terms accepted'), 'boolean' => $user['User']['termsaccepted']);
|
||||
$tableData[] = array('key' => __('Must change password'), 'boolean' => $user['User']['change_pw']);
|
||||
}
|
||||
if (!empty($user['User']['gpgkey'])) {
|
||||
$tableData[] = array(
|
||||
'key' => __('PGP key'),
|
||||
'element' => 'genericElements/key',
|
||||
'element_params' => array('key' => $user['User']['gpgkey']),
|
||||
);
|
||||
$table_data[] = array(
|
||||
'key' => __('Last password change'),
|
||||
'html' => $user['User']['last_pw_change'] ? $this->Time->time($user['User']['last_pw_change']) : __('N/A')
|
||||
$tableData[] = array(
|
||||
'key' => __('PGP key fingerprint'),
|
||||
'value_class' => 'quickSelect',
|
||||
'value' => $user['User']['fingerprint'] ? chunk_split($user['User']['fingerprint'], 4, ' ') : 'N/A'
|
||||
);
|
||||
if ($admin_view) {
|
||||
$table_data[] = array(
|
||||
'key' => __('News read at'),
|
||||
'html' => $user['User']['newsread'] ? $this->Time->time($user['User']['newsread']) : __('N/A')
|
||||
);
|
||||
$table_data[] = array(
|
||||
'key' => __('Disabled'),
|
||||
'class' => empty($user['User']['disabled']) ? '' : 'background-red',
|
||||
'boolean' => $user['User']['disabled']
|
||||
);
|
||||
}
|
||||
echo $this->element('genericElements/assetLoader', array(
|
||||
'css' => array('vis', 'distribution-graph'),
|
||||
'js' => array('vis', 'jquery-ui.min', 'network-distribution-graph')
|
||||
));
|
||||
echo sprintf(
|
||||
'<div class="users view"><div class="row-fluid"><div class="span8" style="margin:0px;">%s</div></div>%s%s%s<div style="margin-top:20px;">%s%s%s</div></div>',
|
||||
sprintf(
|
||||
'<h2>%s</h2>%s',
|
||||
__('User %s', h($user['User']['email'])),
|
||||
$this->element('genericElements/viewMetaTable', array('table_data' => $table_data))
|
||||
),
|
||||
sprintf(
|
||||
'<br><a href="%s" class="btn btn-inverse" download>%s</a>',
|
||||
sprintf(
|
||||
'%s/users/view/%s.json',
|
||||
$baseurl,
|
||||
h($user['User']['id'])
|
||||
),
|
||||
__('Download user profile for data portability')
|
||||
),
|
||||
sprintf(
|
||||
' <a href="%s" class="btn btn-inverse">%s</a>',
|
||||
sprintf(
|
||||
'%s/logs/index',
|
||||
$baseurl
|
||||
),
|
||||
__('Review user logs')
|
||||
),
|
||||
sprintf(
|
||||
' <a href="%s" class="btn btn-inverse">%s</a>',
|
||||
sprintf(
|
||||
'%s/users/view_login_history/%s',
|
||||
$baseurl,
|
||||
h($user['User']['id'])
|
||||
),
|
||||
__('Review user logins')
|
||||
),
|
||||
$me['Role']['perm_auth'] ? $this->element('/genericElements/accordion', array('title' => __('Auth keys'), 'url' => '/auth_keys/index/' . h($user['User']['id']))) : '',
|
||||
$me['Role']['perm_site_admin'] ?
|
||||
$this->element(
|
||||
'/genericElements/accordion',
|
||||
[
|
||||
'title' => __('Benchmarks'),
|
||||
'url' => '/benchmarks/index/scope:user/average:1/aggregate:1/key:' . h($user['User']['id'])
|
||||
]
|
||||
) :
|
||||
'',
|
||||
$this->element('/genericElements/accordion', array('title' => 'Events', 'url' => '/events/index/searchemail:' . urlencode(h($user['User']['email']))))
|
||||
$tableData[] = array(
|
||||
'key' => __('PGP key status'),
|
||||
'value_class' => (empty($user['User']['pgp_status']) || $user['User']['pgp_status'] !== 'OK') ? 'red': '',
|
||||
'value' => !empty($user['User']['pgp_status']) ? $user['User']['pgp_status'] : 'N/A'
|
||||
);
|
||||
$current_menu = [
|
||||
'admin_view' => ['menuList' => 'admin', 'menuItem' => 'viewUser'],
|
||||
'view' => ['menuList' => 'globalActions', 'menuItem' => 'view']
|
||||
];
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', $current_menu[$admin_view ? 'admin_view' : 'view']);
|
||||
} else {
|
||||
$tableData[] = array(
|
||||
'key' => __('PGP key'),
|
||||
'boolean' => false,
|
||||
);
|
||||
}
|
||||
if (Configure::read('SMIME.enabled')) {
|
||||
$tableData[] = array(
|
||||
'key' => __('S/MIME Public certificate'),
|
||||
'element' => 'genericElements/key',
|
||||
'element_params' => array('key' => $user['User']['certif_public']),
|
||||
);
|
||||
}
|
||||
$tableData[] = array(
|
||||
'key' => __('Created'),
|
||||
'html' => $user['User']['date_created'] ? $this->Time->time($user['User']['date_created']) : __('N/A')
|
||||
);
|
||||
$tableData[] = array(
|
||||
'key' => __('Last password change'),
|
||||
'html' => $user['User']['last_pw_change'] ? $this->Time->time($user['User']['last_pw_change']) : __('N/A')
|
||||
);
|
||||
if ($admin_view) {
|
||||
$tableData[] = array(
|
||||
'key' => __('News read at'),
|
||||
'html' => $user['User']['newsread'] ? $this->Time->time($user['User']['newsread']) : __('N/A')
|
||||
);
|
||||
$tableData[] = array(
|
||||
'key' => __('Disabled'),
|
||||
'class' => empty($user['User']['disabled']) ? '' : 'background-red',
|
||||
'boolean' => $user['User']['disabled']
|
||||
);
|
||||
}
|
||||
echo $this->element('genericElements/assetLoader', array(
|
||||
'css' => array('vis', 'distribution-graph'),
|
||||
'js' => array('vis', 'jquery-ui.min', 'network-distribution-graph')
|
||||
));
|
||||
echo sprintf(
|
||||
'<div class="users view"><div class="row-fluid"><div class="span8" style="margin:0px;">%s</div></div>%s%s%s<div style="margin-top:20px;">%s%s%s</div></div>',
|
||||
sprintf(
|
||||
'<h2>%s</h2>%s',
|
||||
__('User %s', h($user['User']['email'])),
|
||||
$this->element('genericElements/viewMetaTable', array('table_data' => $tableData))
|
||||
),
|
||||
sprintf(
|
||||
'<br><a href="%s" class="btn btn-inverse" download>%s</a>',
|
||||
sprintf(
|
||||
'%s/users/view/%s.json',
|
||||
$baseurl,
|
||||
h($user['User']['id'])
|
||||
),
|
||||
__('Download user profile for data portability')
|
||||
),
|
||||
sprintf(
|
||||
' <a href="%s" class="btn btn-inverse">%s</a>',
|
||||
sprintf(
|
||||
'%s/logs/index',
|
||||
$baseurl
|
||||
),
|
||||
__('Review user logs')
|
||||
),
|
||||
sprintf(
|
||||
' <a href="%s" class="btn btn-inverse">%s</a>',
|
||||
sprintf(
|
||||
'%s/users/view_login_history/%s',
|
||||
$baseurl,
|
||||
h($user['User']['id'])
|
||||
),
|
||||
__('Review user logins')
|
||||
),
|
||||
$me['Role']['perm_auth'] ? $this->element('/genericElements/accordion', array('title' => __('Auth keys'), 'url' => '/auth_keys/index/' . h($user['User']['id']))) : '',
|
||||
$me['Role']['perm_site_admin'] ?
|
||||
$this->element(
|
||||
'/genericElements/accordion',
|
||||
[
|
||||
'title' => __('Benchmarks'),
|
||||
'url' => '/benchmarks/index/scope:user/average:1/aggregate:1/key:' . h($user['User']['id'])
|
||||
]
|
||||
) :
|
||||
'',
|
||||
$this->element('/genericElements/accordion', array('title' => 'Events', 'url' => '/events/index/searchemail:' . urlencode(h($user['User']['email']))))
|
||||
);
|
||||
$current_menu = [
|
||||
'admin_view' => ['menuList' => 'admin', 'menuItem' => 'viewUser'],
|
||||
'view' => ['menuList' => 'globalActions', 'menuItem' => 'view']
|
||||
];
|
||||
echo $this->element('/genericElements/SideMenu/side_menu', $current_menu[$admin_view ? 'admin_view' : 'view']);
|
||||
|
|
|
@ -19,6 +19,7 @@ from enum import Enum
|
|||
try:
|
||||
from pymisp import PyMISP, MISPOrganisation, MISPUser, MISPRole, MISPSharingGroup, MISPEvent, MISPLog, MISPSighting, Distribution
|
||||
from pymisp.exceptions import PyMISPError, NoKey, MISPServerError
|
||||
from pymisp.api import get_uuid_or_id_from_abstract_misp
|
||||
except ImportError:
|
||||
if sys.version_info < (3, 6):
|
||||
print('This test suite requires Python 3.6+, breaking.')
|
||||
|
@ -126,6 +127,12 @@ def random() -> str:
|
|||
return str(uuid.uuid4()).split("-")[0]
|
||||
|
||||
|
||||
def publish_immediately(pymisp: PyMISP, event: Union[MISPEvent, int, str, uuid.UUID], with_email: bool = False):
|
||||
event_id = get_uuid_or_id_from_abstract_misp(event)
|
||||
action = "alert" if with_email else "publish"
|
||||
return send(pymisp, 'POST', f'events/{action}/{event_id}/disable_background_processing:1')
|
||||
|
||||
|
||||
class TestSecurity(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
|
@ -782,6 +789,30 @@ class TestSecurity(unittest.TestCase):
|
|||
# Password should be still the same
|
||||
self.assertIsInstance(login(url, self.test_usr.email, self.test_usr_password), requests.Session)
|
||||
|
||||
def test_forget_password_not_enabled(self):
|
||||
logged_in = PyMISP(url, self.test_usr.authkey)
|
||||
logged_in.global_pythonify = True
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
send(logged_in, "GET", f"/users/forget")
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
send(logged_in, "GET", f"/users/password_reset/abcd")
|
||||
|
||||
def test_otp_disabled(self):
|
||||
with self.__setting("Security.otp_disabled", True):
|
||||
logged_in = PyMISP(url, self.test_usr.authkey)
|
||||
logged_in.global_pythonify = True
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
send(logged_in, "GET", f"/users/email_otp")
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
send(logged_in, "GET", f"/users/totp_new")
|
||||
|
||||
with self.assertRaises(Exception):
|
||||
send(logged_in, "GET", f"/users/totp_delete/1")
|
||||
|
||||
def test_add_user_by_org_admin(self):
|
||||
user = MISPUser()
|
||||
user.email = 'testusr@user' + random() + '.local' # make name always unique
|
||||
|
@ -1253,8 +1284,7 @@ class TestSecurity(unittest.TestCase):
|
|||
self.assertEqual(len(attributes["Attribute"]), 0, attributes)
|
||||
|
||||
# Publish
|
||||
self.assertSuccessfulResponse(self.admin_misp_connector.publish(created_event))
|
||||
time.sleep(6);
|
||||
self.assertSuccessfulResponse(publish_immediately(self.admin_misp_connector, created_event))
|
||||
|
||||
# Event is published, so normal user should see that event
|
||||
self.assertTrue(logged_in.event_exists(created_event.uuid))
|
||||
|
|
Loading…
Reference in New Issue