mirror of https://github.com/MISP/MISP
Merge pull request #8144 from JakubOnderka/oidc-check-validity
new: [oidc] Check user validitypull/8159/head
commit
83d2dcb64f
|
@ -46,6 +46,15 @@ class UserShell extends AppShell
|
|||
],
|
||||
],
|
||||
]);
|
||||
$parser->addSubcommand('check_validity', [
|
||||
'help' => __('Check users validity from external identity provider and block not valid user.'),
|
||||
'parser' => [
|
||||
'options' => [
|
||||
'block_invalid' => ['help' => __('Block user that are considered invalid.'), 'boolean' => true],
|
||||
'update' => ['help' => __('Update user role or organisation.'), 'boolean' => true],
|
||||
],
|
||||
]
|
||||
]);
|
||||
$parser->addSubcommand('change_pw', [
|
||||
'help' => __('Change user password.'),
|
||||
'parser' => [
|
||||
|
@ -237,6 +246,44 @@ class UserShell extends AppShell
|
|||
$this->out("User $userId unblocked.");
|
||||
}
|
||||
|
||||
public function check_validity()
|
||||
{
|
||||
$auth = Configure::read('Security.auth');
|
||||
if (!$auth) {
|
||||
$this->error('External authentication is not enabled');
|
||||
}
|
||||
if (!is_array($auth)) {
|
||||
throw new Exception("`Security.auth` config value must be array.");
|
||||
}
|
||||
if (!in_array('OidcAuth.Oidc', $auth, true)) {
|
||||
$this->error('This method is currently supported just by OIDC auth provider');
|
||||
}
|
||||
|
||||
App::uses('Oidc', 'OidcAuth.Lib');
|
||||
$oidc = new Oidc($this->User);
|
||||
|
||||
$users = $this->User->find('all', [
|
||||
'recursive' => -1,
|
||||
'contain' => ['UserSetting'],
|
||||
'conditions' => ['disabled' => false], // fetch just not disabled users
|
||||
]);
|
||||
$blockInvalid = $this->params['block_invalid'];
|
||||
$update = $this->params['update'];
|
||||
|
||||
foreach ($users as $user) {
|
||||
$user['User']['UserSetting'] = $user['UserSetting'];
|
||||
$user = $user['User'];
|
||||
|
||||
if ($blockInvalid) {
|
||||
$result = $oidc->blockInvalidUser($user, true, $update);
|
||||
} else {
|
||||
$result = $oidc->isUserValid($user, true, $update);
|
||||
}
|
||||
|
||||
$this->out("{$user['email']}: " . ($result ? '<success>valid</success>' : '<error>invalid</error>'));
|
||||
}
|
||||
}
|
||||
|
||||
public function change_pw()
|
||||
{
|
||||
list($userId, $newPassword) = $this->args;
|
||||
|
|
|
@ -541,7 +541,7 @@ class AppController extends Controller
|
|||
return false;
|
||||
}
|
||||
|
||||
if ($user['disabled']) {
|
||||
if ($user['disabled'] || (isset($user['logged_by_authkey']) && $user['logged_by_authkey']) && !$this->User->checkIfUserIsValid($user)) {
|
||||
if ($this->_shouldLog('disabled:' . $user['id'])) {
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], 'Login attempt by disabled user.');
|
||||
|
|
|
@ -991,6 +991,10 @@ class EventsController extends AppController
|
|||
|
||||
private function __attachInfoToEvents(array $columns, array $events)
|
||||
{
|
||||
if (empty($events)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$user = $this->Auth->user();
|
||||
|
||||
if (in_array('tags', $columns, true) || in_array('clusters', $columns, true)) {
|
||||
|
|
|
@ -97,6 +97,11 @@ class UserSettingsController extends AppController
|
|||
);
|
||||
}
|
||||
}
|
||||
// Do not show internal settings
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
$conditions['AND'][] = ['NOT' => ['UserSetting.setting' => $this->UserSetting->getInternalSettingNames()]];
|
||||
}
|
||||
|
||||
if ($this->_isRest()) {
|
||||
$params = array(
|
||||
'conditions' => $conditions
|
||||
|
@ -127,9 +132,12 @@ class UserSettingsController extends AppController
|
|||
|
||||
public function view($id)
|
||||
{
|
||||
if (!$this->_isRest()) {
|
||||
throw new BadRequestException("This endpoint is accessible just by REST requests.");
|
||||
}
|
||||
// check if the ID is valid and whether a user setting with the given ID exists
|
||||
if (empty($id) || !is_numeric($id)) {
|
||||
throw new InvalidArgumentException(__('Invalid ID passed.'));
|
||||
throw new BadRequestException(__('Invalid ID passed.'));
|
||||
}
|
||||
$userSetting = $this->UserSetting->find('first', array(
|
||||
'recursive' => -1,
|
||||
|
@ -145,18 +153,14 @@ class UserSettingsController extends AppController
|
|||
if (!$checkAccess) {
|
||||
throw new NotFoundException(__('Invalid user setting.'));
|
||||
}
|
||||
if ($this->_isRest()) {
|
||||
unset($userSetting['User']);
|
||||
return $this->RestResponse->viewData($userSetting, $this->response->type());
|
||||
} else {
|
||||
$this->set($data, $userSetting);
|
||||
}
|
||||
unset($userSetting['User']);
|
||||
return $this->RestResponse->viewData($userSetting, $this->response->type());
|
||||
}
|
||||
|
||||
public function setSetting($user_id = false, $setting = false)
|
||||
{
|
||||
if (!empty($setting)) {
|
||||
if (!$this->UserSetting->checkSettingValidity($setting)) {
|
||||
if (!$this->UserSetting->checkSettingValidity($setting) || $this->UserSetting->isInternal($setting)) {
|
||||
throw new MethodNotAllowedException(__('Invalid setting.'));
|
||||
}
|
||||
$settingPermCheck = $this->UserSetting->checkSettingAccess($this->Auth->user(), $setting);
|
||||
|
@ -177,10 +181,6 @@ class UserSettingsController extends AppController
|
|||
if (!empty($setting)) {
|
||||
$this->request->data['UserSetting']['setting'] = $setting;
|
||||
}
|
||||
// force our user's ID as the user ID in all cases
|
||||
$userSetting = array(
|
||||
'user_id' => $this->Auth->user('id')
|
||||
);
|
||||
$result = $this->UserSetting->setSetting($this->Auth->user(), $this->request->data);
|
||||
if ($result) {
|
||||
// if we've managed to save our setting
|
||||
|
@ -217,12 +217,10 @@ class UserSettingsController extends AppController
|
|||
// load the valid settings from the model
|
||||
if ($this->_isSiteAdmin()) {
|
||||
$users = $this->UserSetting->User->find('list', array(
|
||||
'recursive' => -1,
|
||||
'fields' => array('User.id', 'User.email')
|
||||
));
|
||||
} else if ($this->_isAdmin()) {
|
||||
$users = $this->UserSetting->User->find('list', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('User.org_id' => $this->Auth->user('org_id')),
|
||||
'fields' => array('User.id', 'User.email')
|
||||
));
|
||||
|
@ -234,7 +232,7 @@ class UserSettingsController extends AppController
|
|||
}
|
||||
$this->set('setting', $setting);
|
||||
$this->set('users', $users);
|
||||
$this->set('validSettings', UserSetting::VALID_SETTINGS);
|
||||
$this->set('validSettings', $this->UserSetting->settingPlaceholders($this->Auth->user()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,7 +250,7 @@ class UserSettingsController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
if (!$this->UserSetting->checkSettingValidity($setting)) {
|
||||
if (!$this->UserSetting->checkSettingValidity($setting) || $this->UserSetting->isInternal($setting)) {
|
||||
throw new NotFoundException(__('Invalid setting.'));
|
||||
}
|
||||
|
||||
|
@ -365,7 +363,7 @@ class UserSettingsController extends AppController
|
|||
'UserSetting' => array(
|
||||
'user_id' => $this->Auth->user('id'),
|
||||
'setting' => 'homepage',
|
||||
'value' => json_encode(array('path' => $this->request->data['path']))
|
||||
'value' => ['path' => $this->request->data['path']],
|
||||
)
|
||||
);
|
||||
$result = $this->UserSetting->setSetting($this->Auth->user(), $setting);
|
||||
|
@ -393,13 +391,13 @@ class UserSettingsController extends AppController
|
|||
$hideColumns[] = $columnName;
|
||||
}
|
||||
|
||||
$setting = array(
|
||||
'UserSetting' => array(
|
||||
$setting = [
|
||||
'UserSetting' => [
|
||||
'user_id' => $this->Auth->user()['id'],
|
||||
'setting' => 'event_index_hide_columns',
|
||||
'value' => json_encode($hideColumns)
|
||||
)
|
||||
);
|
||||
'value' => $hideColumns,
|
||||
]
|
||||
];
|
||||
$this->UserSetting->setSetting($this->Auth->user(), $setting);
|
||||
return $this->RestResponse->saveSuccessResponse('UserSettings', 'eventIndexColumnToggle', false, 'json', 'Column visibility switched');
|
||||
}
|
||||
|
|
|
@ -733,6 +733,9 @@ class User extends AppModel
|
|||
$user['User']['Role'] = $user['Role'];
|
||||
$user['User']['Organisation'] = $user['Organisation'];
|
||||
$user['User']['Server'] = $user['Server'];
|
||||
if (isset($user['UserSetting'])) {
|
||||
$user['User']['UserSetting'] = $user['UserSetting'];
|
||||
}
|
||||
return $user['User'];
|
||||
}
|
||||
|
||||
|
@ -820,7 +823,7 @@ class User extends AppModel
|
|||
*/
|
||||
public function sendEmail(array $user, $body, $bodyNoEnc = false, $subject, $replyToUser = false)
|
||||
{
|
||||
if ($user['User']['disabled']) {
|
||||
if ($user['User']['disabled'] || !$this->checkIfUserIsValid($user['User'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1406,6 +1409,29 @@ class User extends AppModel
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user still valid at identity provider.
|
||||
* @param array $user
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function checkIfUserIsValid(array $user)
|
||||
{
|
||||
$auth = Configure::read('Security.auth');
|
||||
if (!$auth) {
|
||||
return true;
|
||||
}
|
||||
if (!is_array($auth)) {
|
||||
throw new Exception("`Security.auth` config value must be array.");
|
||||
}
|
||||
if (!in_array('OidcAuth.Oidc', $auth, true)) {
|
||||
return true; // this method currently makes sense just for OIDC auth provider
|
||||
}
|
||||
App::uses('Oidc', 'OidcAuth.Lib');
|
||||
$oidc = new Oidc($this);
|
||||
return $oidc->isUserValid($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize GPG. Returns `null` if initialization failed.
|
||||
*
|
||||
|
|
|
@ -68,7 +68,7 @@ class UserSetting extends AppModel
|
|||
)
|
||||
),
|
||||
'homepage' => array(
|
||||
'path' => '/events/index'
|
||||
'placeholder' => ['path' => '/events/index'],
|
||||
),
|
||||
'default_restsearch_parameters' => array(
|
||||
'placeholder' => array(
|
||||
|
@ -98,7 +98,7 @@ class UserSetting extends AppModel
|
|||
'placeholder' => ['clusters'],
|
||||
],
|
||||
'oidc' => [ // Data saved by OIDC plugin
|
||||
'restricted' => 'perm_site_admin',
|
||||
'internal' => true,
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -134,11 +134,53 @@ class UserSetting extends AppModel
|
|||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $setting
|
||||
* @return bool
|
||||
*/
|
||||
public function checkSettingValidity($setting)
|
||||
{
|
||||
return isset(self::VALID_SETTINGS[$setting]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $setting
|
||||
* @return bool
|
||||
*/
|
||||
public function isInternal($setting)
|
||||
{
|
||||
if (!isset(self::VALID_SETTINGS[$setting]['internal'])) {
|
||||
return false;
|
||||
}
|
||||
return self::VALID_SETTINGS[$setting]['internal'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @return array
|
||||
*/
|
||||
public function settingPlaceholders(array $user)
|
||||
{
|
||||
$output = [];
|
||||
foreach (self::VALID_SETTINGS as $setting => $config) {
|
||||
if ($this->checkSettingAccess($user, $setting) === true) {
|
||||
$output[$setting] = $config['placeholder'];
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function getInternalSettingNames()
|
||||
{
|
||||
$internal = [];
|
||||
foreach (self::VALID_SETTINGS as $setting => $config) {
|
||||
if (isset($config['internal']) && $config['internal']) {
|
||||
$internal[] = $setting;
|
||||
}
|
||||
}
|
||||
return $internal;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @param string $setting
|
||||
|
@ -146,6 +188,9 @@ class UserSetting extends AppModel
|
|||
*/
|
||||
public function checkSettingAccess(array $user, $setting)
|
||||
{
|
||||
if ($this->isInternal($setting)) {
|
||||
return 'site_admin';
|
||||
}
|
||||
if (!empty(self::VALID_SETTINGS[$setting]['restricted'])) {
|
||||
$roleCheck = self::VALID_SETTINGS[$setting]['restricted'];
|
||||
if (!is_array($roleCheck)) {
|
||||
|
@ -164,18 +209,25 @@ class UserSetting extends AppModel
|
|||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
/**
|
||||
* canModify expects an auth user object or a user ID and a loaded setting as input parameters
|
||||
* check if the user can modify/remove the given entry
|
||||
* returns true for site admins
|
||||
* returns true for org admins if setting["User"]["org_id"] === $user["org_id"]
|
||||
* returns true for any user if setting["user_id"] === $user["id"]
|
||||
* @param array|int $user Current user
|
||||
* @param array $setting
|
||||
* @param int $user_id
|
||||
* @return bool
|
||||
*/
|
||||
public function checkAccess($user, $setting, $user_id = false)
|
||||
public function checkAccess($user, array $setting, $user_id = false)
|
||||
{
|
||||
if (is_numeric($user)) {
|
||||
$user = $this->User->getAuthUser($user);
|
||||
}
|
||||
if ($this->isInternal($setting['UserSetting']['setting']) && !$user['Role']['perm_site_admin']) {
|
||||
return false;
|
||||
}
|
||||
if (empty($setting) && !empty($user_id) && is_numeric($user_id)) {
|
||||
$userToCheck = $this->User->find('first', array(
|
||||
'conditions' => array('User.id' => $user_id),
|
||||
|
@ -393,7 +445,7 @@ class UserSetting extends AppModel
|
|||
if (empty($userSetting['user_id'])) {
|
||||
$userSetting['user_id'] = $user['id'];
|
||||
}
|
||||
if (empty($data['UserSetting']['setting']) || !isset($data['UserSetting']['setting'])) {
|
||||
if (empty($data['UserSetting']['setting'])) {
|
||||
throw new MethodNotAllowedException(__('This endpoint expects both a setting and a value to be set.'));
|
||||
}
|
||||
if (!$this->checkSettingValidity($data['UserSetting']['setting'])) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?php
|
||||
App::uses('BaseAuthenticate', 'Controller/Component/Auth');
|
||||
App::uses('Oidc', 'OidcAuth.Lib');
|
||||
|
||||
/**
|
||||
* Config options:
|
||||
|
@ -12,13 +13,12 @@ App::uses('BaseAuthenticate', 'Controller/Component/Auth');
|
|||
* - OidcAuth.organisation_property (default: `organization`)
|
||||
* - OidcAuth.roles_property (default: `roles`)
|
||||
* - OidcAuth.default_org
|
||||
* - OidcAuth.unblock
|
||||
* - OidcAuth.unblock (boolean, default: false)
|
||||
* - OidcAuth.offline_access (boolean, default: false)
|
||||
* - OidcAuth.check_user_validity (integer, default `0`)
|
||||
*/
|
||||
class OidcAuthenticate extends BaseAuthenticate
|
||||
{
|
||||
/** @var User|null */
|
||||
private $userModel;
|
||||
|
||||
/**
|
||||
* @param CakeRequest $request
|
||||
* @param CakeResponse $response
|
||||
|
@ -27,297 +27,8 @@ class OidcAuthenticate extends BaseAuthenticate
|
|||
*/
|
||||
public function authenticate(CakeRequest $request, CakeResponse $response)
|
||||
{
|
||||
$oidc = $this->prepareClient();
|
||||
|
||||
if (!$oidc->authenticate()) {
|
||||
throw new Exception("OIDC authentication was not successful.");
|
||||
}
|
||||
|
||||
$verifiedClaims = $oidc->getVerifiedClaims();
|
||||
|
||||
$mispUsername = isset($verifiedClaims->email) ? $verifiedClaims->email : $oidc->requestUserInfo('email');
|
||||
$this->log($mispUsername, "Trying login.");
|
||||
|
||||
$sub = $verifiedClaims->sub;
|
||||
$organisationProperty = $this->getConfig('organisation_property', 'organization');
|
||||
if (property_exists($verifiedClaims, $organisationProperty)) {
|
||||
$organisationName = $verifiedClaims->{$organisationProperty};
|
||||
} else {
|
||||
$organisationName = $this->getConfig('default_org');
|
||||
}
|
||||
|
||||
$roles = [];
|
||||
$roleProperty = $this->getConfig('roles_property', 'roles');
|
||||
if (property_exists($verifiedClaims, $roleProperty)) {
|
||||
$roles = $verifiedClaims->{$roleProperty};
|
||||
}
|
||||
if (empty($roles)) {
|
||||
$roles = $oidc->requestUserInfo($roleProperty);
|
||||
}
|
||||
|
||||
// Try to find user by `sub` field, that is unique
|
||||
$this->settings['fields'] = ['username' => 'sub'];
|
||||
$user = $this->_findUser($sub);
|
||||
|
||||
if (!$user) { // User by sub not found, try to find by email
|
||||
$this->settings['fields'] = ['username' => 'email'];
|
||||
$user = $this->_findUser($mispUsername);
|
||||
if ($user && $user['sub'] !== null && $user['sub'] !== $sub) {
|
||||
$this->log($mispUsername, "User sub doesn't match ({$user['sub']} != $sub), could not login.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$organisationId = $this->checkOrganization($organisationName, $user, $mispUsername);
|
||||
if (!$organisationId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleId = $this->getUserRole($roles, $mispUsername);
|
||||
if ($roleId === null) {
|
||||
$this->log($mispUsername, 'No role was assigned.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
$this->log($mispUsername, "Found in database with ID {$user['id']}.");
|
||||
|
||||
if ($user['sub'] === null) {
|
||||
$this->userModel()->updateField($user, 'sub', $sub);
|
||||
$this->log($mispUsername, "User sub changed from NULL to $sub.");
|
||||
$user['sub'] = $sub;
|
||||
}
|
||||
|
||||
if ($user['email'] !== $mispUsername) {
|
||||
$this->userModel()->updateField($user, 'email', $mispUsername);
|
||||
$this->log($mispUsername, "User e-mail changed from {$user['email']} to $mispUsername.");
|
||||
$user['email'] = $mispUsername;
|
||||
}
|
||||
|
||||
if ($user['org_id'] != $organisationId) {
|
||||
$this->userModel()->updateField($user, 'org_id', $organisationId);
|
||||
$this->log($mispUsername, "User organisation changed from {$user['org_id']} to $organisationId.");
|
||||
$user['org_id'] = $organisationId;
|
||||
}
|
||||
|
||||
if ($user['role_id'] != $roleId) {
|
||||
$this->userModel()->updateField($user, 'role_id', $roleId);
|
||||
$this->log($mispUsername, "User role changed from {$user['role_id']} to $roleId.");
|
||||
$user['role_id'] = $roleId;
|
||||
}
|
||||
|
||||
if ($user['disabled'] && $this->getConfig('unblock', false)) {
|
||||
$this->userModel()->updateField($user, 'disabled', false);
|
||||
$this->log($mispUsername, "Unblocking user.");
|
||||
$user['disabled'] = false;
|
||||
}
|
||||
$this->storeMetadata($user['id'], $verifiedClaims);
|
||||
$this->log($mispUsername, 'Logged in.');
|
||||
return $user;
|
||||
}
|
||||
|
||||
$this->log($mispUsername, 'Not found in database.');
|
||||
|
||||
$userData = [
|
||||
'email' => $mispUsername,
|
||||
'org_id' => $organisationId,
|
||||
'newsread' => time(),
|
||||
'role_id' => $roleId,
|
||||
'change_pw' => 0,
|
||||
'date_created' => time(),
|
||||
'sub' => $sub,
|
||||
];
|
||||
|
||||
if (!$this->userModel()->save($userData)) {
|
||||
throw new RuntimeException("Could not save user `$mispUsername` to database.");
|
||||
}
|
||||
|
||||
$this->storeMetadata($this->userModel()->id, $verifiedClaims);
|
||||
|
||||
$this->log($mispUsername, "Saved in database with ID {$this->userModel()->id}");
|
||||
$this->log($mispUsername, 'Logged in.');
|
||||
return $this->_findUser($mispUsername);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \JakubOnderka\OpenIDConnectClient|\Jumbojett\OpenIDConnectClient
|
||||
* @throws Exception
|
||||
*/
|
||||
private function prepareClient()
|
||||
{
|
||||
$providerUrl = $this->getConfig('provider_url');
|
||||
if (!filter_var($providerUrl, FILTER_VALIDATE_URL)) {
|
||||
throw new RuntimeException("Config option `OidcAuth.provider_url` must be valid URL.");
|
||||
}
|
||||
|
||||
$clientId = $this->getConfig('client_id');
|
||||
$clientSecret = $this->getConfig('client_secret');
|
||||
$authenticationMethod = $this->getConfig('authentication_method', false);
|
||||
|
||||
if (class_exists("\JakubOnderka\OpenIDConnectClient")) {
|
||||
$oidc = new \JakubOnderka\OpenIDConnectClient($providerUrl, $clientId, $clientSecret);
|
||||
if ($authenticationMethod !== false && $authenticationMethod !== null) {
|
||||
$oidc->setAuthenticationMethod($authenticationMethod);
|
||||
}
|
||||
} else if (class_exists("\Jumbojett\OpenIDConnectClient")) {
|
||||
// OpenIDConnectClient will append well-know path, so if well-know path is already part of the url, remove it
|
||||
// This is required just for Jumbojett, not for JakubOnderka
|
||||
$wellKnownPosition = strpos($providerUrl, '/.well-known/');
|
||||
if ($wellKnownPosition !== false) {
|
||||
$providerUrl = substr($providerUrl, 0, $wellKnownPosition);
|
||||
}
|
||||
|
||||
$oidc = new \Jumbojett\OpenIDConnectClient($providerUrl, $clientId, $clientSecret);
|
||||
if ($authenticationMethod !== false && $authenticationMethod !== null) {
|
||||
throw new Exception("Jumbojett OIDC implementation do not support changing authentication method, please use JakubOnderka's client");
|
||||
}
|
||||
} else {
|
||||
throw new Exception("OpenID connect client is not installed.");
|
||||
}
|
||||
|
||||
$ccm = $this->getConfig('code_challenge_method', false);
|
||||
if ($ccm) {
|
||||
$oidc->setCodeChallengeMethod($ccm);
|
||||
}
|
||||
|
||||
$oidc->setRedirectURL(Configure::read('MISP.baseurl') . '/users/login');
|
||||
return $oidc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $org
|
||||
* @param array|null $user
|
||||
* @param string $mispUsername
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
private function checkOrganization($org, $user, $mispUsername)
|
||||
{
|
||||
if (empty($org)) {
|
||||
$this->log($mispUsername, "Organisation name not provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$orgIsUuid = Validation::uuid($org);
|
||||
|
||||
$orgAux = $this->userModel()->Organisation->find('first', [
|
||||
'fields' => ['Organisation.id'],
|
||||
'conditions' => $orgIsUuid ? ['uuid' => mb_strtolower($org)] : ['name' => $org],
|
||||
]);
|
||||
if (empty($orgAux)) {
|
||||
if ($orgIsUuid) {
|
||||
$this->log($mispUsername, "Could not found organisation with UUID `$org`.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$orgUserId = 1; // By default created by the admin
|
||||
if ($user) {
|
||||
$orgUserId = $user['id'];
|
||||
}
|
||||
$orgId = $this->userModel()->Organisation->createOrgFromName($org, $orgUserId, true);
|
||||
$this->log($mispUsername, "User organisation `$org` created with ID $orgId.");
|
||||
} else {
|
||||
$orgId = $orgAux['Organisation']['id'];
|
||||
$this->log($mispUsername, "User organisation `$org` found with ID $orgId.");
|
||||
}
|
||||
return $orgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $roles Role list provided by OIDC
|
||||
* @param string $mispUsername
|
||||
* @return int|null Role ID or null if no role matches
|
||||
*/
|
||||
private function getUserRole(array $roles, $mispUsername)
|
||||
{
|
||||
$this->log($mispUsername, 'Provided roles: ' . implode(', ', $roles));
|
||||
$roleMapper = $this->getConfig('role_mapper');
|
||||
if (!is_array($roleMapper)) {
|
||||
throw new RuntimeException("Config option `OidcAuth.role_mapper` must be array.");
|
||||
}
|
||||
|
||||
$roleNameToId = $this->userModel()->Role->find('list', [
|
||||
'fields' => ['Role.name', 'Role.id'],
|
||||
]);
|
||||
$roleNameToId = array_change_key_case($roleNameToId); // normalize role names to lowercase
|
||||
|
||||
foreach ($roleMapper as $oidcRole => $mispRole) {
|
||||
if (in_array($oidcRole, $roles, true)) {
|
||||
if (!is_numeric($mispRole)) {
|
||||
$mispRole = mb_strtolower($mispRole);
|
||||
if (isset($roleNameToId[$mispRole])) {
|
||||
$mispRole = $roleNameToId[$mispRole];
|
||||
} else {
|
||||
$this->log($mispUsername, "MISP Role with name `$mispRole` not found, skipping.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return $mispRole; // first match wins
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $config
|
||||
* @param mixed|null $default
|
||||
* @return mixed
|
||||
*/
|
||||
private function getConfig($config, $default = null)
|
||||
{
|
||||
$value = Configure::read("OidcAuth.$config");
|
||||
if (empty($value)) {
|
||||
if ($default === null) {
|
||||
throw new RuntimeException("Config option `OidcAuth.$config` is not set.");
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @param stdClass $verifiedClaims
|
||||
* @return array|bool|mixed|null
|
||||
* @throws Exception
|
||||
*/
|
||||
private function storeMetadata($userId, \stdClass $verifiedClaims)
|
||||
{
|
||||
// OIDC session ID
|
||||
if (isset($verifiedClaims->sid)) {
|
||||
CakeSession::write('oidc_sid', $verifiedClaims->sid);
|
||||
}
|
||||
|
||||
$value = [];
|
||||
foreach (['preferred_username', 'given_name', 'family_name'] as $field) {
|
||||
if (property_exists($verifiedClaims, $field)) {
|
||||
$value[$field] = $verifiedClaims->{$field};
|
||||
}
|
||||
}
|
||||
|
||||
return $this->userModel()->UserSetting->setSettingInternal($userId, 'oidc', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $message
|
||||
*/
|
||||
private function log($username, $message)
|
||||
{
|
||||
CakeLog::info("OIDC: User `$username` – $message");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return User
|
||||
*/
|
||||
private function userModel()
|
||||
{
|
||||
if (isset($this->userModel)) {
|
||||
return $this->userModel;
|
||||
}
|
||||
|
||||
$this->userModel = ClassRegistry::init($this->settings['userModel']);
|
||||
return $this->userModel;
|
||||
$userModel = ClassRegistry::init($this->settings['userModel']);
|
||||
$oidc = new Oidc($userModel);
|
||||
return $oidc->authenticate($this->settings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,472 @@
|
|||
<?php
|
||||
class Oidc
|
||||
{
|
||||
private $oidcClient;
|
||||
|
||||
/** @var User */
|
||||
private $User;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
$this->User = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|false
|
||||
* @throws Exception
|
||||
*/
|
||||
public function authenticate(array $settings)
|
||||
{
|
||||
$oidc = $this->prepareClient();
|
||||
|
||||
if (!$oidc->authenticate()) {
|
||||
throw new Exception("OIDC authentication was not successful.");
|
||||
}
|
||||
|
||||
$claims = $oidc->getVerifiedClaims();
|
||||
|
||||
$mispUsername = $claims->email ?? $oidc->requestUserInfo('email');
|
||||
$this->log($mispUsername, "Trying login.");
|
||||
|
||||
$sub = $claims->sub; // sub is required
|
||||
|
||||
// Try to find user by `sub` field, that is unique
|
||||
$user = $this->_findUser($settings, ['sub' => $sub]);
|
||||
|
||||
if (!$user) { // User by sub not found, try to find by email
|
||||
$user = $this->_findUser($settings, ['email' => $mispUsername]);
|
||||
if ($user && $user['sub'] !== null && $user['sub'] !== $sub) {
|
||||
$this->log($mispUsername, "User sub doesn't match ({$user['sub']} != $sub), could not login.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$organisationProperty = $this->getConfig('organisation_property', 'organization');
|
||||
$organisationName = $claims->{$organisationProperty} ?? $this->getConfig('default_org');
|
||||
$organisationId = $this->checkOrganization($organisationName, $user, $mispUsername);
|
||||
if (!$organisationId) {
|
||||
if ($user) {
|
||||
$this->block($user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleProperty = $this->getConfig('roles_property', 'roles');
|
||||
$roles = $claims->{$roleProperty} ?? $oidc->requestUserInfo($roleProperty);
|
||||
if ($roles === null) {
|
||||
$this->log($user['email'], "Role property `$roleProperty` is missing in claims.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleId = $this->getUserRole($roles, $mispUsername);
|
||||
if ($roleId === null) {
|
||||
$this->log($mispUsername, 'No role was assigned.');
|
||||
if ($user) {
|
||||
$this->block($user);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($user) {
|
||||
$this->log($mispUsername, "Found in database with ID {$user['id']}.");
|
||||
|
||||
if ($user['sub'] === null) {
|
||||
$this->User->updateField($user, 'sub', $sub);
|
||||
$this->log($mispUsername, "User sub changed from NULL to $sub.");
|
||||
$user['sub'] = $sub;
|
||||
}
|
||||
|
||||
if ($user['email'] !== $mispUsername) {
|
||||
$this->User->updateField($user, 'email', $mispUsername);
|
||||
$this->log($mispUsername, "User e-mail changed from {$user['email']} to $mispUsername.");
|
||||
$user['email'] = $mispUsername;
|
||||
}
|
||||
|
||||
if ($user['org_id'] != $organisationId) {
|
||||
$this->User->updateField($user, 'org_id', $organisationId);
|
||||
$this->log($mispUsername, "User organisation changed from {$user['org_id']} to $organisationId.");
|
||||
$user['org_id'] = $organisationId;
|
||||
}
|
||||
|
||||
if ($user['role_id'] != $roleId) {
|
||||
$this->User->updateField($user, 'role_id', $roleId);
|
||||
$this->log($mispUsername, "User role changed from {$user['role_id']} to $roleId.");
|
||||
$user['role_id'] = $roleId;
|
||||
}
|
||||
|
||||
if ($user['disabled'] && $this->getConfig('unblock', false)) {
|
||||
$this->User->updateField($user, 'disabled', false);
|
||||
$this->log($mispUsername, "Unblocking user.");
|
||||
$user['disabled'] = false;
|
||||
}
|
||||
|
||||
$refreshToken = $this->getConfig('offline_access', false) ? $oidc->getRefreshToken() : null;
|
||||
$this->storeMetadata($user['id'], $claims, $refreshToken);
|
||||
|
||||
$this->log($mispUsername, 'Logged in.');
|
||||
return $user;
|
||||
}
|
||||
|
||||
$this->log($mispUsername, 'Not found in database.');
|
||||
|
||||
$userData = [
|
||||
'email' => $mispUsername,
|
||||
'org_id' => $organisationId,
|
||||
'newsread' => time(),
|
||||
'role_id' => $roleId,
|
||||
'change_pw' => 0,
|
||||
'date_created' => time(),
|
||||
'sub' => $sub,
|
||||
];
|
||||
|
||||
if (!$this->User->save($userData)) {
|
||||
throw new RuntimeException("Could not save user `$mispUsername` to database.");
|
||||
}
|
||||
|
||||
$refreshToken = $this->getConfig('offline_access', false) ? $oidc->getRefreshToken() : null;
|
||||
$this->storeMetadata($this->User->id, $claims, $refreshToken);
|
||||
|
||||
$this->log($mispUsername, "Saved in database with ID {$this->User->id}");
|
||||
$this->log($mispUsername, 'Logged in.');
|
||||
$user = $this->_findUser($settings, ['id' => $this->User->id]);
|
||||
|
||||
if ($user['User']['sub'] !== $sub) { // just to be sure that we have the correct user
|
||||
throw new Exception("User {$user['email']} sub doesn't match ({$user['sub']} != $sub)");
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @param bool $ignoreValidityTime Ignore `check_user_validity` setting and always check if user is valid
|
||||
* @param bool $update Update user role or organisation from OIDC
|
||||
* @return bool True if user is still valid, false if not
|
||||
* @throws Exception
|
||||
*/
|
||||
public function isUserValid(array $user, $ignoreValidityTime = false, $update = false)
|
||||
{
|
||||
if (!$this->getConfig('offline_access', false)) {
|
||||
return true; // offline access is not enabled, so it is not possible to verify user
|
||||
}
|
||||
|
||||
if (!$ignoreValidityTime) {
|
||||
$checkUserValidityEvery = $this->getConfig('check_user_validity', 0);
|
||||
if ($checkUserValidityEvery === 0) {
|
||||
return true; // validity checking is disabled
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($user['id'])) {
|
||||
throw new InvalidArgumentException("Invalid user model provided.");
|
||||
}
|
||||
|
||||
if (empty($user['sub'])) {
|
||||
return true; // user is not OIDC managed user
|
||||
}
|
||||
|
||||
$userInfo = $this->findUserInfo($user);
|
||||
if (!isset($userInfo['refresh_token'])) {
|
||||
$this->log($user['email'], "User don't have refresh token, considering user is not valid");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$ignoreValidityTime && $userInfo['validity_check_timestamp'] > time() - $checkUserValidityEvery) {
|
||||
return true; // user was checked in last `check_user_validity`, do not check again
|
||||
}
|
||||
|
||||
$oidc = $this->prepareClient();
|
||||
|
||||
try {
|
||||
$oidc->refreshToken($userInfo['refresh_token']);
|
||||
} catch (JakubOnderka\ErrorResponse $e) {
|
||||
if ($e->getError() === 'invalid_grant') {
|
||||
$this->log($user['email'], "Refreshing token is not possible because of `{$e->getMessage()}`, considering user is not valid");
|
||||
return false;
|
||||
} else {
|
||||
$this->log($user['email'], "Refreshing token is not possible because of `{$e->getMessage()}`, considering user is still valid");
|
||||
return true;
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$this->log($user['email'], "Refreshing token is not possible because of `{$e->getMessage()}`, considering user is still valid");
|
||||
return true;
|
||||
}
|
||||
|
||||
$claims = $oidc->getVerifiedClaims();
|
||||
if ($user['sub'] !== $claims->sub) {
|
||||
throw new Exception("User {$user['email']} sub doesn't match ({$user['sub']} != $claims->sub)");
|
||||
}
|
||||
|
||||
// Check user role
|
||||
$roleProperty = $this->getConfig('roles_property', 'roles');
|
||||
$roles = $claims->{$roleProperty} ?? $oidc->requestUserInfo($roleProperty);
|
||||
if ($roles === null) {
|
||||
$this->log($user['email'], "Role property `$roleProperty` is missing in claims.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$roleId = $this->getUserRole($roles, $user['email']);
|
||||
if ($roleId === null) {
|
||||
$this->log($user['email'], 'No role was assigned.');
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($update && $user['role_id'] != $roleId) {
|
||||
$this->User->updateField($user, 'role_id', $roleId);
|
||||
$this->log($user['email'], "User role changed from {$user['role_id']} to $roleId.");
|
||||
}
|
||||
|
||||
// Check user org
|
||||
$organisationProperty = $this->getConfig('organisation_property', 'organization');
|
||||
$organisationName = $claims->{$organisationProperty} ?? $this->getConfig('default_org');
|
||||
$organisationId = $this->checkOrganization($organisationName, $user, $user['email']);
|
||||
if (!$organisationId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($update && $user['org_id'] != $organisationId) {
|
||||
$this->User->updateField($user, 'org_id', $organisationId);
|
||||
$this->log($user['email'], "User organisation changed from {$user['org_id']} to $organisationId.");
|
||||
}
|
||||
|
||||
// Update refresh token if new token provided
|
||||
if ($oidc->getRefreshToken()) {
|
||||
$this->storeMetadata($user['id'], $claims, $oidc->getRefreshToken());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @param bool $ignoreValidityTime
|
||||
* @param bool $update Update user role or organisation
|
||||
* @return bool True if user was blocked, false if not
|
||||
* @throws Exception
|
||||
*/
|
||||
public function blockInvalidUser(array $user, $ignoreValidityTime = false, $update = false)
|
||||
{
|
||||
$isValid = $this->isUserValid($user, $ignoreValidityTime, $update);
|
||||
if (!$isValid) {
|
||||
$this->block($user);
|
||||
}
|
||||
return $isValid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \JakubOnderka\OpenIDConnectClient
|
||||
* @throws Exception
|
||||
*/
|
||||
private function prepareClient()
|
||||
{
|
||||
if ($this->oidcClient) {
|
||||
return $this->oidcClient;
|
||||
}
|
||||
|
||||
$providerUrl = $this->getConfig('provider_url');
|
||||
$clientId = $this->getConfig('client_id');
|
||||
$clientSecret = $this->getConfig('client_secret');
|
||||
|
||||
if (class_exists("\JakubOnderka\OpenIDConnectClient")) {
|
||||
$oidc = new \JakubOnderka\OpenIDConnectClient($providerUrl, $clientId, $clientSecret);
|
||||
} else if (class_exists("\Jumbojett\OpenIDConnectClient")) {
|
||||
throw new Exception("Jumbojett OIDC implementation is not supported anymore, please use JakubOnderka's client");
|
||||
} else {
|
||||
throw new Exception("OpenID Connect client is not installed.");
|
||||
}
|
||||
|
||||
$authenticationMethod = $this->getConfig('authentication_method', false);
|
||||
if ($authenticationMethod !== false && $authenticationMethod !== null) {
|
||||
$oidc->setAuthenticationMethod($authenticationMethod);
|
||||
}
|
||||
|
||||
$ccm = $this->getConfig('code_challenge_method', false);
|
||||
if ($ccm) {
|
||||
$oidc->setCodeChallengeMethod($ccm);
|
||||
}
|
||||
|
||||
if ($this->getConfig('offline_access', false)) {
|
||||
$oidc->addScope('offline_access');
|
||||
}
|
||||
|
||||
$oidc->setRedirectURL(Configure::read('MISP.baseurl') . '/users/login');
|
||||
$this->oidcClient = $oidc;
|
||||
return $oidc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $org
|
||||
* @param array|null $user
|
||||
* @param string $mispUsername
|
||||
* @return int
|
||||
* @throws Exception
|
||||
*/
|
||||
private function checkOrganization($org, $user, $mispUsername)
|
||||
{
|
||||
if (empty($org)) {
|
||||
$this->log($mispUsername, "Organisation name not provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$orgIsUuid = Validation::uuid($org);
|
||||
|
||||
$orgAux = $this->User->Organisation->find('first', [
|
||||
'fields' => ['Organisation.id'],
|
||||
'conditions' => $orgIsUuid ? ['uuid' => strtolower($org)] : ['name' => $org],
|
||||
]);
|
||||
if (empty($orgAux)) {
|
||||
if ($orgIsUuid) {
|
||||
$this->log($mispUsername, "Could not found organisation with UUID `$org`.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$orgUserId = 1; // By default created by the admin
|
||||
if ($user) {
|
||||
$orgUserId = $user['id'];
|
||||
}
|
||||
$orgId = $this->User->Organisation->createOrgFromName($org, $orgUserId, true);
|
||||
$this->log($mispUsername, "User organisation `$org` created with ID $orgId.");
|
||||
} else {
|
||||
$orgId = $orgAux['Organisation']['id'];
|
||||
$this->log($mispUsername, "User organisation `$org` found with ID $orgId.");
|
||||
}
|
||||
return $orgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $roles Role list provided by OIDC
|
||||
* @param string $mispUsername
|
||||
* @return int|null Role ID or null if no role matches
|
||||
*/
|
||||
private function getUserRole(array $roles, $mispUsername)
|
||||
{
|
||||
$this->log($mispUsername, 'Provided roles: ' . implode(', ', $roles));
|
||||
$roleMapper = $this->getConfig('role_mapper');
|
||||
if (!is_array($roleMapper)) {
|
||||
throw new RuntimeException("Config option `OidcAuth.role_mapper` must be array.");
|
||||
}
|
||||
|
||||
$roleNameToId = $this->User->Role->find('list', [
|
||||
'fields' => ['Role.name', 'Role.id'],
|
||||
]);
|
||||
$roleNameToId = array_change_key_case($roleNameToId); // normalize role names to lowercase
|
||||
|
||||
foreach ($roleMapper as $oidcRole => $mispRole) {
|
||||
if (in_array($oidcRole, $roles, true)) {
|
||||
if (!is_numeric($mispRole)) {
|
||||
$mispRole = mb_strtolower($mispRole);
|
||||
if (isset($roleNameToId[$mispRole])) {
|
||||
$mispRole = $roleNameToId[$mispRole];
|
||||
} else {
|
||||
$this->log($mispUsername, "MISP Role with name `$mispRole` not found, skipping.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return $mispRole; // first match wins
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $settings
|
||||
* @param array $conditions
|
||||
* @return array|null
|
||||
*/
|
||||
private function _findUser(array $settings, array $conditions)
|
||||
{
|
||||
$result = $this->User->find('first', [
|
||||
'conditions' => $conditions,
|
||||
'recursive' => $settings['recursive'],
|
||||
'fields' => $settings['userFields'],
|
||||
'contain' => $settings['contain'],
|
||||
]);
|
||||
if ($result) {
|
||||
$user = $result['User'];
|
||||
unset($result['User']);
|
||||
return array_merge($user, $result);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $config
|
||||
* @param mixed|null $default
|
||||
* @return mixed
|
||||
*/
|
||||
private function getConfig($config, $default = null)
|
||||
{
|
||||
$value = Configure::read("OidcAuth.$config");
|
||||
if (empty($value)) {
|
||||
if ($default === null) {
|
||||
throw new RuntimeException("Config option `OidcAuth.$config` is not set.");
|
||||
}
|
||||
return $default;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @return array
|
||||
*/
|
||||
private function findUserInfo(array $user)
|
||||
{
|
||||
if (isset($user['UserSetting'])) {
|
||||
foreach ($user['UserSetting'] as $userSetting) {
|
||||
if ($userSetting['setting'] === 'oidc') {
|
||||
return $userSetting['value'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $this->User->UserSetting->getValueForUser($user['id'], 'oidc');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @param stdClass $verifiedClaims
|
||||
* @param string|null $refreshToken
|
||||
* @return array|bool|mixed|null
|
||||
* @throws Exception
|
||||
*/
|
||||
private function storeMetadata($userId, \stdClass $verifiedClaims, $refreshToken = null)
|
||||
{
|
||||
// OIDC session ID
|
||||
if (isset($verifiedClaims->sid)) {
|
||||
CakeSession::write('oidc_sid', $verifiedClaims->sid);
|
||||
}
|
||||
|
||||
$value = [];
|
||||
foreach (['preferred_username', 'given_name', 'family_name'] as $field) {
|
||||
if (property_exists($verifiedClaims, $field)) {
|
||||
$value[$field] = $verifiedClaims->{$field};
|
||||
}
|
||||
}
|
||||
if ($refreshToken) {
|
||||
$value['validity_check_timestamp'] = time();
|
||||
$value['refresh_token'] = $refreshToken;
|
||||
}
|
||||
|
||||
return $this->User->UserSetting->setSettingInternal($userId, 'oidc', $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
private function block(array $user)
|
||||
{
|
||||
$this->User->updateField($user, 'disabled', true);
|
||||
$this->log($user['email'], "User blocked by OIDC");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @param string $message
|
||||
*/
|
||||
private function log($username, $message)
|
||||
{
|
||||
CakeLog::info("OIDC: User `$username` – $message");
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
# MISP OpenID Connect Authentication
|
||||
|
||||
This plugin provides ability to use OpenID as Single sign-on for login users to MISP.
|
||||
When plugin is enabled, users are direcly redirected to SSO provider and it is not possible
|
||||
When plugin is enabled, users are directly redirected to SSO provider and it is not possible
|
||||
to login with passwords stored in MISP.
|
||||
|
||||
## Usage
|
||||
|
@ -45,5 +45,9 @@ $config = array(
|
|||
|
||||
## Caveats
|
||||
|
||||
* When user is blocked in SSO (IdM), he/she will be not blocked in MISP. He could not log in, but users authentication keys will still work and also he/she will still receive all emails.
|
||||
When user is blocked in SSO (IdM), he/she will be not blocked in MISP. He could not log in, but users authentication keys will still work and also he/she will still receive all emails.
|
||||
|
||||
To solve this problem:
|
||||
1) set `OidcAuth.offline_access` to `true` - with that, IdP will be requested to provide offline access token
|
||||
2) set `OidcAuth.check_user_validity` to number of seconds, after which user will be revalidated if he is still active in IdP. Zero means that this functionality is disabled. Recommended value is `300`.
|
||||
3) because offline tokens will expire when not used, you can run `cake user check_user_validity` to check all user in one call
|
||||
|
|
|
@ -39,8 +39,8 @@
|
|||
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'globalActions', 'menuItem' => 'user_settings_set'));
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
var validSettings = <?php echo json_encode($validSettings); ?>;
|
||||
$(document).ready(function() {
|
||||
var validSettings = <?= json_encode($validSettings); ?>;
|
||||
$(function() {
|
||||
loadUserSettingValue();
|
||||
changeUserSettingPlaceholder();
|
||||
$('#UserSettingSetting').on('change', function() {
|
||||
|
@ -56,11 +56,11 @@
|
|||
var user_id = $('#UserSettingUserId').val();
|
||||
var setting = $('#UserSettingSetting').val();
|
||||
$.ajax({
|
||||
type:"get",
|
||||
type: "get",
|
||||
url: baseurl + "/user_settings/getSetting/" + user_id + "/" + setting,
|
||||
success: function (data, textStatus) {
|
||||
success: function (data) {
|
||||
if (data === '[]') {
|
||||
var data = '';
|
||||
data = '';
|
||||
} else {
|
||||
data = JSON.parse(data);
|
||||
data = JSON.stringify(data, undefined, 4);
|
||||
|
@ -73,7 +73,7 @@
|
|||
function changeUserSettingPlaceholder() {
|
||||
var setting = $('#UserSettingSetting').val();
|
||||
if (setting in validSettings) {
|
||||
$('#UserSettingValue').attr("placeholder", "Example:\n" + JSON.stringify(validSettings[setting]["placeholder"], undefined, 4));
|
||||
$('#UserSettingValue').attr("placeholder", "Example:\n" + JSON.stringify(validSettings[setting], undefined, 4));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -4587,7 +4587,7 @@ function checkNoticeList(type) {
|
|||
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$(function() {
|
||||
// Show popover for disabled input that contains `data-disabled-reason`.
|
||||
$('input:disabled[data-disabled-reason]').popover("destroy").popover({
|
||||
placement: 'right',
|
||||
|
@ -4642,7 +4642,7 @@ $(document).ready(function() {
|
|||
var url = $(this).data('checkbox-url');
|
||||
});
|
||||
|
||||
$('#setHomePage').click(function(event) {
|
||||
$('#setHomePage').parent().click(function(event) {
|
||||
event.preventDefault();
|
||||
setHomePage();
|
||||
});
|
||||
|
@ -5209,7 +5209,7 @@ function setHomePage() {
|
|||
$.ajax({
|
||||
type: 'GET',
|
||||
url: baseurl + '/userSettings/setHomePage',
|
||||
success:function (data) {
|
||||
success: function (data) {
|
||||
$('#ajax_hidden_container').html(data);
|
||||
var currentPage = $('#setHomePage').data('current-page');
|
||||
$('#UserSettingPath').val(currentPage);
|
||||
|
|
Loading…
Reference in New Issue