mirror of https://github.com/MISP/MISP
new: [authkeys] Allowed IPs
parent
8c316b7245
commit
599819f7f9
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;';
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
],
|
||||
]);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<?= $field['function']($data);
|
|
@ -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,
|
||||
|
|
|
@ -5298,7 +5298,8 @@ function submitGenericFormInPlace() {
|
|||
$('#genericModal').modal('hide').remove();
|
||||
$('body').append(data);
|
||||
$('#genericModal').modal();
|
||||
}
|
||||
},
|
||||
error: xhrFailCallback,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue