mirror of https://github.com/MISP/MISP
new: [API] Read only authkeys
parent
51821b5de2
commit
017249451b
|
@ -1525,17 +1525,7 @@ class AppController extends Controller
|
|||
if (isset($sessionUser['authkey_id'])) {
|
||||
// Reload authkey
|
||||
$this->loadModel('AuthKey');
|
||||
$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'];
|
||||
$user = $this->AuthKey->updateUserData($user, $sessionUser['authkey_id']);
|
||||
}
|
||||
if (isset($sessionUser['logged_by_authkey'])) {
|
||||
$user['logged_by_authkey'] = $sessionUser['logged_by_authkey'];
|
||||
|
|
|
@ -82,7 +82,7 @@ class AuthKeysController extends AppController
|
|||
$authKey['AuthKey']['expiration'] = date('Y-m-d H:i:s', $authKey['AuthKey']['expiration']);
|
||||
return $authKey;
|
||||
},
|
||||
'fields' => ['comment', 'allowed_ips', 'expiration'],
|
||||
'fields' => ['comment', 'allowed_ips', 'expiration', 'read_only'],
|
||||
'contain' => ['User.id', 'User.org_id']
|
||||
]);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
|
@ -100,6 +100,7 @@ class AuthKeysController extends AppController
|
|||
]);
|
||||
$this->set('edit', true);
|
||||
$this->set('validity', Configure::read('Security.advanced_authkeys_validity'));
|
||||
$this->set('title_for_layout', __('Edit auth key'));
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
|
@ -134,6 +135,7 @@ class AuthKeysController extends AppController
|
|||
])
|
||||
];
|
||||
$this->set(compact('dropdownData'));
|
||||
$this->set('title_for_layout', __('Add auth key'));
|
||||
$this->set('menuData', [
|
||||
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
|
||||
'menuItem' => 'authKeyAdd',
|
||||
|
@ -162,7 +164,7 @@ class AuthKeysController extends AppController
|
|||
$this->set('uniqueIps', $uniqueIps);
|
||||
}
|
||||
|
||||
$this->set('title_for_layout', __('Auth Key'));
|
||||
$this->set('title_for_layout', __('Auth key'));
|
||||
$this->set('menuData', [
|
||||
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
|
||||
'menuItem' => 'authKeyView',
|
||||
|
|
|
@ -71,9 +71,9 @@ class ACLComponent extends Component
|
|||
'viewPicture' => array('*'),
|
||||
),
|
||||
'authKeys' => [
|
||||
'add' => ['perm_auth'],
|
||||
'delete' => ['perm_auth'],
|
||||
'edit' => ['perm_auth'],
|
||||
'add' => ['AND' => ['perm_auth', 'not_read_only_authkey']],
|
||||
'delete' => ['AND' => ['perm_auth', 'not_read_only_authkey']],
|
||||
'edit' => ['AND' => ['perm_auth', 'not_read_only_authkey']],
|
||||
'index' => ['perm_auth'],
|
||||
'view' => ['perm_auth']
|
||||
],
|
||||
|
@ -468,9 +468,9 @@ class ACLComponent extends Component
|
|||
'display' => array('*'),
|
||||
),
|
||||
'posts' => array(
|
||||
'add' => array('*'),
|
||||
'delete' => array('*'),
|
||||
'edit' => array('*'),
|
||||
'add' => array('not_read_only_authkey'),
|
||||
'delete' => array('not_read_only_authkey'),
|
||||
'edit' => array('not_read_only_authkey'),
|
||||
'pushMessageToZMQ' => array('perm_site_admin')
|
||||
),
|
||||
'regexp' => array(
|
||||
|
@ -483,7 +483,7 @@ class ACLComponent extends Component
|
|||
'index' => array('*'),
|
||||
),
|
||||
'restClientHistory' => array(
|
||||
'delete' => array('*'),
|
||||
'delete' => array('not_read_only_authkey'),
|
||||
'index' => array('*')
|
||||
),
|
||||
'roles' => array(
|
||||
|
@ -693,7 +693,7 @@ class ACLComponent extends Component
|
|||
'admin_quickEmail' => array('perm_admin'),
|
||||
'admin_view' => array('perm_admin'),
|
||||
'attributehistogram' => array('*'),
|
||||
'change_pw' => ['AND' => ['self_management_enabled', 'password_change_enabled']],
|
||||
'change_pw' => ['AND' => ['self_management_enabled', 'password_change_enabled', 'not_read_only_authkey']],
|
||||
'checkAndCorrectPgps' => array(),
|
||||
'checkIfLoggedIn' => array('*'),
|
||||
'dashboard' => array('*'),
|
||||
|
@ -711,7 +711,7 @@ class ACLComponent extends Component
|
|||
'register' => array('*'),
|
||||
'registrations' => array('perm_site_admin'),
|
||||
'resetAllSyncAuthKeys' => array(),
|
||||
'resetauthkey' => ['AND' => ['self_management_enabled', 'perm_auth']],
|
||||
'resetauthkey' => ['AND' => ['self_management_enabled', 'perm_auth', 'not_read_only_authkey']],
|
||||
'request_API' => array('*'),
|
||||
'routeafterlogin' => array('*'),
|
||||
'statistics' => array('*'),
|
||||
|
@ -727,10 +727,10 @@ class ACLComponent extends Component
|
|||
'userSettings' => array(
|
||||
'index' => array('*'),
|
||||
'view' => array('*'),
|
||||
'setSetting' => array('*'),
|
||||
'setSetting' => array('not_read_only_authkey'),
|
||||
'getSetting' => array('*'),
|
||||
'delete' => array('*'),
|
||||
'setHomePage' => array('*'),
|
||||
'delete' => array('not_read_only_authkey'),
|
||||
'setHomePage' => array('not_read_only_authkey'),
|
||||
'eventIndexColumnToggle' => ['*'],
|
||||
),
|
||||
'warninglists' => array(
|
||||
|
@ -792,6 +792,10 @@ class ACLComponent extends Component
|
|||
$this->dynamicChecks['delegation_enabled'] = function (array $user) {
|
||||
return (bool)Configure::read('MISP.delegation');
|
||||
};
|
||||
// Returns true if current user is not using advanced auth key or if authkey is not read only
|
||||
$this->dynamicChecks['not_read_only_authkey'] = function (array $user) {
|
||||
return !isset($user['authkey_read_only']) || !$user['authkey_read_only'];
|
||||
};
|
||||
}
|
||||
|
||||
private function __checkLoggedActions($user, $controller, $action)
|
||||
|
|
|
@ -90,7 +90,7 @@ class AppModel extends Model
|
|||
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, 66 => false, 67 => false, 68 => false,
|
||||
69 => false, 70 => false, 71 => true,
|
||||
69 => false, 70 => false, 71 => true, 72 => true,
|
||||
);
|
||||
|
||||
public $advanced_updates_description = array(
|
||||
|
@ -1608,6 +1608,9 @@ class AppModel extends Model
|
|||
$sqlArray[] = "ALTER TABLE `warninglist_entries` ADD `comment` text DEFAULT NULL;";
|
||||
$sqlArray[] = "ALTER TABLE `warninglists` ADD `default` tinyint(1) NOT NULL DEFAULT 1, ADD `category` varchar(20) NOT NULL DEFAULT 'false_positive', DROP COLUMN `warninglist_entry_count`";
|
||||
break;
|
||||
case 72:
|
||||
$sqlArray[] = "ALTER TABLE `auth_keys` ADD `read_only` tinyint(1) NOT NULL DEFAULT 0 AFTER `expiration`;";
|
||||
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;';
|
||||
|
|
|
@ -108,6 +108,24 @@ class AuthKey extends AppModel
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @param int $authKeyId
|
||||
* @return array
|
||||
*/
|
||||
public function updateUserData(array $user, $authKeyId)
|
||||
{
|
||||
$authKey = $this->find('first', [
|
||||
'conditions' => ['id' => $authKeyId, 'user_id' => $user['id']],
|
||||
'fields' => ['id', 'expiration', 'allowed_ips', 'read_only'],
|
||||
'recursive' => -1,
|
||||
]);
|
||||
if (empty($authKey)) {
|
||||
throw new RuntimeException("Auth key with ID $authKeyId doesn't exist anymore.");
|
||||
}
|
||||
return $this->setUserData($user, $authKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $authkey
|
||||
* @return array|false
|
||||
|
@ -118,7 +136,7 @@ class AuthKey extends AppModel
|
|||
$end = substr($authkey, -4);
|
||||
$possibleAuthkeys = $this->find('all', [
|
||||
'recursive' => -1,
|
||||
'fields' => ['id', 'authkey', 'user_id', 'expiration', 'allowed_ips'],
|
||||
'fields' => ['id', 'authkey', 'user_id', 'expiration', 'allowed_ips', 'read_only'],
|
||||
'conditions' => [
|
||||
'OR' => [
|
||||
'expiration >' => time(),
|
||||
|
@ -133,9 +151,7 @@ class AuthKey extends AppModel
|
|||
if ($passwordHasher->check($authkey, $possibleAuthkey['AuthKey']['authkey'])) {
|
||||
$user = $this->User->getAuthUser($possibleAuthkey['AuthKey']['user_id']);
|
||||
if ($user) {
|
||||
$user['authkey_id'] = $possibleAuthkey['AuthKey']['id'];
|
||||
$user['authkey_expiration'] = $possibleAuthkey['AuthKey']['expiration'];
|
||||
$user['allowed_ips'] = $possibleAuthkey['AuthKey']['allowed_ips'];
|
||||
$user = $this->setUserData($user, $possibleAuthkey);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
@ -143,6 +159,30 @@ class AuthKey extends AppModel
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @param array $authkey
|
||||
* @return array
|
||||
*/
|
||||
private function setUserData(array $user, array $authkey)
|
||||
{
|
||||
$user['authkey_id'] = $authkey['AuthKey']['id'];
|
||||
$user['authkey_expiration'] = $authkey['AuthKey']['expiration'];
|
||||
$user['allowed_ips'] = $authkey['AuthKey']['allowed_ips'];
|
||||
$user['authkey_read_only'] = (bool)$authkey['AuthKey']['read_only'];
|
||||
|
||||
if ($authkey['AuthKey']['read_only']) {
|
||||
// Disable all permissions, keep just `perm_auth` unchanged
|
||||
foreach ($user['Role'] as $key => &$value) {
|
||||
if (substr($key, 0, 5) === 'perm_' && $key !== 'perm_auth') {
|
||||
$value = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @param int|null $keyId
|
||||
|
@ -264,7 +304,7 @@ class AuthKey extends AppModel
|
|||
|
||||
/**
|
||||
* When key is modified, update `date_modified` for user that was assigned to that key, so session data
|
||||
* will be realoaded.
|
||||
* will be reloaded.
|
||||
* @see AppController::_refreshAuth
|
||||
*/
|
||||
public function afterSave($created, $options = array())
|
||||
|
|
|
@ -28,6 +28,11 @@ echo $this->element('genericElements/Form/genericForm', [
|
|||
'class' => 'datepicker span6',
|
||||
'placeholder' => "YYYY-MM-DD",
|
||||
'type' => 'text'
|
||||
],
|
||||
[
|
||||
'field' => 'read_only',
|
||||
'label' => __('Read only (it will be not possible to do any change operation with this token)'),
|
||||
'type' => 'checkbox',
|
||||
]
|
||||
],
|
||||
'submit' => [
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
<?php
|
||||
echo sprintf('<div%s>', empty($ajax) ? ' class="index"' : '');
|
||||
if (!$advancedEnabled) {
|
||||
echo '<div class="alert">' . __('Advanced auth keys are not enabled.') . '</div>';
|
||||
}
|
||||
echo sprintf('<div%s>', empty($ajax) ? ' class="index"' : '');
|
||||
echo $this->element('genericElements/IndexTable/index_table', [
|
||||
'data' => [
|
||||
'data' => $data,
|
||||
|
@ -14,6 +14,7 @@
|
|||
'children' => [
|
||||
'data' => [
|
||||
'type' => 'simple',
|
||||
'fa-icon' => 'plus',
|
||||
'text' => __('Add authentication key'),
|
||||
'class' => 'btn btn-primary',
|
||||
'onClick' => 'openGenericModal',
|
||||
|
|
|
@ -63,6 +63,11 @@ echo $this->element('genericElements/SingleViews/single_view', [
|
|||
'path' => 'AuthKey.expiration',
|
||||
'type' => 'expiration'
|
||||
],
|
||||
[
|
||||
'key' => __('Read only'),
|
||||
'path' => 'AuthKey.read_only',
|
||||
'type' => 'boolean'
|
||||
],
|
||||
[
|
||||
'key' => __('Key usage'),
|
||||
'type' => 'sparkline',
|
||||
|
|
|
@ -618,6 +618,17 @@
|
|||
"column_default": null,
|
||||
"extra": ""
|
||||
},
|
||||
{
|
||||
"column_name": "read_only",
|
||||
"is_nullable": "NO",
|
||||
"data_type": "tinyint",
|
||||
"character_maximum_length": null,
|
||||
"numeric_precision": "3",
|
||||
"collation_name": null,
|
||||
"column_type": "tinyint(1)",
|
||||
"column_default": "0",
|
||||
"extra": ""
|
||||
},
|
||||
{
|
||||
"column_name": "user_id",
|
||||
"is_nullable": "NO",
|
||||
|
@ -8191,5 +8202,5 @@
|
|||
"id": true
|
||||
}
|
||||
},
|
||||
"db_version": "71"
|
||||
"db_version": "72"
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import time
|
|||
import json
|
||||
import datetime
|
||||
import unittest
|
||||
from typing import Union, List
|
||||
from typing import Union, List, Optional
|
||||
import urllib3 # type: ignore
|
||||
import logging
|
||||
import uuid
|
||||
|
@ -588,6 +588,87 @@ class TestSecurity(unittest.TestCase):
|
|||
|
||||
self.__delete_advanced_authkey(auth_key["id"])
|
||||
|
||||
def test_advanced_authkeys_read_only_false(self):
|
||||
with self.__setting("Security.advanced_authkeys", True):
|
||||
auth_key = self.__create_advanced_authkey(self.test_usr.id, {
|
||||
"read_only": 0,
|
||||
})
|
||||
self.assertFalse(auth_key["read_only"])
|
||||
|
||||
# Try to login
|
||||
logged_in = self.__login_by_advanced_authkey(auth_key)
|
||||
|
||||
# Create new event should not be possible with read only key
|
||||
event = logged_in.add_event(self.__generate_event())
|
||||
check_response(event)
|
||||
|
||||
self.__delete_advanced_authkey(auth_key["id"])
|
||||
|
||||
def test_advanced_authkeys_read_only(self):
|
||||
with self.__setting("Security.advanced_authkeys", True):
|
||||
auth_key = self.__create_advanced_authkey(self.test_usr.id, {
|
||||
"read_only": 1,
|
||||
})
|
||||
self.assertTrue(auth_key["read_only"])
|
||||
|
||||
# Try to login
|
||||
logged_in = self.__login_by_advanced_authkey(auth_key)
|
||||
|
||||
# Create new event should not be possible with read only key
|
||||
event = logged_in.add_event(self.__generate_event())
|
||||
with self.assertRaises(Exception):
|
||||
check_response(event)
|
||||
|
||||
self.__delete_advanced_authkey(auth_key["id"])
|
||||
|
||||
def test_advanced_authkeys_read_only_edit_self(self):
|
||||
with self.__setting("Security.advanced_authkeys", True):
|
||||
auth_key = self.__create_advanced_authkey(self.test_usr.id, {
|
||||
"read_only": 1,
|
||||
})
|
||||
self.assertTrue(auth_key["read_only"])
|
||||
|
||||
# Try to login
|
||||
logged_in = self.__login_by_advanced_authkey(auth_key)
|
||||
|
||||
# Edit current auth key and set it to not read_only should be not possible
|
||||
with self.assertRaises(Exception):
|
||||
send(logged_in, "POST", f'authKeys/edit/{auth_key["id"]}', {"read_only": 0})
|
||||
|
||||
self.__delete_advanced_authkey(auth_key["id"])
|
||||
|
||||
def test_advanced_authkeys_read_only_create_new_authkey(self):
|
||||
with self.__setting("Security.advanced_authkeys", True):
|
||||
auth_key = self.__create_advanced_authkey(self.test_usr.id, {
|
||||
"read_only": 1,
|
||||
})
|
||||
self.assertTrue(auth_key["read_only"])
|
||||
|
||||
# Try to login
|
||||
logged_in = self.__login_by_advanced_authkey(auth_key)
|
||||
|
||||
# Create new auth key should be not possible
|
||||
with self.assertRaises(Exception):
|
||||
send(logged_in, "POST", f'authKeys/add/{logged_in._current_user.id}')
|
||||
|
||||
self.__delete_advanced_authkey(auth_key["id"])
|
||||
|
||||
def test_advanced_authkeys_read_only_reset_authkey(self):
|
||||
with self.__setting("Security.advanced_authkeys", True):
|
||||
auth_key = self.__create_advanced_authkey(self.test_usr.id, {
|
||||
"read_only": 1,
|
||||
})
|
||||
self.assertTrue(auth_key["read_only"])
|
||||
|
||||
# Try to login
|
||||
logged_in = self.__login_by_advanced_authkey(auth_key)
|
||||
|
||||
# Create new auth key should be not possible
|
||||
with self.assertRaises(Exception):
|
||||
send(logged_in, "POST", "users/resetauthkey/me")
|
||||
|
||||
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)
|
||||
|
@ -1424,8 +1505,16 @@ class TestSecurity(unittest.TestCase):
|
|||
self.assertEqual(int(role_id), int(user.role_id))
|
||||
return user
|
||||
|
||||
def __create_advanced_authkey(self, user_id: int, data=None):
|
||||
return send(self.admin_misp_connector, "POST", f'authKeys/add/{user_id}', data=data)["AuthKey"]
|
||||
def __create_advanced_authkey(self, user_id: int, data: Optional[dict] = None) -> dict:
|
||||
auth_key = send(self.admin_misp_connector, "POST", f'authKeys/add/{user_id}', data=data)["AuthKey"]
|
||||
# it is not possible to call `assertEqual`, because we use this method in `setUpClass` method
|
||||
assert user_id == auth_key["user_id"], "Key was created for different user"
|
||||
return auth_key
|
||||
|
||||
def __login_by_advanced_authkey(self, auth_key: dict) -> PyMISP:
|
||||
logged_in = PyMISP(url, auth_key["authkey_raw"])
|
||||
self.assertEqual(logged_in._current_user.id, auth_key["user_id"], "Logged in by different user")
|
||||
return logged_in
|
||||
|
||||
def __delete_advanced_authkey(self, key_id: int):
|
||||
return send(self.admin_misp_connector, "POST", f'authKeys/delete/{key_id}')
|
||||
|
|
Loading…
Reference in New Issue