Merge branch 'develop' of github.com:MISP/MISP into develop

pull/7071/merge
iglocska 2021-03-03 17:24:36 +01:00
commit e394cfbe66
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
42 changed files with 775 additions and 473 deletions

View File

@ -243,7 +243,8 @@ CREATE TABLE IF NOT EXISTS event_reports (
`deleted` tinyint(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id),
CONSTRAINT u_uuid UNIQUE (uuid),
INDEX `name` (`name`)
INDEX `name` (`name`),
INDEX `event_id` (`event_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
@ -455,7 +456,7 @@ CREATE TABLE IF NOT EXISTS `galaxy_clusters` (
`collection_uuid` varchar(255) COLLATE utf8_bin NOT NULL,
`type` varchar(255) COLLATE utf8_bin NOT NULL,
`value` text COLLATE utf8_bin NOT NULL,
`tag_name` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '',
`tag_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
`description` text COLLATE utf8_bin NOT NULL,
`galaxy_id` int(11) NOT NULL,
`source` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '',

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);
}
}
@ -1261,7 +1268,7 @@ class AppController extends Controller
private function __sessionMassage()
{
if (!empty(Configure::read('MISP.uuid'))) {
if (empty(Configure::read('Session.cookie')) && !empty(Configure::read('MISP.uuid'))) {
Configure::write('Session.cookie', 'MISP-' . Configure::read('MISP.uuid'));
}
if (!empty(Configure::read('Session.cookieTimeout')) || !empty(Configure::read('Session.timeout'))) {
@ -1454,10 +1461,10 @@ class AppController extends Controller
if ($this->userRole['perm_site_admin']) {
return true;
}
if ($this->userRole['perm_modify_org'] && $event['Event']['orgc_id'] == $this->Auth->user('org_id')) {
if ($this->userRole['perm_modify_org'] && $event['Event']['orgc_id'] == $this->Auth->user()['org_id']) {
return true;
}
if ($this->userRole['perm_modify'] && $event['Event']['user_id'] == $this->Auth->user('id')) {
if ($this->userRole['perm_modify'] && $event['Event']['user_id'] == $this->Auth->user()['id']) {
return true;
}
return false;
@ -1501,17 +1508,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

@ -687,7 +687,7 @@ class ACLComponent extends Component
'register' => array('*'),
'registrations' => array('perm_site_admin'),
'resetAllSyncAuthKeys' => array(),
'resetauthkey' => array('*'),
'resetauthkey' => ['AND' => ['self_management_enabled', 'perm_auth']],
'request_API' => array('*'),
'routeafterlogin' => array('*'),
'statistics' => array('*'),

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

@ -4938,6 +4938,7 @@ class EventsController extends AppController
if (!$event) {
throw new NotFoundException(__('Invalid event.'));
}
$mayModify = $this->__canModifyEvent($event);
$eventId = $event['Event']['id'];
$this->loadModel('Module');
@ -5108,7 +5109,7 @@ class EventsController extends AppController
$this->set('module', $module);
$this->set('eventId', $eventId);
$this->set('event', $event);
$this->set('mayModify', $this->__canModifyEvent($event));
$this->set('mayModify', $mayModify);
}
public function exportModule($module, $id, $standard = false)

View File

@ -558,8 +558,8 @@ class GalaxiesController extends AppController
if (empty($clusters)) {
throw new MethodNotAllowedException('Invalid Galaxy.');
}
$this->Galaxy->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->Galaxy->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k] = $this->Galaxy->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
}
$galaxy = $this->Galaxy->find('first', array(

View File

@ -96,62 +96,64 @@ class GalaxyClustersController extends AppController
)
);
return $this->RestResponse->viewData($clusters, $this->response->type());
} else {
$this->paginate['conditions']['AND'][] = $contextConditions;
$this->paginate['conditions']['AND'][] = $searchConditions;
$this->paginate['conditions']['AND'][] = $aclConditions;
$this->paginate['contain'] = array_merge($this->paginate['contain'], array('Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation'));
$clusters = $this->paginate();
$tagIds = array();
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k] = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k]['GalaxyCluster']['relation_counts'] = array(
'out' => count($clusters[$k]['GalaxyClusterRelation']),
'in' => count($clusters[$k]['TargetingClusterRelation']),
);
if (isset($cluster['Tag']['id'])) {
$tagIds[] = $cluster['Tag']['id'];
$clusters[$k]['GalaxyCluster']['tag_id'] = $cluster['Tag']['id'];
}
$clusters[$k]['GalaxyCluster']['synonyms'] = array();
foreach ($cluster['GalaxyElement'] as $element) {
$clusters[$k]['GalaxyCluster']['synonyms'][] = $element['value'];
}
$clusters[$k]['GalaxyCluster']['event_count'] = 0; // real number is assigned later
}
$eventCountsForTags = $this->GalaxyCluster->Tag->EventTag->countForTags($tagIds, $this->Auth->user());
$this->loadModel('Sighting');
$csvForTags = $this->Sighting->tagsSparkline($tagIds, $this->Auth->user(), '0');
foreach ($clusters as $k => $cluster) {
if (isset($cluster['GalaxyCluster']['tag_id'])) {
if (isset($csvForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['csv'] = $csvForTags[$cluster['GalaxyCluster']['tag_id']];
}
if (isset($eventCountsForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['GalaxyCluster']['event_count'] = $eventCountsForTags[$cluster['GalaxyCluster']['tag_id']];
}
}
}
$customClusterCount = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), [
'count' => true,
'conditions' => [
'AND' => [$searchConditions, $aclConditions],
'GalaxyCluster.default' => 0,
]
]);
$this->loadModel('Attribute');
$distributionLevels = $this->Attribute->distributionLevels;
unset($distributionLevels[5]);
$this->set('distributionLevels', $distributionLevels);
$this->set('list', $clusters);
$this->set('galaxy_id', $galaxyId);
$this->set('custom_cluster_count', $customClusterCount);
}
$this->paginate['conditions']['AND'][] = $contextConditions;
$this->paginate['conditions']['AND'][] = $searchConditions;
$this->paginate['conditions']['AND'][] = $aclConditions;
$this->paginate['contain'] = array_merge($this->paginate['contain'], array('Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation'));
$clusters = $this->paginate();
$this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
$tagIds = array();
foreach ($clusters as $k => $cluster) {
$clusters[$k] = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
$clusters[$k]['GalaxyCluster']['relation_counts'] = array(
'out' => count($clusters[$k]['GalaxyClusterRelation']),
'in' => count($clusters[$k]['TargetingClusterRelation']),
);
if (isset($cluster['Tag']['id'])) {
$tagIds[] = $cluster['Tag']['id'];
$clusters[$k]['GalaxyCluster']['tag_id'] = $cluster['Tag']['id'];
}
$clusters[$k]['GalaxyCluster']['synonyms'] = array();
foreach ($cluster['GalaxyElement'] as $element) {
$clusters[$k]['GalaxyCluster']['synonyms'][] = $element['value'];
}
$clusters[$k]['GalaxyCluster']['event_count'] = 0; // real number is assigned later
}
$eventCountsForTags = $this->GalaxyCluster->Tag->EventTag->countForTags($tagIds, $this->Auth->user());
$this->loadModel('Sighting');
$csvForTags = $this->Sighting->tagsSparkline($tagIds, $this->Auth->user(), '0');
foreach ($clusters as $k => $cluster) {
if (isset($cluster['GalaxyCluster']['tag_id'])) {
if (isset($csvForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['csv'] = $csvForTags[$cluster['GalaxyCluster']['tag_id']];
}
if (isset($eventCountsForTags[$cluster['GalaxyCluster']['tag_id']])) {
$clusters[$k]['GalaxyCluster']['event_count'] = $eventCountsForTags[$cluster['GalaxyCluster']['tag_id']];
}
}
}
$customClusterCount = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), [
'count' => true,
'conditions' => [
'AND' => [$searchConditions, $aclConditions],
'GalaxyCluster.default' => 0,
]
]);
$this->loadModel('Attribute');
$distributionLevels = $this->Attribute->distributionLevels;
unset($distributionLevels[5]);
$this->set('distributionLevels', $distributionLevels);
$this->set('list', $clusters);
$this->set('galaxy_id', $galaxyId);
$this->set('custom_cluster_count', $customClusterCount);
if ($this->request->is('ajax')) {
$this->layout = 'ajax';
$this->render('ajax/index');
@ -179,7 +181,9 @@ class GalaxyClustersController extends AppController
if ($this->_isRest()) {
return $this->RestResponse->viewData($cluster, $this->response->type());
} else {
$cluster = $this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $cluster);
$clusters = [$cluster];
$this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
$cluster = $clusters[0];
$cluster = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $cluster);
$this->set('id', $id);
$this->set('galaxy', ['Galaxy' => $cluster['GalaxyCluster']['Galaxy']]);

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

@ -1294,24 +1294,22 @@ class UsersController extends AppController
public function resetauthkey($id = null, $alert = false)
{
if (!$this->_isAdmin() && Configure::read('MISP.disableUserSelfManagement')) {
throw new MethodNotAllowedException('User self-management has been disabled on this instance.');
}
if (!$this->request->is('post') && !$this->request->is('put')) {
throw new MethodNotAllowedException(__('This functionality is only accessible via POST requests.'));
}
if ($id == 'me') {
if ($id === 'me') {
$id = $this->Auth->user('id');
// Reset just current auth key
$keyId = isset($this->Auth->user()['authkey_id']) ? $this->Auth->user()['authkey_id'] : null;
} else {
$keyId = null;
}
if (!$this->userRole['perm_auth']) {
throw new MethodNotAllowedException(__('Invalid action.'));
}
$newkey = $this->User->resetauthkey($this->Auth->user(), $id, $alert);
$newkey = $this->User->resetauthkey($this->Auth->user(), $id, $alert, $keyId);
if ($newkey === false) {
throw new MethodNotAllowedException(__('Invalid user.'));
}
if (!$this->_isRest()) {
$this->Flash->success(__('New authkey generated.', true));
$this->Flash->success(__('New authkey generated.'));
$this->redirect($this->referer());
} else {
return $this->RestResponse->saveSuccessResponse('User', 'resetauthkey', $id, $this->response->type(), 'Authkey updated: ' . $newkey);

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(
@ -1568,6 +1568,13 @@ class AppModel extends Model
INDEX `value` (`value`(255))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
break;
case 66:
$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;';
@ -1821,18 +1828,19 @@ class AppModel extends Model
}
}
private function __addIndex($table, $field, $length = false)
private function __addIndex($table, $field, $length = null, $unique = false)
{
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
$dataSource = $dataSourceConfig['datasource'];
$this->Log = ClassRegistry::init('Log');
$index = $unique ? 'UNIQUE INDEX' : 'INDEX';
if ($dataSource == 'Database/Postgres') {
$addIndex = "CREATE INDEX idx_" . $table . "_" . $field . " ON " . $table . " (" . $field . ");";
$addIndex = "CREATE $index idx_" . $table . "_" . $field . " ON " . $table . " (" . $field . ");";
} else {
if (!$length) {
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`);";
$addIndex = "ALTER TABLE `" . $table . "` ADD $index `" . $field . "` (`" . $field . "`);";
} else {
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`(" . $length . "));";
$addIndex = "ALTER TABLE `" . $table . "` ADD $index `" . $field . "` (`" . $field . "`(" . $length . "));";
}
}
$result = true;
@ -1841,7 +1849,7 @@ class AppModel extends Model
try {
$this->query($addIndex);
} catch (Exception $e) {
$duplicate = (strpos($e->getMessage(), '1061') !== false);
$duplicate = strpos($e->getMessage(), '1061') !== false;
$errorMessage = $e->getMessage();
$result = false;
}

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;
}
@ -97,26 +142,67 @@ class AuthKey extends AppModel
return false;
}
public function resetauthkey($id)
/**
* @param int $userId
* @param int|null $keyId
* @return false|string
* @throws Exception
*/
public function resetAuthKey($userId, $keyId = null)
{
$existing_authkeys = $this->find('all', [
'recursive' => -1,
'conditions' => [
'user_id' => $id
]
]);
foreach ($existing_authkeys as $key) {
$key['AuthKey']['expiration'] = time();
$this->save($key);
$time = time();
if ($keyId) {
$currentAuthkey = $this->find('first', [
'recursive' => -1,
'conditions' => [
'id' => $keyId,
'user_id' => $userId,
],
]);
if (empty($currentAuthkey)) {
throw new RuntimeException("Key with ID $keyId for user with ID $userId not found.");
}
$currentAuthkey['AuthKey']['expiration'] = $time;
if (!$this->save($currentAuthkey)) {
throw new RuntimeException("Key with ID $keyId could not be saved.");
}
$comment = __("Created by resetting auth key %s\n%s", $keyId, $currentAuthkey['AuthKey']['comment']);
$allowedIps = isset($currentAuthkey['AuthKey']['allowed_ips']) ? $currentAuthkey['AuthKey']['allowed_ips'] : [];
return $this->createnewkey($userId, $comment, $allowedIps);
} else {
$existingAuthkeys = $this->find('all', [
'recursive' => -1,
'conditions' => [
'OR' => [
'expiration >' => $time,
'expiration' => 0
],
'user_id' => $userId
]
]);
foreach ($existingAuthkeys as $key) {
$key['AuthKey']['expiration'] = $time;
$this->save($key);
}
return $this->createnewkey($userId);
}
return $this->createnewkey($id);
}
public function createnewkey($id)
/**
* @param int $userId
* @param string $comment
* @param array $allowedIps
* @return false|string
* @throws Exception
*/
public function createnewkey($userId, $comment = '', array $allowedIps = [])
{
$newKey = [
'authkey' => (new RandomTool())->random_str(true, 40),
'user_id' => $id
'user_id' => $userId,
'comment' => $comment,
'allowed_ips' => empty($allowedIps) ? null : $allowedIps,
];
$this->create();
if ($this->save($newKey)) {
@ -175,6 +261,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

@ -1226,7 +1226,7 @@ class Feed extends AppModel
$md5Values = array_map('md5', array_column($values, 'value'));
$redis->del('misp:feed_cache:' . $feedId);
foreach (array_chunk($md5Values, 1000) as $k => $chunk) {
foreach (array_chunk($md5Values, 5000) as $k => $chunk) {
$pipe = $redis->multi(Redis::PIPELINE);
if (method_exists($redis, 'sAddArray')) {
$redis->sAddArray('misp:feed_cache:' . $feedId, $chunk);
@ -1238,7 +1238,7 @@ class Feed extends AppModel
}
}
$pipe->exec();
$this->jobProgress($jobId, __('Feed %s: %s/%s values cached.', $feedId, $k * 1000, count($md5Values)));
$this->jobProgress($jobId, __('Feed %s: %s/%s values cached.', $feedId, $k * 5000, count($md5Values)));
}
$redis->set('misp:feed_cache_timestamp:' . $feedId, time());
return true;

View File

@ -832,11 +832,30 @@ class GalaxyCluster extends AppModel
return $element;
}
public function attachExtendByInfo($user, $cluster)
/**
* @param array $user
* @param array $clusters
* @return void
*/
public function attachExtendByInfo(array $user, array &$clusters)
{
$extensions = $this->fetchGalaxyClusters($user, array('conditions' => array('extends_uuid' => $cluster['GalaxyCluster']['uuid'])));
$cluster['GalaxyCluster']['extended_by'] = $extensions;
return $cluster;
if (empty($clusters)) {
return;
}
$clusterUuids = array_column(array_column($clusters, 'GalaxyCluster'), 'uuid');
$extensions = $this->fetchGalaxyClusters($user, [
'conditions' => ['extends_uuid' => $clusterUuids],
]);
foreach ($clusters as &$cluster) {
$extendedBy = [];
foreach ($extensions as $extension) {
if ($cluster['GalaxyCluster']['uuid'] === $extension['GalaxyCluster']['extends_uuid']) {
$extendedBy[] = $extension;
}
}
$cluster['GalaxyCluster']['extended_by'] = $extendedBy;
}
}
public function attachExtendFromInfo($user, $cluster)
@ -896,7 +915,7 @@ class GalaxyCluster extends AppModel
if (!$isGalaxyTag) {
return null;
}
$conditions = array('LOWER(GalaxyCluster.tag_name)' => strtolower($name));
$conditions = array('GalaxyCluster.tag_name' => $name);
}
$cluster = $this->fetchGalaxyClusters($user, array(
'conditions' => $conditions,
@ -924,7 +943,7 @@ class GalaxyCluster extends AppModel
if (count(array_filter($namesOrIds, 'is_numeric')) === count($namesOrIds)) { // all elements are numeric
$conditions = array('GalaxyCluster.id' => $namesOrIds);
} else {
$conditions = array('LOWER(GalaxyCluster.tag_name)' => array_map('strtolower', $namesOrIds));
$conditions = array('GalaxyCluster.tag_name' => $namesOrIds);
}
$options = ['conditions' => $conditions];
@ -1451,7 +1470,7 @@ class GalaxyCluster extends AppModel
foreach ($events as $event) {
foreach ($event['EventTag'] as $eventTag) {
if ($eventTag['Tag']['is_galaxy']) {
$clusterTagNames[$eventTag['Tag']['id']] = strtolower($eventTag['Tag']['name']);
$clusterTagNames[$eventTag['Tag']['id']] = $eventTag['Tag']['name'];
}
}
}
@ -1461,7 +1480,7 @@ class GalaxyCluster extends AppModel
}
$options = [
'conditions' => ['LOWER(GalaxyCluster.tag_name)' => $clusterTagNames],
'conditions' => ['GalaxyCluster.tag_name' => $clusterTagNames],
'contain' => ['Galaxy', 'GalaxyElement'],
];
$clusters = $this->fetchGalaxyClusters($user, $options);

View File

@ -92,7 +92,7 @@ class Job extends AppModel
}
}
try {
if ($this->save($jobData)) {
if ($this->save($jobData, ['atomic' => false])) {
return true;
}
$this->log("Could not save progress for job $jobId because of validation errors: " . json_encode($this->validationErrors), LOG_NOTICE);

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

@ -4,6 +4,7 @@ App::uses('GpgTool', 'Tools');
/**
* @property-read array $serverSettings
* @property Organisation $Organisation
*/
class Server extends AppModel
{
@ -1259,16 +1260,23 @@ class Server extends AppModel
return true;
}
/**
* @return array
*/
public function getCurrentServerSettings()
{
$this->Module = ClassRegistry::init('Module');
$serverSettings = $this->serverSettings;
$moduleTypes = array('Enrichment', 'Import', 'Export', 'Cortex');
$serverSettings = $this->readModuleSettings($serverSettings, $moduleTypes);
return $serverSettings;
}
private function readModuleSettings($serverSettings, $moduleTypes)
/**
* @param array $serverSettings
* @param array $moduleTypes
* @return array
*/
private function readModuleSettings(array $serverSettings, array $moduleTypes)
{
$this->Module = ClassRegistry::init('Module');
foreach ($moduleTypes as $moduleType) {
@ -1277,12 +1285,12 @@ class Server extends AppModel
foreach ($results as $module => $data) {
foreach ($data as $result) {
$setting = array('level' => 1, 'errorMessage' => '');
if ($result['type'] == 'boolean') {
if ($result['type'] === 'boolean') {
$setting['test'] = 'testBool';
$setting['type'] = 'boolean';
$setting['description'] = __('Enable or disable the %s module.', $module);
$setting['value'] = false;
} elseif ($result['type'] == 'orgs') {
} elseif ($result['type'] === 'orgs') {
$setting['description'] = __('Restrict the %s module to the given organisation.', $module);
$setting['value'] = 0;
$setting['test'] = 'testLocalOrg';
@ -1359,15 +1367,11 @@ class Server extends AppModel
public function serverSettingsRead($unsorted = false)
{
$this->Module = ClassRegistry::init('Module');
$serverSettings = $this->getCurrentServerSettings();
$currentSettings = Configure::read();
if (Configure::read('Plugin.Enrichment_services_enable')) {
$this->readModuleSettings($serverSettings, array('Enrichment'));
}
$finalSettingsUnsorted = $this->__serverSettingsRead($serverSettings, $currentSettings);
foreach ($finalSettingsUnsorted as $key => $temp) {
if (in_array($temp['tab'], array_keys($this->__settingTabMergeRules))) {
if (isset($this->__settingTabMergeRules[$temp['tab']])) {
$finalSettingsUnsorted[$key]['tab'] = $this->__settingTabMergeRules[$temp['tab']];
}
}

View File

@ -1106,7 +1106,7 @@ class User extends AppModel
return $results;
}
public function resetauthkey($user, $id, $alert = false)
public function resetauthkey($user, $id, $alert = false, $keyId = null)
{
$this->id = $id;
if (!$id || !$this->exists($id)) {
@ -1123,8 +1123,7 @@ class User extends AppModel
$this->extralog(
$user,
'reset_auth_key',
sprintf(
__('Authentication key for user %s (%s) updated.'),
__('Authentication key for user %s (%s) updated.',
$updatedUser['User']['id'],
$updatedUser['User']['email']
),
@ -1133,7 +1132,7 @@ class User extends AppModel
);
} else {
$this->AuthKey = ClassRegistry::init('AuthKey');
$newkey = $this->AuthKey->resetauthkey($id);
$newkey = $this->AuthKey->resetAuthKey($id, $keyId);
}
if ($alert) {
$baseurl = Configure::read('MISP.external_baseurl');

View File

@ -72,9 +72,9 @@ class OidcAuthenticate extends BaseAuthenticate
}
if ($user['role_id'] != $roleId) {
$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;
}
$this->log($mispUsername, 'Logged in.');
@ -182,20 +182,18 @@ class OidcAuthenticate extends BaseAuthenticate
]);
$roleNameToId = array_change_key_case($roleNameToId); // normalize role names to lowercase
$userRole = null;
foreach ($roles as $role) {
if (isset($roleMapper[$role])) {
$roleId = $roleMapper[$role];
if (!is_numeric($roleId)) {
$roleId = mb_strtolower($roleId);
if (isset($roleNameToId[$roleId])) {
$roleId = $roleNameToId[$roleId];
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 `$roleId` not found, skipping.");
$this->log($mispUsername, "MISP Role with name `$mispRole` not found, skipping.");
continue;
}
}
return $roleId; // first match wins
return $mispRole; // first match wins
}
}

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

@ -526,4 +526,3 @@
</ul>
</div>
</div>
<input type="hidden" class="keyboardShortcutsConfig" value="/shortcuts/global_menu.json" />

View File

@ -127,6 +127,5 @@
echo $this->Html->css('distribution-graph');
echo $this->Html->script('network-distribution-graph');
?>
<input type="hidden" class="keyboardShortcutsConfig" value="/shortcuts/event_index.json" />
<?php
if (!$ajax) echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'event-collection', 'menuItem' => 'index'));

View File

@ -580,4 +580,3 @@ $(function () {
});
});
</script>
<input type="hidden" value="/shortcuts/event_view.json" class="keyboardShortcutsConfig" />

View File

@ -1,85 +1,91 @@
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster'));
$extendedFromHtml = '';
if (!empty($cluster['GalaxyCluster']['extended_from'])) {
$element = $this->element('genericElements/IndexTable/Fields/links', array(
'url' => $baseurl . '/galaxy_clusters/view/',
'row' => $cluster,
'field' => array(
'data_path' => 'GalaxyCluster.extended_from.GalaxyCluster.id',
'title' => sprintf(__('%s (version: %s)'), $cluster['GalaxyCluster']['extended_from']['GalaxyCluster']['value'], $cluster['GalaxyCluster']['extends_version'])
),
));
$extendedFromHtml = sprintf('<ul><li>%s</li></ul>', $element);
}
if ($newVersionAvailable) {
$extendedFromHtml .= sprintf('<div class="alert alert-warning">%s</div>', sprintf(__('New version available! <a href="%s">Update cluster to version <b>%s</b></a>'),
'/galaxy_clusters/updateCluster/' . $cluster['GalaxyCluster']['id'],
h($cluster['GalaxyCluster']['extended_from']['GalaxyCluster']['version'])
));
}
$extendedByHtml = '';
$extendByLinks = array();
foreach ($cluster['GalaxyCluster']['extended_by'] as $extendCluster) {
$element = $this->element('genericElements/IndexTable/Fields/links', array(
'url' => '/galaxy_clusters/view/',
'row' => $extendCluster,
'field' => array(
'data_path' => 'GalaxyCluster.id',
'title' => sprintf(__('%s (parent version: %s)'), $extendCluster['GalaxyCluster']['value'], $extendCluster['GalaxyCluster']['extends_version'])
),
));
$extendByLinks[] = sprintf('<li>%s</li>', $element);
}
if (!empty($extendByLinks)) {
$extendedByHtml = sprintf('<ul>%s</ul>', implode('', $extendByLinks));
}
$table_data = array();
$table_data[] = array('key' => __('Cluster ID'), 'value' => $cluster['GalaxyCluster']['id']);
$table_data[] = array('key' => __('Name'), 'value' => $cluster['GalaxyCluster']['value']);
$table_data[] = array('key' => __('Parent Galaxy'), 'value' => $cluster['GalaxyCluster']['Galaxy']['name'] ? $cluster['GalaxyCluster']['Galaxy']['name'] : $cluster['GalaxyCluster']['Galaxy']['type']);
$table_data[] = array('key' => __('Description'), 'value' => $cluster['GalaxyCluster']['description']);
$table_data[] = array('key' => __('Published'), 'boolean' => $cluster['GalaxyCluster']['published'], 'class' => (!$cluster['GalaxyCluster']['published'] ? 'background-red bold': ''));
$table_data[] = array('key' => __('Default'), 'boolean' => $cluster['GalaxyCluster']['default'], 'class' => (!$cluster['GalaxyCluster']['published'] ? 'black': 'black'));
$table_data[] = array('key' => __('Version'), 'value' => $cluster['GalaxyCluster']['version']);
$table_data[] = array('key' => __('UUID'), 'value' => $cluster['GalaxyCluster']['uuid'], 'value_class' => 'quickSelect');
$table_data[] = array('key' => __('Collection UUID'), 'value' => $cluster['GalaxyCluster']['collection_uuid'], 'value_class' => 'quickSelect');
$table_data[] = array(
'key' => __('Source'),
'html' => filter_var($cluster['GalaxyCluster']['source'], FILTER_VALIDATE_URL) ?
'<a href="' . $cluster['GalaxyCluster']['source'] . '" rel="noreferrer noopener">' . h($cluster['GalaxyCluster']['source']) :
h($cluster['GalaxyCluster']['source']),
);
$table_data[] = array('key' => __('Authors'), 'value' => !empty($cluster['GalaxyCluster']['authors']) ? implode(', ', $cluster['GalaxyCluster']['authors']) : __('N/A'));
$table_data[] = array('key' => __('Distribution'), 'element' => 'genericElements/IndexTable/Fields/distribution_levels', 'element_params' => array(
'row' => $cluster['GalaxyCluster'],
'field' => array('data_path' => 'distribution')
$extendedFromHtml = '';
if (!empty($cluster['GalaxyCluster']['extended_from'])) {
$element = $this->element('genericElements/IndexTable/Fields/links', array(
'url' => $baseurl . '/galaxy_clusters/view/',
'row' => $cluster,
'field' => array(
'data_path' => 'GalaxyCluster.extended_from.GalaxyCluster.id',
'title' => sprintf(__('%s (version: %s)'), $cluster['GalaxyCluster']['extended_from']['GalaxyCluster']['value'], $cluster['GalaxyCluster']['extends_version'])
),
));
$table_data[] = array(
'key' => __('Owner Organisation'),
'html' => $this->OrgImg->getOrgImg(array('name' => $cluster['GalaxyCluster']['Org']['name'], 'id' => $cluster['GalaxyCluster']['Org']['id'], 'size' => 18), true),
);
$table_data[] = array(
'key' => __('Creator Organisation'),
'html' => $this->OrgImg->getOrgImg(array('name' => $cluster['GalaxyCluster']['Orgc']['name'], 'id' => $cluster['GalaxyCluster']['Orgc']['id'], 'size' => 18), true),
);
$table_data[] = array('key' => __('Connector tag'), 'value' => $cluster['GalaxyCluster']['tag_name']);
$table_data[] = array('key' => __('Events'), 'html' => isset($cluster['GalaxyCluster']['tag_count']) ?
sprintf('<a href="%s">%s</a>',
sprintf('%s/events/index/searchtag:%s', $baseurl, h($cluster['GalaxyCluster']['tag_id'])),
__n('%s event', '%s events', $cluster['GalaxyCluster']['tag_count'], h($cluster['GalaxyCluster']['tag_count']))
):
'<span>0</span>'
);
if (!empty($extendedFromHtml)) {
$table_data[] = array('key' => __('Forked From'), 'html' => $extendedFromHtml);
}
if (!empty($extendedByHtml)) {
$table_data[] = array('key' => __('Forked By'), 'html' => $extendedByHtml);
}
$extendedFromHtml = sprintf('<ul><li>%s</li></ul>', $element);
}
if ($newVersionAvailable) {
$extendedFromHtml .= sprintf('<div class="alert alert-warning">%s</div>', sprintf(__('New version available! <a href="%s">Update cluster to version <b>%s</b></a>'),
'/galaxy_clusters/updateCluster/' . $cluster['GalaxyCluster']['id'],
h($cluster['GalaxyCluster']['extended_from']['GalaxyCluster']['version'])
));
}
$extendedByHtml = '';
$extendByLinks = array();
foreach ($cluster['GalaxyCluster']['extended_by'] as $extendCluster) {
$element = $this->element('genericElements/IndexTable/Fields/links', array(
'url' => '/galaxy_clusters/view/',
'row' => $extendCluster,
'field' => array(
'data_path' => 'GalaxyCluster.id',
'title' => sprintf(__('%s (parent version: %s)'), $extendCluster['GalaxyCluster']['value'], $extendCluster['GalaxyCluster']['extends_version'])
),
));
$extendByLinks[] = sprintf('<li>%s</li>', $element);
}
if (!empty($extendByLinks)) {
$extendedByHtml = sprintf('<ul>%s</ul>', implode('', $extendByLinks));
}
$description = $this->Markdown->cleanup($cluster['GalaxyCluster']['description']);
$table_data = array();
$table_data[] = array('key' => __('Cluster ID'), 'value' => $cluster['GalaxyCluster']['id']);
$table_data[] = array('key' => __('Name'), 'value' => $cluster['GalaxyCluster']['value']);
$table_data[] = array('key' => __('Parent Galaxy'), 'value' => $cluster['GalaxyCluster']['Galaxy']['name'] ? $cluster['GalaxyCluster']['Galaxy']['name'] : $cluster['GalaxyCluster']['Galaxy']['type']);
$table_data[] = array('key' => __('Description'), 'value' => $description, 'value_class' => 'md');
if (!$cluster['GalaxyCluster']['default']) {
$table_data[] = [
'key' => __('Published'),
'boolean' => $cluster['GalaxyCluster']['published'],
'class' => !$cluster['GalaxyCluster']['published'] ? 'background-red bold' : ''
];
}
$table_data[] = array('key' => __('Default'), 'boolean' => $cluster['GalaxyCluster']['default'], 'class' => 'black');
$table_data[] = array('key' => __('Version'), 'value' => $cluster['GalaxyCluster']['version']);
$table_data[] = array('key' => __('UUID'), 'value' => $cluster['GalaxyCluster']['uuid'], 'value_class' => 'quickSelect');
$table_data[] = array('key' => __('Collection UUID'), 'value' => $cluster['GalaxyCluster']['collection_uuid'], 'value_class' => 'quickSelect');
$table_data[] = array(
'key' => __('Source'),
'html' => filter_var($cluster['GalaxyCluster']['source'], FILTER_VALIDATE_URL) ?
'<a href="' . $cluster['GalaxyCluster']['source'] . '" rel="noreferrer noopener">' . h($cluster['GalaxyCluster']['source']) :
h($cluster['GalaxyCluster']['source']),
);
$table_data[] = array('key' => __('Authors'), 'value' => !empty($cluster['GalaxyCluster']['authors']) ? implode(', ', $cluster['GalaxyCluster']['authors']) : __('N/A'));
$table_data[] = array('key' => __('Distribution'), 'element' => 'genericElements/IndexTable/Fields/distribution_levels', 'element_params' => array(
'row' => $cluster['GalaxyCluster'],
'field' => array('data_path' => 'distribution')
));
$table_data[] = array(
'key' => __('Owner Organisation'),
'html' => $this->OrgImg->getOrgImg(array('name' => $cluster['GalaxyCluster']['Org']['name'], 'id' => $cluster['GalaxyCluster']['Org']['id'], 'size' => 18), true),
);
$table_data[] = array(
'key' => __('Creator Organisation'),
'html' => $this->OrgImg->getOrgImg(array('name' => $cluster['GalaxyCluster']['Orgc']['name'], 'id' => $cluster['GalaxyCluster']['Orgc']['id'], 'size' => 18), true),
);
$table_data[] = array('key' => __('Connector tag'), 'value' => $cluster['GalaxyCluster']['tag_name']);
$table_data[] = array('key' => __('Events'), 'html' => isset($cluster['GalaxyCluster']['tag_count']) ?
sprintf('<a href="%s">%s</a>',
sprintf('%s/events/index/searchtag:%s', $baseurl, h($cluster['GalaxyCluster']['tag_id'])),
__n('%s event', '%s events', $cluster['GalaxyCluster']['tag_count'], h($cluster['GalaxyCluster']['tag_count']))
):
'<span>0</span>'
);
if (!empty($extendedFromHtml)) {
$table_data[] = array('key' => __('Forked From'), 'html' => $extendedFromHtml);
}
if (!empty($extendedByHtml)) {
$table_data[] = array('key' => __('Forked By'), 'html' => $extendedByHtml);
}
?>
<div class='view'>
<div class="row-fluid">
@ -98,6 +104,12 @@
</div>
<div id="elements_content"></div>
</div>
<?= $this->element('genericElements/assetLoader', array(
'js' => array(
'markdown-it',
),
));
?>
<script type="text/javascript">
$(function () {
$.get("<?= $baseurl ?>/galaxy_elements/index/<?php echo $cluster['GalaxyCluster']['id']; ?>", function(data) {
@ -110,4 +122,10 @@ $(function () {
$("#relations_container").html(data);
});
});
md = window.markdownit('default');
md.disable(['image'])
var $md = $('.md');
$md.html(md.render($md.text()));
</script>
<?= $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster'));

View File

@ -0,0 +1,27 @@
<?php
App::uses('AppHelper', 'View/Helper');
class MarkdownHelper extends AppHelper
{
/**
* Converts markdown formatted string to text
* @param string $string
* @return string
*/
public function toText($string)
{
$string = $this->cleanup($string);
// Remove markdown style links
$string = preg_replace('/\[([^]]+)]\([^)]+\)/', '$1', $string);
// Remove citations
$string = preg_replace('/\(Citation: [^)]+\)/', '', $string);
return $string;
}
public function cleanup($string)
{
// Remove <code> blocks and replace by ticks
$string = preg_replace('/<code>([^<]+)<\/code>/', '`$1`', $string);
return $string;
}
}

View File

@ -32,7 +32,7 @@
function queueInterval(k, id) {
intervalArray[k] = setInterval(function() {
if (tabIsActive) {
if (!document.hidden) {
$.getJSON('<?php echo $baseurl; ?>/jobs/getGenerateCorrelationProgress/' + id, function(data) {
var x = document.getElementById("bar" + id);
x.style.width = data+"%";

View File

@ -102,7 +102,6 @@
<?php
endif;
?>
var tabIsActive = true;
var baseurl = '<?php echo $baseurl; ?>';
var here = '<?php
if (substr($this->params['action'], 0, 6) === 'admin_') {
@ -111,13 +110,6 @@
echo $baseurl . '/' . h($this->params['controller']) . '/' . h($this->params['action']);
}
?>';
$(document).ready(function(){
$(window).blur(function() {
tabIsActive = false;
});
$(window).focus(function() {
tabIsActive = true;
});
<?php
if (!Configure::read('MISP.disable_auto_logout') and $me):
?>
@ -125,7 +117,6 @@
<?php
endif;
?>
});
</script>
</body>
</html>

View File

@ -33,7 +33,7 @@
));
?>
</head>
<body>
<body data-controller="<?= h($this->params['controller']) ?>" data-action="<?= h($this->params['action']) ?>">
<div id="popover_form" class="ajax_popover_form"></div>
<div id="popover_form_large" class="ajax_popover_form ajax_popover_form_large"></div>
<div id="popover_form_x_large" class="ajax_popover_form ajax_popover_form_x_large"></div>
@ -71,7 +71,8 @@
'bootstrap-datepicker',
'bootstrap-colorpicker',
'misp',
'keyboard-shortcuts'
'keyboard-shortcuts-definition',
'keyboard-shortcuts',
)
));
echo $this->element('footer');
@ -98,7 +99,6 @@
<?php
endif;
?>
var tabIsActive = true;
var baseurl = '<?php echo $baseurl; ?>';
var here = '<?php
if (substr($this->params['action'], 0, 6) === 'admin_') {
@ -107,12 +107,6 @@
echo $baseurl . '/' . h($this->params['controller']) . '/' . h($this->params['action']);
}
?>';
$(function(){
$(window).blur(function() {
tabIsActive = false;
}).focus(function() {
tabIsActive = true;
});
<?php
if (!Configure::read('MISP.disable_auto_logout') && isset($me) && $me):
?>
@ -120,7 +114,6 @@
<?php
endif;
?>
});
</script>
</body>
</html>

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

@ -0,0 +1,61 @@
function getShortcutsDefinition() {
var shortcuts = [
{
"key": "l",
"description": "Go to event list",
"action": function () {
document.location.href = baseurl + '/events/index';
}
},
{
"key": "e",
"description": "Go to add event page",
"action": function () {
document.location.href = baseurl + '/events/add';
}
}
];
var $body = $(document.body);
if ($body.data('controller') === 'events' && $body.data('action') === 'view') {
shortcuts.push({
"key": "t",
"description": "Open the tag selection modal",
"action": function () {
$('.addTagButton').first().click();
}
});
shortcuts.push({
"key": "f",
"description": "Open the freetext import modal",
"action": function () {
$('#freetext-button').click();
}
});
shortcuts.push({
"key": "a",
"description": "Add an attribute",
"action": function () {
$('#create-button').click();
}
});
shortcuts.push({
"key": "s",
"description": "Focus the filter attribute bar",
"action": function () {
$('#quickFilterField').focus();
}
});
}
if ($body.data('controller') === 'events' && $body.data('action') === 'index') {
shortcuts.push({
"key": "s",
"description": "Focus the filter events bar",
"action": function () {
$('#quickFilterField').focus();
}
});
}
return shortcuts;
}

View File

@ -21,13 +21,7 @@ let keyboardShortcutsManager = {
init() {
/* Codacy comment to notify that baseurl is a read-only global variable. */
/* global baseurl */
let shortcutURIs = [];
for(let keyboardShortcutElement of $('.keyboardShortcutsConfig')) {
shortcutURIs.push(keyboardShortcutElement.value);
this.ajaxGet(baseurl + keyboardShortcutElement.value).then((response) => {
this.mapKeyboardShortcuts(JSON.parse(response));
});
}
this.mapKeyboardShortcuts(getShortcutsDefinition());
this.setKeyboardListener();
},
@ -61,7 +55,7 @@ let keyboardShortcutsManager = {
* @param {} config The shortcut JSON list: [{key: string, description: string, action: string(eval-able JS code)}]
*/
mapKeyboardShortcuts(config) {
for(let shortcut of config.shortcuts) {
for(let shortcut of config) {
this.shortcutKeys.set(shortcut.key, shortcut);
}
this.addShortcutListToHTML();
@ -76,39 +70,14 @@ let keyboardShortcutsManager = {
window.onkeyup = (keyboardEvent) => {
if(this.shortcutKeys.has(keyboardEvent.key)) {
let activeElement = document.activeElement.tagName;
if( !this.ESCAPED_TAG_NAMES.includes(activeElement)) {
eval(this.shortcutKeys.get(keyboardEvent.key).action);
if(!this.ESCAPED_TAG_NAMES.includes(activeElement)) {
this.shortcutKeys.get(keyboardEvent.key).action();
}
} else if(this.NAVIGATION_KEYS.includes(keyboardEvent.key)) {
window.dispatchEvent(new CustomEvent(this.EVENTS[keyboardEvent.key], {detail: keyboardEvent}));
}
}
},
/**
* Queries the given URL with a GET request and returns a Promise
* that resolves when the response arrives.
* @param string url The URL to fetch.
*/
ajaxGet(url) {
return new Promise(function(resolve, reject) {
let req = new XMLHttpRequest();
req.open("GET", url);
req.onload = function() {
if (req.status === 200) {
resolve(req.response);
} else {
reject(new Error(req.statusText));
}
};
req.onerror = function() {
reject(new Error("Network error"));
};
req.send();
});
}
}
// Inits the keyboard shortcut manager's main routine and the click event on the keyboard shortcut triangle at the bottom of the screen.

View File

@ -4808,7 +4808,7 @@ $(document.body).on('click', 'a[data-paginator]', function (e) {
});
function queryEventLock(event_id) {
if (tabIsActive) {
if (!document.hidden) {
$.ajax({
url: baseurl + "/events/checkLocks/" + event_id,
type: "get",
@ -4826,7 +4826,7 @@ function queryEventLock(event_id) {
}
function checkIfLoggedIn() {
if (tabIsActive) {
if (!document.hidden) {
$.get(baseurl + "/users/checkIfLoggedIn.json")
.fail(function (xhr) {
if (xhr.status === 403) {
@ -5298,7 +5298,8 @@ function submitGenericFormInPlace() {
$('#genericModal').modal('hide').remove();
$('body').append(data);
$('#genericModal').modal();
}
},
error: xhrFailCallback,
});
}

View File

@ -1,9 +0,0 @@
{
"shortcuts": [
{
"key": "s",
"description": "Focus the filter events bar",
"action": "$('#quickFilterField').focus()"
}
]
}

View File

@ -1,24 +0,0 @@
{
"shortcuts": [
{
"key": "t",
"description": "Open the tag selection modal",
"action": "$('.addTagButton').first().click()"
},
{
"key": "f",
"description": "Open the freetext import modal",
"action": "$('#freetext-button').click()"
},
{
"key": "a",
"description": "Add an attribute",
"action": "$('#create-button').click()"
},
{
"key": "s",
"description": "Focus the filter attribute bar",
"action": "$('#quickFilterField').focus()"
}
]
}

View File

@ -1,15 +0,0 @@
{
"shortcuts": [
{
"key": "l",
"description": "Go to event list",
"action": "document.location.href = baseurl + '/events/index'"
},
{
"key": "e",
"description": "Go to add event page",
"action": "document.location.href = baseurl + '/events/add'"
}
]
}

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": [
@ -2356,7 +2367,7 @@
"data_type": "varchar",
"character_maximum_length": "255",
"numeric_precision": null,
"collation_name": "utf8_bin",
"collation_name": "utf8_unicode_ci",
"column_type": "varchar(255)",
"column_default": "",
"extra": ""
@ -7654,7 +7665,8 @@
"event_reports": {
"id": true,
"uuid": true,
"name": false
"name": false,
"event_id": false
},
"event_tags": {
"id": true,
@ -7974,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)