diff --git a/INSTALL/MYSQL.sql b/INSTALL/MYSQL.sql index 4c0dee91e..284128120 100644 --- a/INSTALL/MYSQL.sql +++ b/INSTALL/MYSQL.sql @@ -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 '', diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php index 7397c2082..68349da20 100755 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -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; + } } diff --git a/app/Controller/AuthKeysController.php b/app/Controller/AuthKeysController.php index b90bb2fa4..47e1fef57 100644 --- a/app/Controller/AuthKeysController.php +++ b/app/Controller/AuthKeysController.php @@ -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) diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index fb398c910..783329be2 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -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('*'), diff --git a/app/Controller/Component/CRUDComponent.php b/app/Controller/Component/CRUDComponent.php index 1e682c197..5a63087c4 100644 --- a/app/Controller/Component/CRUDComponent.php +++ b/app/Controller/Component/CRUDComponent.php @@ -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 { diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 3a64d2f49..829853d94 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -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) diff --git a/app/Controller/GalaxiesController.php b/app/Controller/GalaxiesController.php index 15e609fc6..917a6fe01 100644 --- a/app/Controller/GalaxiesController.php +++ b/app/Controller/GalaxiesController.php @@ -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( diff --git a/app/Controller/GalaxyClustersController.php b/app/Controller/GalaxyClustersController.php index db5ef3108..dcd313c7d 100644 --- a/app/Controller/GalaxyClustersController.php +++ b/app/Controller/GalaxyClustersController.php @@ -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']]); diff --git a/app/Controller/ServersController.php b/app/Controller/ServersController.php index 7e07d5b3d..52a95d388 100644 --- a/app/Controller/ServersController.php +++ b/app/Controller/ServersController.php @@ -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)); diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index 966eb329f..3d3c47d04 100644 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -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); diff --git a/app/Lib/Tools/CidrTool.php b/app/Lib/Tools/CidrTool.php index faf978f3d..b21351c0c 100644 --- a/app/Lib/Tools/CidrTool.php +++ b/app/Lib/Tools/CidrTool.php @@ -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 * diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index 3348e9ecc..3f4e0d216 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.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( @@ -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; } diff --git a/app/Model/AuthKey.php b/app/Model/AuthKey.php index f4711b6b7..5dbfb8ac2 100644 --- a/app/Model/AuthKey.php +++ b/app/Model/AuthKey.php @@ -1,6 +1,7 @@ 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. diff --git a/app/Model/Feed.php b/app/Model/Feed.php index 4d9cdb8af..037bc3dd8 100644 --- a/app/Model/Feed.php +++ b/app/Model/Feed.php @@ -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; diff --git a/app/Model/GalaxyCluster.php b/app/Model/GalaxyCluster.php index 76cf6eeb4..0fb8d4d30 100644 --- a/app/Model/GalaxyCluster.php +++ b/app/Model/GalaxyCluster.php @@ -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); diff --git a/app/Model/Job.php b/app/Model/Job.php index ecb8a846c..9193a2366 100644 --- a/app/Model/Job.php +++ b/app/Model/Job.php @@ -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); diff --git a/app/Model/Log.php b/app/Model/Log.php index 1cf7aee90..19b7aeb64 100644 --- a/app/Model/Log.php +++ b/app/Model/Log.php @@ -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)); } diff --git a/app/Model/Server.php b/app/Model/Server.php index c4c938583..e0dc2c339 100644 --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -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']]; } } diff --git a/app/Model/User.php b/app/Model/User.php index e8f70527d..ca505519c 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -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'); diff --git a/app/Plugin/OidcAuth/Controller/Component/Auth/OidcAuthenticate.php b/app/Plugin/OidcAuth/Controller/Component/Auth/OidcAuthenticate.php index 5031c65c9..4ec5befbe 100644 --- a/app/Plugin/OidcAuth/Controller/Component/Auth/OidcAuthenticate.php +++ b/app/Plugin/OidcAuth/Controller/Component/Auth/OidcAuthenticate.php @@ -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 } } diff --git a/app/View/AuthKeys/add.ctp b/app/View/AuthKeys/add.ctp index 62e5a40db..a3cd94042 100644 --- a/app/View/AuthKeys/add.ctp +++ b/app/View/AuthKeys/add.ctp @@ -1,7 +1,7 @@ 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', diff --git a/app/View/AuthKeys/index.ctp b/app/View/AuthKeys/index.ctp index 127132961..77c3ab45f 100644 --- a/app/View/AuthKeys/index.ctp +++ b/app/View/AuthKeys/index.ctp @@ -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( diff --git a/app/View/AuthKeys/view.ctp b/app/View/AuthKeys/view.ctp index 37f786371..43b79780b 100644 --- a/app/View/AuthKeys/view.ctp +++ b/app/View/AuthKeys/view.ctp @@ -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("
", 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), + ] + ], +]); diff --git a/app/View/Elements/genericElements/IndexTable/Fields/datetime.ctp b/app/View/Elements/genericElements/IndexTable/Fields/datetime.ctp index 34d3f3bbf..b78c98c5a 100644 --- a/app/View/Elements/genericElements/IndexTable/Fields/datetime.ctp +++ b/app/View/Elements/genericElements/IndexTable/Fields/datetime.ctp @@ -1,26 +1,27 @@ 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( - '%s', - $field['onClick'], - $data - ); - } - echo $data; +} +if (!empty($field['onClick'])) { + $data = sprintf( + '%s', + $field['onClick'], + $data + ); +} +echo $data; diff --git a/app/View/Elements/genericElements/SingleViews/Fields/customField.ctp b/app/View/Elements/genericElements/SingleViews/Fields/customField.ctp new file mode 100644 index 000000000..a056047b4 --- /dev/null +++ b/app/View/Elements/genericElements/SingleViews/Fields/customField.ctp @@ -0,0 +1 @@ + - diff --git a/app/View/Events/index.ctp b/app/View/Events/index.ctp index cacdfb804..f54efefbe 100644 --- a/app/View/Events/index.ctp +++ b/app/View/Events/index.ctp @@ -127,6 +127,5 @@ echo $this->Html->css('distribution-graph'); echo $this->Html->script('network-distribution-graph'); ?> - element('/genericElements/SideMenu/side_menu', array('menuList' => 'event-collection', 'menuItem' => 'index')); diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index b52438a09..361adec22 100644 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -580,4 +580,3 @@ $(function () { }); }); - diff --git a/app/View/GalaxyClusters/view.ctp b/app/View/GalaxyClusters/view.ctp index cf91eff31..f7d869b04 100755 --- a/app/View/GalaxyClusters/view.ctp +++ b/app/View/GalaxyClusters/view.ctp @@ -1,85 +1,91 @@ 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('', $element); - } - if ($newVersionAvailable) { - $extendedFromHtml .= sprintf('
%s
', sprintf(__('New version available! Update cluster to version %s'), - '/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('
  • %s
  • ', $element); - } - if (!empty($extendByLinks)) { - $extendedByHtml = sprintf('', 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) ? - '' . 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('%s', - 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'])) - ): - '0' - ); - if (!empty($extendedFromHtml)) { - $table_data[] = array('key' => __('Forked From'), 'html' => $extendedFromHtml); - } - if (!empty($extendedByHtml)) { - $table_data[] = array('key' => __('Forked By'), 'html' => $extendedByHtml); - } + $extendedFromHtml = sprintf('', $element); +} +if ($newVersionAvailable) { + $extendedFromHtml .= sprintf('
    %s
    ', sprintf(__('New version available! Update cluster to version %s'), + '/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('
  • %s
  • ', $element); +} +if (!empty($extendByLinks)) { + $extendedByHtml = sprintf('', 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) ? + '' . 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('%s', + 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'])) + ): + '0' + ); +if (!empty($extendedFromHtml)) { + $table_data[] = array('key' => __('Forked From'), 'html' => $extendedFromHtml); +} +if (!empty($extendedByHtml)) { + $table_data[] = array('key' => __('Forked By'), 'html' => $extendedByHtml); +} ?>
    @@ -98,6 +104,12 @@
    +element('genericElements/assetLoader', array( + 'js' => array( + 'markdown-it', + ), +)); +?> +element('/genericElements/SideMenu/side_menu', array('menuList' => 'galaxies', 'menuItem' => 'view_cluster')); diff --git a/app/View/Helper/MarkdownHelper.php b/app/View/Helper/MarkdownHelper.php new file mode 100644 index 000000000..ae9318428 --- /dev/null +++ b/app/View/Helper/MarkdownHelper.php @@ -0,0 +1,27 @@ +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 blocks and replace by ticks + $string = preg_replace('/([^<]+)<\/code>/', '`$1`', $string); + return $string; + } +} diff --git a/app/View/Jobs/index.ctp b/app/View/Jobs/index.ctp index fd1fd4b30..162e9a22d 100644 --- a/app/View/Jobs/index.ctp +++ b/app/View/Jobs/index.ctp @@ -32,7 +32,7 @@ function queueInterval(k, id) { intervalArray[k] = setInterval(function() { - if (tabIsActive) { + if (!document.hidden) { $.getJSON('/jobs/getGenerateCorrelationProgress/' + id, function(data) { var x = document.getElementById("bar" + id); x.style.width = data+"%"; diff --git a/app/View/Layouts/dashboard.ctp b/app/View/Layouts/dashboard.ctp index 839d0dad8..479080ccb 100644 --- a/app/View/Layouts/dashboard.ctp +++ b/app/View/Layouts/dashboard.ctp @@ -102,7 +102,6 @@ - var tabIsActive = true; var baseurl = ''; var here = '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; - }); @@ -125,7 +117,6 @@ - }); diff --git a/app/View/Layouts/default.ctp b/app/View/Layouts/default.ctp index 3b79002e4..ae7fbd82f 100644 --- a/app/View/Layouts/default.ctp +++ b/app/View/Layouts/default.ctp @@ -33,7 +33,7 @@ )); ?> - +
    @@ -71,7 +71,8 @@ 'bootstrap-datepicker', 'bootstrap-colorpicker', 'misp', - 'keyboard-shortcuts' + 'keyboard-shortcuts-definition', + 'keyboard-shortcuts', ) )); echo $this->element('footer'); @@ -98,7 +99,6 @@ - var tabIsActive = true; var baseurl = ''; var here = '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; - }); @@ -120,7 +114,6 @@ - }); diff --git a/app/webroot/css/main.css b/app/webroot/css/main.css index 0d2b5e537..7a260d31b 100644 --- a/app/webroot/css/main.css +++ b/app/webroot/css/main.css @@ -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, diff --git a/app/webroot/js/keyboard-shortcuts-definition.js b/app/webroot/js/keyboard-shortcuts-definition.js new file mode 100644 index 000000000..bd6acbe3f --- /dev/null +++ b/app/webroot/js/keyboard-shortcuts-definition.js @@ -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; +} diff --git a/app/webroot/js/keyboard-shortcuts.js b/app/webroot/js/keyboard-shortcuts.js index 56db0de2d..f2432e2a9 100644 --- a/app/webroot/js/keyboard-shortcuts.js +++ b/app/webroot/js/keyboard-shortcuts.js @@ -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. diff --git a/app/webroot/js/misp.js b/app/webroot/js/misp.js index 2664d58ad..b0f688361 100644 --- a/app/webroot/js/misp.js +++ b/app/webroot/js/misp.js @@ -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, }); } diff --git a/app/webroot/shortcuts/event_index.json b/app/webroot/shortcuts/event_index.json deleted file mode 100644 index 15e6fa1cb..000000000 --- a/app/webroot/shortcuts/event_index.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "shortcuts": [ - { - "key": "s", - "description": "Focus the filter events bar", - "action": "$('#quickFilterField').focus()" - } - ] -} \ No newline at end of file diff --git a/app/webroot/shortcuts/event_view.json b/app/webroot/shortcuts/event_view.json deleted file mode 100644 index c411c8802..000000000 --- a/app/webroot/shortcuts/event_view.json +++ /dev/null @@ -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()" - } - ] -} diff --git a/app/webroot/shortcuts/global_menu.json b/app/webroot/shortcuts/global_menu.json deleted file mode 100644 index 6ee3ca893..000000000 --- a/app/webroot/shortcuts/global_menu.json +++ /dev/null @@ -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'" - } - ] -} - diff --git a/db_schema.json b/db_schema.json index f2427a0c0..03dbe7b97 100644 --- a/db_schema.json +++ b/db_schema.json @@ -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" } diff --git a/tests/testlive_security.py b/tests/testlive_security.py index 766746b5d..f794c9137 100644 --- a/tests/testlive_security.py +++ b/tests/testlive_security.py @@ -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)