new: [authkeys] Allowed IPs

pull/7104/head
Jakub Onderka 2021-02-27 11:13:47 +01:00
parent 8c316b7245
commit 599819f7f9
17 changed files with 380 additions and 157 deletions

View File

@ -125,8 +125,8 @@ class AppController extends Controller
}
if (!$this->_isRest()) {
$this->__contentSecurityPolicy();
$this->response->header('X-XSS-Protection', '1; mode=block');
}
$this->response->header('X-XSS-Protection', '1; mode=block');
if (!empty($this->params['named']['sql'])) {
$this->sql_dump = intval($this->params['named']['sql']);
@ -446,22 +446,9 @@ class AppController extends Controller
} else {
// User not authenticated correctly
// reset the session information
$redis = $this->User->setupRedis();
// Do not log every fail, but just once per hour
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . $authKeyToStore)) {
$redis->setex('misp:auth_fail_throttling:' . $authKeyToStore, 3600, 1);
if ($this->_shouldLog($authKeyToStore)) {
$this->loadModel('Log');
$this->Log->create();
$log = array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => 0,
'email' => 'SYSTEM',
'action' => 'auth_fail',
'title' => "Failed authentication using API key ($authKeyToStore)",
'change' => null,
);
$this->Log->save($log);
$this->Log->createLogEntry('SYSTEM', 'auth_fail', 'User', 0, "Failed authentication using API key ($authKeyToStore)");
}
$this->Session->destroy();
}
@ -548,8 +535,10 @@ class AppController extends Controller
}
if ($user['disabled']) {
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], 'Login attempt by disabled 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.');
}
$this->Auth->logout();
if ($this->_isRest()) {
@ -565,11 +554,33 @@ class AppController extends Controller
if (isset($user['authkey_expiration']) && $user['authkey_expiration']) {
$time = isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
if ($user['authkey_expiration'] < $time) {
if ($this->_shouldLog('expired:' . $user['authkey_id'])) {
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], "Login attempt by expired auth key {$user['authkey_id']}.");
}
$this->Auth->logout();
throw new ForbiddenException('Auth key is expired');
}
}
if (!empty($user['allowed_ips'])) {
App::uses('CidrTool', 'Tools');
$cidrTool = new CidrTool($user['allowed_ips']);
$remoteIp = $this->_remoteIp();
if ($remoteIp === null) {
$this->Auth->logout();
throw new ForbiddenException('Auth key is limited to IP address, but IP address not found');
}
if (!$cidrTool->contains($remoteIp)) {
if ($this->_shouldLog('not_allowed_ip:' . $user['authkey_id'] . ':' . $remoteIp)) {
$this->Log = ClassRegistry::init('Log');
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], "Login attempt from not allowed IP address for auth key {$user['authkey_id']}.");
}
$this->Auth->logout();
throw new ForbiddenException('It is not possible to use this Auth key from your IP address');
}
}
$isUserRequest = !$this->_isRest() && !$this->request->is('ajax') && !$this->_isAutomation();
// Next checks makes sense just for user direct HTTP request, so skip REST and AJAX calls
if (!$isUserRequest) {
@ -632,7 +643,7 @@ class AppController extends Controller
return;
}
$remoteAddress = trim($_SERVER['REMOTE_ADDR']);
$remoteAddress = $this->_remoteIp();
$pipe = $redis->multi(Redis::PIPELINE);
// keep for 30 days
@ -680,11 +691,7 @@ class AppController extends Controller
$change .= PHP_EOL . 'Request body: ' . $payload;
}
$this->Log = ClassRegistry::init('Log');
try {
$this->Log->createLogEntry($user, 'request', 'User', $user['id'], 'Paranoid log entry', $change);
} catch (Exception $e) {
// When `MISP.log_skip_db_logs_completely` is enabled, Log::createLogEntry method throws exception
}
$this->Log->createLogEntry($user, 'request', 'User', $user['id'], 'Paranoid log entry', $change);
}
}
@ -1500,17 +1507,47 @@ class AppController extends Controller
throw new RuntimeException("User with ID {$sessionUser['id']} not exists.");
}
if (isset($sessionUser['authkey_id'])) {
// Reload authkey
$this->loadModel('AuthKey');
if (!$this->AuthKey->exists($sessionUser['authkey_id'])) {
$authKey = $this->AuthKey->find('first', [
'conditions' => ['id' => $sessionUser['authkey_id'], 'user_id' => $user['id']],
'fields' => ['id', 'expiration', 'allowed_ips'],
'recursive' => -1,
]);
if (empty($authKey)) {
throw new RuntimeException("Auth key with ID {$sessionUser['authkey_id']} not exists.");
}
$user['authkey_id'] = $authKey['AuthKey']['id'];
$user['authkey_expiration'] = $authKey['AuthKey']['expiration'];
$user['allowed_ips'] = $authKey['AuthKey']['allowed_ips'];
}
foreach (['authkey_id', 'authkey_expiration', 'logged_by_authkey'] as $copy) {
if (isset($sessionUser[$copy])) {
$user[$copy] = $sessionUser[$copy];
}
if (isset($sessionUser['logged_by_authkey'])) {
$user['logged_by_authkey'] = $sessionUser['logged_by_authkey'];
}
$this->Auth->login($user);
return $user;
}
/**
* @return string|null
*/
protected function _remoteIp()
{
$ipHeader = Configure::read('MISP.log_client_ip_header') ?: 'REMOTE_ADDR';
return isset($_SERVER[$ipHeader]) ? trim($_SERVER[$ipHeader]) : null;
}
/**
* @param string $key
* @return bool Returns true if the same log defined by $key was not stored in last hour
*/
protected function _shouldLog($key)
{
$redis = $this->User->setupRedis();
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . $key)) {
$redis->setex('misp:auth_fail_throttling:' . $key, 3600, 1);
return true;
}
return false;
}
}

View File

@ -71,8 +71,34 @@ class AuthKeysController extends AppController
public function edit($id)
{
$this->set('metaGroup', 'admin');
$this->set('metaAction', 'authkeys_edit');
$this->CRUD->edit($id, [
'conditions' => $this->__prepareConditions(),
'afterFind' => function (array $authKey) {
unset($authKey['AuthKey']['authkey']);
if (is_array($authKey['AuthKey']['allowed_ips'])) {
$authKey['AuthKey']['allowed_ips'] = implode("\n", $authKey['AuthKey']['allowed_ips']);
}
$authKey['AuthKey']['expiration'] = date('Y-m-d H:i:s', $authKey['AuthKey']['expiration']);
return $authKey;
},
'fields' => ['comment', 'allowed_ips', 'expiration'],
]);
if ($this->IndexFilter->isRest()) {
return $this->restResponsePayload;
}
$this->set('dropdownData', [
'user' => $this->User->find('list', [
'sort' => ['username' => 'asc'],
'conditions' => ['id' => $this->request->data['AuthKey']['user_id']],
])
]);
$this->set('menuData', [
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
'menuItem' => 'authKeyAdd',
]);
$this->set('edit', true);
$this->set('validity', Configure::read('Security.advanced_authkeys_validity'));
$this->render('add');
}
public function add($user_id = false)

View File

@ -145,13 +145,21 @@ class CRUDComponent extends Component
if (empty($id)) {
throw new NotFoundException(__('Invalid %s.', $modelName));
}
$data = $this->Controller->{$modelName}->find('first',
isset($params['get']) ? $params['get'] : [
'recursive' => -1,
'conditions' => [
'id' => $id
]
]);
$query = isset($params['get']) ? $params['get'] : [
'recursive' => -1,
'conditions' => [
'id' => $id
],
];
if (!empty($params['conditions'])) {
$query['conditions']['AND'][] = $params['conditions'];
}
/** @var Model $model */
$model = $this->Controller->{$modelName};
$data = $model->find('first', $query);
if (isset($params['afterFind'])) {
$data = $params['afterFind']($data);
}
if ($this->Controller->request->is('post') || $this->Controller->request->is('put')) {
$input = $this->Controller->request->data;
if (empty($input[$modelName])) {
@ -171,7 +179,10 @@ class CRUDComponent extends Component
$data[$modelName][$field] = $fieldData;
}
}
if ($this->Controller->{$modelName}->save($data)) {
if (isset($params['beforeSave'])) {
$data = $params['beforeSave']($data);
}
if ($model->save($data)) {
$message = __('%s updated.', $modelName);
if ($this->Controller->IndexFilter->isRest()) {
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
@ -182,7 +193,9 @@ class CRUDComponent extends Component
}
} else {
if ($this->Controller->IndexFilter->isRest()) {
$controllerName = $this->Controller->params['controller'];
$actionName = $this->Controller->params['action'];
$this->Controller->restResponsePayload = $this->Controller->RestResponse->saveFailResponse($controllerName, $actionName, false, $model->validationErrors, 'json');
}
}
} else {

View File

@ -2434,9 +2434,9 @@ misp.direct_call(relative_path, body)
}
$message = 'CSP reported violation';
$ipHeader = Configure::read('MISP.log_client_ip_header') ?: 'REMOTE_ADDR';
if (isset($_SERVER[$ipHeader])) {
$message .= ' from IP ' . $_SERVER[$ipHeader];
$remoteIp = $this->_remoteIp();
if ($remoteIp) {
$message .= ' from IP ' . $remoteIp;
}
$this->log("$message: " . json_encode($report['csp-report'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

View File

@ -66,6 +66,26 @@ class CidrTool
return $match;
}
/**
* @param string $cidr
* @return bool
*/
public static function validate($cidr)
{
$parts = explode('/', $cidr, 2);
$ipBytes = inet_pton($parts[0]);
if ($ipBytes === false) {
return false;
}
$maximumNetmask = strlen($ipBytes) === 4 ? 32 : 128;
if (isset($parts[1]) && ($parts[1] > $maximumNetmask || $parts[1] < 0)) {
return false; // Netmask part of CIDR is invalid
}
return true;
}
/**
* Using solution from https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/IpUtils.php
*

View File

@ -89,7 +89,7 @@ class AppModel extends Model
45 => false, 46 => false, 47 => false, 48 => false, 49 => false, 50 => false,
51 => false, 52 => false, 53 => false, 54 => false, 55 => false, 56 => false,
57 => false, 58 => false, 59 => false, 60 => false, 61 => false, 62 => false,
63 => true, 64 => false, 65 => false
63 => true, 64 => false, 65 => false, 66 => false, 67 => false,
);
public $advanced_updates_description = array(
@ -1572,6 +1572,9 @@ class AppModel extends Model
$sqlArray[] = "ALTER TABLE `galaxy_clusters` MODIFY COLUMN `tag_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '';";
$indexArray[] = ['event_reports', 'event_id'];
break;
case 67:
$sqlArray[] = "ALTER TABLE `auth_keys` ADD `allowed_ips` text DEFAULT NULL;";
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';

View File

@ -1,6 +1,7 @@
<?php
App::uses('AppModel', 'Model');
App::uses('RandomTool', 'Tools');
App::uses('CidrTool', 'Tools');
/**
* @property User $User
@ -26,7 +27,6 @@ class AuthKey extends AppModel
// massage the data before we send it off for validation before saving anything
public function beforeValidate($options = array())
{
//parent::beforeValidate();
if (empty($this->data['AuthKey']['id'])) {
if (empty($this->data['AuthKey']['uuid'])) {
$this->data['AuthKey']['uuid'] = CakeText::uuid();
@ -42,22 +42,66 @@ class AuthKey extends AppModel
$this->data['AuthKey']['authkey_end'] = substr($authkey, -4);
$this->data['AuthKey']['authkey_raw'] = $authkey;
$this->authkey_raw = $authkey;
}
$validity = Configure::read('Security.advanced_authkeys_validity');
if (empty($this->data['AuthKey']['expiration'])) {
$this->data['AuthKey']['expiration'] = $validity ? strtotime("+$validity days") : 0;
if (!empty($this->data['AuthKey']['allowed_ips'])) {
if (is_string($this->data['AuthKey']['allowed_ips'])) {
$this->data['AuthKey']['allowed_ips'] = trim($this->data['AuthKey']['allowed_ips']);
if (empty($this->data['AuthKey']['allowed_ips'])) {
$this->data['AuthKey']['allowed_ips'] = [];
} else {
$this->data['AuthKey']['allowed_ips'] = explode("\n", $this->data['AuthKey']['allowed_ips']);
$this->data['AuthKey']['allowed_ips'] = array_map('trim', $this->data['AuthKey']['allowed_ips']);
}
}
if (!is_array($this->data['AuthKey']['allowed_ips'])) {
$this->invalidate('allowed_ips', 'Allowed IPs must be array');
}
foreach ($this->data['AuthKey']['allowed_ips'] as $cidr) {
if (!CidrTool::validate($cidr)) {
$this->invalidate('allowed_ips', "$cidr is not valid IP range");
}
}
}
$creationTime = isset($this->data['AuthKey']['created']) ? $this->data['AuthKey']['created'] : time();
$validity = Configure::read('Security.advanced_authkeys_validity');
if (empty($this->data['AuthKey']['expiration'])) {
$this->data['AuthKey']['expiration'] = $validity ? strtotime("+$validity days", $creationTime) : 0;
} else {
$expiration = is_numeric($this->data['AuthKey']['expiration']) ?
(int)$this->data['AuthKey']['expiration'] :
strtotime($this->data['AuthKey']['expiration']);
if ($expiration === false) {
$this->invalidate('expiration', __('Expiration must be in YYYY-MM-DD format.'));
}
if ($validity && $expiration > strtotime("+$validity days", $creationTime)) {
$this->invalidate('expiration', __('Maximal key validity is %s days.', $validity));
}
$this->data['AuthKey']['expiration'] = $expiration;
}
return true;
}
public function afterFind($results, $primary = false)
{
foreach ($results as $key => $val) {
if (isset($val['AuthKey']['allowed_ips'])) {
$results[$key]['AuthKey']['allowed_ips'] = $this->jsonDecode($val['AuthKey']['allowed_ips']);
}
}
return $results;
}
public function beforeSave($options = array())
{
if (isset($this->data['AuthKey']['allowed_ips'])) {
if (empty($this->data['AuthKey']['allowed_ips'])) {
$this->data['AuthKey']['allowed_ips'] = null;
} else {
$expiration = is_numeric($this->data['AuthKey']['expiration']) ?
(int)$this->data['AuthKey']['expiration'] :
strtotime($this->data['AuthKey']['expiration']);
if ($expiration === false) {
$this->invalidate('expiration', __('Expiration must be in YYYY-MM-DD format.'));
}
if ($validity && $expiration > strtotime("+$validity days")) {
$this->invalidate('expiration', __('Maximal key validity is %s days.', $validity));
}
$this->data['AuthKey']['expiration'] = $expiration;
$this->data['AuthKey']['allowed_ips'] = json_encode($this->data['AuthKey']['allowed_ips']);
}
}
return true;
@ -71,9 +115,9 @@ class AuthKey extends AppModel
{
$start = substr($authkey, 0, 4);
$end = substr($authkey, -4);
$existing_authkeys = $this->find('all', [
$possibleAuthkeys = $this->find('all', [
'recursive' => -1,
'fields' => ['id', 'authkey', 'user_id', 'expiration'],
'fields' => ['id', 'authkey', 'user_id', 'expiration', 'allowed_ips'],
'conditions' => [
'OR' => [
'expiration >' => time(),
@ -84,12 +128,13 @@ class AuthKey extends AppModel
]
]);
$passwordHasher = $this->getHasher();
foreach ($existing_authkeys as $existing_authkey) {
if ($passwordHasher->check($authkey, $existing_authkey['AuthKey']['authkey'])) {
$user = $this->User->getAuthUser($existing_authkey['AuthKey']['user_id']);
foreach ($possibleAuthkeys as $possibleAuthkey) {
if ($passwordHasher->check($authkey, $possibleAuthkey['AuthKey']['authkey'])) {
$user = $this->User->getAuthUser($possibleAuthkey['AuthKey']['user_id']);
if ($user) {
$user['authkey_id'] = $existing_authkey['AuthKey']['id'];
$user['authkey_expiration'] = $existing_authkey['AuthKey']['expiration'];
$user['authkey_id'] = $possibleAuthkey['AuthKey']['id'];
$user['authkey_expiration'] = $possibleAuthkey['AuthKey']['expiration'];
$user['allowed_ips'] = $possibleAuthkey['AuthKey']['allowed_ips'];
}
return $user;
}
@ -175,6 +220,18 @@ class AuthKey extends AppModel
return $output;
}
/**
* When key is modified, update `date_modified` for user that was assigned to that key, so session data
* will be realoaded.
* @see AppController::_refreshAuth
*/
public function afterSave($created, $options = array())
{
parent::afterSave($created, $options);
$userId = $this->data['AuthKey']['user_id'];
$this->User->updateAll(['date_modified' => time()], ['User.id' => $userId]);
}
/**
* When key is deleted, update after `date_modified` for user that was assigned to that key, so session data
* will be realoaded and canceled.

View File

@ -199,7 +199,7 @@ class Log extends AppModel
* @param int $modelId
* @param string $title
* @param string|array $change
* @return array
* @return array|null
* @throws Exception
* @throws InvalidArgumentException
*/
@ -238,6 +238,10 @@ class Log extends AppModel
));
if (!$result) {
if ($action === 'request' && !empty(Configure::read('MISP.log_paranoid_skip_db'))) {
return null;
}
throw new Exception("Cannot save log because of validation errors: " . json_encode($this->validationErrors));
}

View File

@ -1,7 +1,7 @@
<?php
echo $this->element('genericElements/Form/genericForm', [
'data' => [
'title' => __('Add auth key'),
'title' => isset($edit) ? __('Edit auth key') : __('Add auth key'),
'description' => __('Auth keys are used for API access. A user can have more than one authkey, so if you would like to use separate keys per tool that queries MISP, add additional keys. Use the comment field to make identifying your keys easier.'),
'fields' => [
[
@ -13,7 +13,14 @@ echo $this->element('genericElements/Form/genericForm', [
[
'field' => 'comment',
'label' => __('Comment'),
'class' => 'span6'
'class' => 'span6',
'rows' => 4,
],
[
'field' => 'allowed_ips',
'label' => __('Allowed IPs'),
'class' => 'span6',
'rows' => 4,
],
[
'field' => 'expiration',

View File

@ -63,12 +63,17 @@
'data_path' => 'AuthKey.last_used',
'element' => 'datetime',
'requirements' => $keyUsageEnabled,
'empty' => __('Never'),
],
[
'name' => __('Comment'),
'sort' => 'AuthKey.comment',
'data_path' => 'AuthKey.comment',
],
[
'name' => __('Allowed IPs'),
'data_path' => 'AuthKey.allowed_ips',
],
],
'title' => empty($ajax) ? __('Authentication key Index') : false,
'description' => empty($ajax) ? __('A list of API keys bound to a user.') : false,
@ -80,7 +85,16 @@
'AuthKey.id'
),
'icon' => 'eye',
'dbclickAction' => true
'dbclickAction' => true,
'title' => 'View auth key',
],
[
'url' => $baseurl . '/auth_keys/edit',
'url_params_data_paths' => array(
'AuthKey.id'
),
'icon' => 'edit',
'title' => 'Edit auth key',
],
[
'onclick' => sprintf(

View File

@ -15,65 +15,72 @@ if (isset($keyUsage)) {
$uniqueIps = null;
}
echo $this->element(
'genericElements/SingleViews/single_view',
[
'title' => 'Auth key view',
'data' => $data,
'fields' => [
[
'key' => __('ID'),
'path' => 'AuthKey.id'
],
[
'key' => __('UUID'),
'path' => 'AuthKey.uuid',
],
[
'key' => __('Auth Key'),
'path' => 'AuthKey',
'type' => 'authkey'
],
[
'key' => __('User'),
'path' => 'User.id',
'pathName' => 'User.email',
'model' => 'users',
'type' => 'model'
],
[
'key' => __('Comment'),
'path' => 'AuthKey.comment'
],
[
'key' => __('Created'),
'path' => 'AuthKey.created',
'type' => 'datetime'
],
[
'key' => __('Expiration'),
'path' => 'AuthKey.expiration',
'type' => 'expiration'
],
[
'key' => __('Key usage'),
'type' => 'sparkline',
'path' => 'AuthKey.id',
'csv' => [
'data' => $keyUsageCsv,
],
'requirement' => isset($keyUsage),
],
[
'key' => __('Last used'),
'raw' => $lastUsed ? $this->Time->time($lastUsed) : __('Not used yet'),
'requirement' => isset($keyUsage),
],
[
'key' => __('Unique IPs'),
'raw' => $uniqueIps,
'requirement' => isset($keyUsage),
]
echo $this->element('genericElements/SingleViews/single_view', [
'title' => 'Auth key view',
'data' => $data,
'fields' => [
[
'key' => __('ID'),
'path' => 'AuthKey.id'
],
]
);
[
'key' => __('UUID'),
'path' => 'AuthKey.uuid',
],
[
'key' => __('Auth Key'),
'path' => 'AuthKey',
'type' => 'authkey'
],
[
'key' => __('User'),
'path' => 'User.id',
'pathName' => 'User.email',
'model' => 'users',
'type' => 'model'
],
[
'key' => __('Comment'),
'path' => 'AuthKey.comment'
],
[
'key' => __('Allowed IPs'),
'type' => 'custom',
'function' => function (array $data) {
if (is_array($data['AuthKey']['allowed_ips'])) {
return implode("<br>", array_map('h', $data['AuthKey']['allowed_ips']));
}
return __('All');
}
],
[
'key' => __('Created'),
'path' => 'AuthKey.created',
'type' => 'datetime'
],
[
'key' => __('Expiration'),
'path' => 'AuthKey.expiration',
'type' => 'expiration'
],
[
'key' => __('Key usage'),
'type' => 'sparkline',
'path' => 'AuthKey.id',
'csv' => [
'data' => $keyUsageCsv,
],
'requirement' => isset($keyUsage),
],
[
'key' => __('Last used'),
'raw' => $lastUsed ? $this->Time->time($lastUsed) : __('Not used yet'),
'requirement' => isset($keyUsage),
],
[
'key' => __('Unique IPs'),
'raw' => $uniqueIps,
'requirement' => isset($keyUsage),
]
],
]);

View File

@ -1,26 +1,27 @@
<?php
$data = Hash::extract($row, $field['data_path']);
if (is_array($data)) {
if (count($data) > 1) {
$data = implode(', ', $data);
$data = Hash::extract($row, $field['data_path']);
if (is_array($data)) {
if (count($data) > 1) {
$data = implode(', ', $data);
} else {
if (count($data) > 0) {
$data = $data[0];
} else {
if (count($data) > 0) {
$data = $data[0];
} else {
$data = '';
}
$data = '';
}
}
if (empty($data) && !empty($field['empty'])) {
$data = $field['empty'];
}
}
if (empty($data) && !empty($field['empty'])) {
$data = $field['empty'];
} else {
$data = $this->Time->time($data);
if (!empty($field['onClick'])) {
$data = sprintf(
'<span onClick="%s">%s</span>',
$field['onClick'],
$data
);
}
echo $data;
}
if (!empty($field['onClick'])) {
$data = sprintf(
'<span onClick="%s">%s</span>',
$field['onClick'],
$data
);
}
echo $data;

View File

@ -0,0 +1 @@
<?= $field['function']($data);

View File

@ -174,13 +174,11 @@ td.searchLabelCancel{
form .error-message {
border-color: #b94a48;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
background-color: #f2dede;
padding: 0 5px;
}
/* Form */
div.form,
div.index,

View File

@ -5298,7 +5298,8 @@ function submitGenericFormInPlace() {
$('#genericModal').modal('hide').remove();
$('body').append(data);
$('#genericModal').modal();
}
},
error: xhrFailCallback,
});
}

View File

@ -483,6 +483,17 @@
"column_type": "text",
"column_default": null,
"extra": ""
},
{
"column_name": "allowed_ips",
"is_nullable": "YES",
"data_type": "text",
"character_maximum_length": "65535",
"numeric_precision": null,
"collation_name": "utf8mb4_unicode_ci",
"column_type": "text",
"column_default": null,
"extra": ""
}
],
"bruteforces": [
@ -7975,5 +7986,5 @@
"id": true
}
},
"db_version": "65"
"db_version": "67"
}

View File

@ -562,6 +562,29 @@ class TestSecurity(unittest.TestCase):
self.__delete_advanced_authkey(auth_key["id"])
def test_advanced_authkeys_invalid_ip(self):
with self.__setting("Security.advanced_authkeys", True):
auth_key = self.__create_advanced_authkey(self.test_usr.id, {
"allowed_ips": ["1.2.3.4"],
})
# Try to login
with self.assertRaises(PyMISPError):
PyMISP(url, auth_key["authkey_raw"])
self.__delete_advanced_authkey(auth_key["id"])
def test_advanced_authkeys_allow_all(self):
with self.__setting("Security.advanced_authkeys", True):
auth_key = self.__create_advanced_authkey(self.test_usr.id, {
"allowed_ips": ["0.0.0.0/0"],
})
# Try to login
PyMISP(url, auth_key["authkey_raw"])
self.__delete_advanced_authkey(auth_key["id"])
def test_authkey_keep_session(self):
with self.__setting( "Security.authkey_keep_session", True):
logged_in = PyMISP(url, self.test_usr.authkey)