diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index c4d757d8c..502c20680 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -551,6 +551,13 @@ class ACLComponent extends Component 'verifyGPG' => array(), 'view' => array('*'), ), + 'userSettings' => array( + 'index' => array('*'), + 'view' => array('*'), + 'setSetting' => array('*'), + 'getSetting' => array('*'), + 'delete' => array('*') + ), 'warninglists' => array( 'checkValue' => array('perm_auth'), 'delete' => array(), diff --git a/app/Controller/Component/RestResponseComponent.php b/app/Controller/Component/RestResponseComponent.php index 72f87c7c2..25a1652eb 100644 --- a/app/Controller/Component/RestResponseComponent.php +++ b/app/Controller/Component/RestResponseComponent.php @@ -249,6 +249,17 @@ class RestResponseComponent extends Component 'http_method' => 'GET' ) ), + 'UserSetting' => array( + 'setSetting' => array( + 'description' => "POST a User setting object in JSON format to this API to create a new setting or update the equivalent existing setting. Admins/site admins can specify a user ID besides their own.", + 'mandatory' => array('setting', 'value'), + 'optional' => array('user_id') + ), + 'delete' => array( + 'description' => "POST or DELETE to this API to delete an existing setting.", + 'params' => array('id') + ) + ), 'Warninglist' => array( 'checkValue' => array( 'description' => "POST a JSON list with value(s) to check against the warninglists to get a JSON dictionary as a response with any hits, if there are any (with the key being the passed value triggering a warning).", @@ -1548,10 +1559,12 @@ class RestResponseComponent extends Component $fieldsConstraint[$sf]['label'] = $label; } } else { - $fieldsConstraint[$field] = $this->__fieldsConstraint[$field]; - $label = $scope . '.' . $field; - $fieldsConstraint[$field]['id'] = $label; - $fieldsConstraint[$field]['label'] = $label; + if (!empty($this->__fieldsConstraint[$field])) { + $fieldsConstraint[$field] = $this->__fieldsConstraint[$field]; + $label = $scope . '.' . $field; + $fieldsConstraint[$field]['id'] = $label; + $fieldsConstraint[$field]['label'] = $label; + } } // add dynamic data and overwrite name collisions diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 9518b9e1e..0f2f914a3 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -2599,6 +2599,7 @@ class EventsController extends AppController } // send out the email $emailResult = $this->Event->sendAlertEmailRouter($id, $this->Auth->user(), $this->Event->data['Event']['publish_timestamp']); + throw new Exception(); if (is_bool($emailResult) && $emailResult == true) { // Performs all the actions required to publish an event $result = $this->Event->publishRouter($id, null, $this->Auth->user()); diff --git a/app/Controller/UserSettingsController.php b/app/Controller/UserSettingsController.php new file mode 100644 index 000000000..47b728c72 --- /dev/null +++ b/app/Controller/UserSettingsController.php @@ -0,0 +1,333 @@ + 60, + 'maxLimit' => 9999, + 'order' => array( + 'UserSetting.id' => 'DESC' + ), + 'contain' => array( + 'User.id', + 'User.email' + ) + ); + + public function index() + { + $filterData = array( + 'request' => $this->request, + 'paramArray' => array('setting', 'user_id', 'sort', 'direction', 'page', 'limit'), + 'named_params' => $this->params['named'] + ); + $exception = false; + $filters = $this->_harvestParameters($filterData, $exception); + $conditions = array(); + if (!empty($filters['setting'])) { + $conditions['AND'][] = array( + 'setting' => $filters['setting'] + ); + } + if (!empty($filters['user_id'])) { + if ($filters['user_id'] === 'all') { + $context = 'all'; + } else if ($filters['user_id'] === 'me') { + $conditions['AND'][] = array( + 'user_id' => $this->Auth->user('id') + ); + $context = 'me'; + } else if ($filters['user_id'] === 'org') { + $conditions['AND'][] = array( + 'user_id' => $this->UserSetting->User->find( + 'list', array( + 'conditions' => array( + 'User.org_id' => $this->Auth->user('org_id') + ), + 'fields' => array( + 'User.id', 'User.id' + ) + ) + ) + ); + $context = 'org'; + } else { + $conditions['AND'][] = array( + 'user_id' => $filters['user_id'] + ); + } + } + if (!$this->_isSiteAdmin()) { + if ($this->_isAdmin()) { + $conditions['AND'][] = array( + 'UserSetting.user_id' => $this->UserSetting->User->find( + 'list', array( + 'conditions' => array( + 'User.org_id' => $this->Auth->user('org_id') + ), + 'fields' => array( + 'User.id', 'User.id' + ) + ) + ) + ); + } else { + $conditions['AND'][] = array( + 'UserSetting.user_id' => $this->Auth->user('id') + ); + } + } + if ($this->_isRest()) { + $params = array( + 'conditions' => $conditions + ); + if (!empty($filters['page'])) { + $params['page'] = $filters['page']; + $params['limit'] = $this->paginate['limit']; + } + if (!empty($filters['limit'])) { + $params['limit'] = $filters['limit']; + } + $userSettings = $this->UserSetting->find('all', $params); + return $this->RestResponse->viewData($userSettings, $this->response->type()); + } else { + $this->paginate['conditions'] = $conditions; + $this->set('data', $this->paginate()); + $this->set('context', empty($context) ? 'null' : $context); + } + } + + public function view($id) + { + // check if the ID is valid and whether a user setting with the given ID exists + if (empty($id) || !is_numeric($id)) { + throw new InvalidArgumentException(__('Invalid ID passed.')); + } + $userSetting = $this->UserSetting->find('first', array( + 'recursive' => -1, + 'conditions' => array( + 'UserSetting.id' => $id + ), + 'contain' => array('User.id', 'User.org_id') + )); + if (empty($userSetting)) { + throw new NotFoundException(__('Invalid user setting.')); + } + $checkAccess = $this->UserSetting->checkAccess($this->Auth->user(), $userSetting); + if (!$checkAccess) { + throw new NotFoundException(__('Invalid user setting.')); + } + if ($this->_isRest()) { + unset($userSetting['User']); + return $this->RestResponse->viewData($userSetting, $this->response->type()); + } else { + $this->set($data, $userSetting); + } + } + + public function setSetting($user_id = false, $setting = false) + { + // handle POST requests + if ($this->request->is('post')) { + // massage the request to allow for unencapsulated POST requests via the API + // {"key": "value"} instead of {"UserSetting": {"key": "value"}} + if (empty($this->request->data['UserSetting'])) { + $this->request->data = array('UserSetting' => $this->request->data); + } + if (!empty($user_id)) { + $this->request->data['UserSetting']['user_id'] = $user_id; + } + if (!empty($setting)) { + $this->request->data['UserSetting']['setting'] = $setting; + } + // force our user's ID as the user ID in all cases + $userSetting = array( + 'user_id' => $this->Auth->user('id') + ); + if (!empty($this->request->data['UserSetting']['user_id']) && is_numeric($this->request->data['UserSetting']['user_id'])) { + $user = $this->UserSetting->User->find('first', array( + 'recursive' => -1, + 'conditions' => array('User.id' => $this->request->data['UserSetting']['user_id']), + 'fields' => array('User.org_id') + )); + if ( + $this->_isSiteAdmin() || + ($this->_isAdmin() && ($user['User']['org_id'] == $this->Auth->user('org_id'))) + ) { + $userSetting['user_id'] = $this->request->data['UserSetting']['user_id']; + } + } + if (empty($this->request->data['UserSetting']['setting'])) { + throw new MethodNotAllowedException(__('This endpoint expects both a setting and a value to be set.')); + } else { + if (!$this->UserSetting->checkSettingValidity($this->request->data['UserSetting']['setting'])) { + throw new MethodNotAllowedException(__('Invalid setting.')); + } + $userSetting['setting'] = $this->request->data['UserSetting']['setting']; + } + $userSetting['value'] = empty($this->request->data['UserSetting']['value']) ? '' : + json_encode(json_decode($this->request->data['UserSetting']['value'], true)); + $existingSetting = $this->UserSetting->find('first', array( + 'recursive' => -1, + 'conditions' => array( + 'UserSetting.user_id' => $userSetting['user_id'], + 'UserSetting.setting' => $userSetting['setting'] + ) + )); + if (empty($existingSetting)) { + $this->UserSetting->create(); + } else { + $userSetting['id'] = $existingSetting['UserSetting']['id']; + } + // save the setting + $result = $this->UserSetting->save(array('UserSetting' => $userSetting)); + if ($result) { + // if we've managed to save our setting + if ($this->_isRest()) { + // if we are dealing with an API request + $userSetting = $this->UserSetting->find('first', array( + 'recursive' => -1, + 'conditions' => array('UserSetting.id' => $this->UserSetting->id) + )); + return $this->RestResponse->viewData($userSetting, $this->response->type()); + } else { + // if we are dealing with a UI request, redirect the user to the user view with the proper flash message + $this->Flash->success(__('Setting saved.')); + $this->redirect(array('controller' => 'user_settings', 'action' => 'index', $this->Auth->User('id'))); + } + } else { + // if we've failed saving our setting + if ($this->_isRest()) { + // if we are dealing with an API request + return $this->RestResponse->saveFailResponse('UserSettings', 'add', false, $this->UserSetting->validationErrors, $this->response->type()); + } else { + /* + * if we are dealing with a UI request, simply set an error in a flash message + * and render the view of this endpoint, pre-populated with the submitted values. + */ + $this->Flash->error(__('Setting could not be saved.')); + } + } + } + if ($this->_isRest()) { + // GET request via the API should describe the endpoint + return $this->RestResponse->describe('UserSettings', 'setSetting', false, $this->response->type()); + } else { + // load the valid settings from the model + $validSettings = $this->UserSetting->validSettings; + if ($this->_isSiteAdmin()) { + $users = $this->UserSetting->User->find('list', array( + 'recursive' => -1, + 'fields' => array('User.id', 'User.email') + )); + } else if ($this->_isAdmin()) { + $users = $this->UserSetting->User->find('list', array( + 'recursive' => -1, + 'conditions' => array('User.org_id' => $this->Auth->user('org_id')), + 'fields' => array('User.id', 'User.email') + )); + } else { + $users = array($this->Auth->user('id') => $this->Auth->user('email')); + } + if (!empty($user_id) && $this->request->is('get')) { + $this->request->data['UserSetting']['user_id'] = $user_id; + } + $this->set('users', $users); + $this->set('validSettings', $validSettings); + } + } + + public function getSetting($user_id, $setting) + { + if (!$this->UserSetting->checkSettingValidity($setting)) { + throw new MethodNotAllowedException(__('Invalid setting.')); + } + $userSetting = $this->UserSetting->find('first', array( + 'recursive' => -1, + 'conditions' => array( + 'UserSetting.user_id' => $user_id, + 'UserSetting.setting' => $setting + ), + 'contain' => array('User.id', 'User.org_id') + )); + $checkAccess = $this->UserSetting->checkAccess($this->Auth->user(), $userSetting, $user_id); + if (empty($checkAccess)) { + throw new MethodNotAllowedException(__('Invalid setting.')); + } + if (!empty($userSetting)) { + $userSetting = json_encode($userSetting['UserSetting']['value']); + } else { + $userSetting = '[]'; + } + return $this->RestResponse->viewData($userSetting, $this->response->type(), false, true); + } + + public function delete($id = false) + { + if ($this->request->is('get') && $this->_isRest()) { + /* + * GET request via the API should describe the endpoint + * Unlike with the add() endpoint, we want to run this check before doing anything else, + * in order to allow us to reach this endpoint without passing a valid ID + */ + return $this->RestResponse->describe('UserSettings', 'delete', false, $this->response->type()); + } + // check if the ID is valid and whether a user setting with the given ID exists + if (empty($id) || !is_numeric($id)) { + throw new InvalidArgumentException(__('Invalid ID passed.')); + } + $userSetting = $this->UserSetting->find('first', array( + 'recursive' => -1, + 'conditions' => array( + 'UserSetting.id' => $id + ), + 'contain' => array('User.id', 'User.org_id') + )); + if (empty($userSetting)) { + throw new NotFoundException(__('Invalid user setting.')); + } + $checkAccess = $this->UserSetting->checkAccess($this->Auth->user(), $userSetting); + if (!$checkAccess) { + throw new NotFoundException(__('Invalid user setting.')); + } + if ($this->request->is('post') || $this->request->is('delete')) { + // Delete the setting that we were after. + $result = $this->UserSetting->delete($userSetting['UserSetting']['id']); + if ($result) { + // set the response for both the UI and API + $message = __('Setting deleted.'); + if ($this->_isRest()) { + return $this->RestResponse->saveSuccessResponse('UserSettings', 'delete', $id, $this->response->type(), $message); + } else { + $this->Flash->success($message); + } + } else { + // set the response for both the UI and API + $message = __('Setting could not be deleted.'); + if ($this->_isRest()) { + return $this->RestResponse->saveFailResponse('UserSettings', 'delete', $id, $message, $this->response->type()); + } else { + $this->Flash->error($message); + } + } + /* + * The API responses stopped executing this function and returned a serialised response to the user. + * For UI users, redirect to where they issued the request from. + */ + $this->redirect($this->referer()); + } else { + throw new MethodNotAllowedException(__('Expecting POST or DELETE request.')); + } + } +} diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index 1c3955791..5d78b80cd 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -75,7 +75,8 @@ class AppModel extends Model 13 => false, 14 => false, 15 => false, 18 => false, 19 => false, 20 => false, 21 => false, 22 => false, 23 => false, 24 => false, 25 => false, 26 => false, 27 => false, 28 => false, 29 => false, 30 => false, 31 => false, 32 => false, - 33 => false, 34 => false, 35 => false, 36 => false, 37 => false, 38 => false + 33 => false, 34 => false, 35 => false, 36 => false, 37 => false, 38 => false, + 39 => false ); public $advanced_updates_description = array( @@ -1246,6 +1247,19 @@ class AppModel extends Model $sqlArray[] = "ALTER TABLE servers ADD priority int(11) NOT NULL DEFAULT 0;"; $indexArray[] = array('servers', 'priority'); break; + case 39: + $sqlArray[] = "CREATE TABLE IF NOT EXISTS user_settings ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `key` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL, + `value` text, + `user_id` int(11) NOT NULL, + `timestamp` int(11) NOT NULL, + PRIMARY KEY (id), + INDEX `key` (`key`), + INDEX `user_id` (`user_id`), + INDEX `timestamp` (`timestamp`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"; + 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;'; @@ -1514,6 +1528,17 @@ class AppModel extends Model return ucfirst($field) . ' cannot be empty.'; } + public function valueIsJson($value) + { + $field = array_keys($value); + $field = $field[0]; + $json_decoded = json_decode($value[$field]); + if ($json_decoded === null) { + return __('Invalid JSON.'); + } + return true; + } + public function valueIsID($value) { $field = array_keys($value); diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index 70bcde067..ee4336d9a 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -799,7 +799,6 @@ class Attribute extends AppModel $this->complexTypeTool = new ComplexTypeTool(); $this->data['Attribute']['value'] = $this->complexTypeTool->refangValue($this->data['Attribute']['value'], $this->data['Attribute']['type']); - if (!empty($this->data['Attribute']['object_id']) && empty($this->data['Attribute']['object_relation'])) { return false; } diff --git a/app/Model/Event.php b/app/Model/Event.php index 112a21334..dcda9692c 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -2980,10 +2980,13 @@ class Event extends AppModel $sgModel = ClassRegistry::init('SharingGroup'); $userCount = count($users); + $this->UserSetting = ClassRegistry::init('UserSetting'); foreach ($users as $k => $user) { - $body = $this->__buildAlertEmailBody($event[0], $user, $oldpublish, $sgModel); - $bodyNoEnc = "A new or modified event was just published on " . $this->__getAnnounceBaseurl() . "/events/view/" . $event[0]['Event']['id']; - $this->User->sendEmail(array('User' => $user), $body, $bodyNoEnc, $subject); + if ($this->UserSetting->checkPublishFilter($user, $event[0])) { + $body = $this->__buildAlertEmailBody($event[0], $user, $oldpublish, $sgModel); + $bodyNoEnc = "A new or modified event was just published on " . $this->__getAnnounceBaseurl() . "/events/view/" . $event[0]['Event']['id']; + $this->User->sendEmail(array('User' => $user), $body, $bodyNoEnc, $subject); + } if ($processId) { $this->Job->id = $processId; $this->Job->saveField('progress', $k / $userCount * 100); diff --git a/app/Model/User.php b/app/Model/User.php index 851821ab1..3867c8c2d 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -202,7 +202,8 @@ class User extends AppModel 'finderQuery' => '', 'counterQuery' => '' ), - 'Post' + 'Post', + 'UserSetting' ); public $actsAs = array( @@ -605,7 +606,7 @@ class User extends AppModel public function getAuthUser($id) { if (empty($id)) { - throw new Exception('Invalid user ID.'); + throw new NotFoundException('Invalid user ID.'); } $conditions = array('User.id' => $id); $user = $this->find('first', array('conditions' => $conditions, 'recursive' => -1,'contain' => array('Organisation', 'Role', 'Server'))); diff --git a/app/Model/UserSetting.php b/app/Model/UserSetting.php new file mode 100644 index 000000000..6139804e3 --- /dev/null +++ b/app/Model/UserSetting.php @@ -0,0 +1,252 @@ + array( + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full'), + 'Containable', + ); + + public $validate = array( + 'json' => array( + 'isValidJson' => array( + 'rule' => array('isValidJson'), + ) + ) + ); + + public $belongsTo = array( + 'User' + ); + + public $validSettings = array( + 'publish_alert_filter' => array( + 'placeholder' => array( + 'AND' => array( + 'NOT' => array( + 'EventTag.name' => array( + '%osint%' + ) + ), + 'OR' => array( + 'Tag.name' => array( + 'tlp:green', + 'tlp:amber', + 'tlp:red', + '%privint%' + ) + ) + ) + ) + ) + ); + + // massage the data before we send it off for validation before saving anything + public function beforeValidate($options = array()) + { + parent::beforeValidate(); + // add a timestamp if it is not set + if (empty($this->data['UserSetting']['timestamp'])) { + $this->data['UserSetting']['timestamp'] = time(); + } + if (!empty($this->data['UserSetting']['value']) && $this->data['UserSetting']['value'] !== 'null') { + if (is_array($this->data['UserSetting']['value'])) { + $this->data['UserSetting']['value'] = json_encode($this->data['UserSetting']['value']); + } + } else { + $this->data['UserSetting']['value'] = '[]'; + } + debug($this->data); + return true; + } + + // Once we run a find, let us decode the JSON field so we can interact with the contents as if it was an array + public function afterFind($results, $primary = false) + { + foreach ($results as $k => $v) { + $results[$k]['UserSetting']['value'] = json_decode($v['UserSetting']['value'], true); + } + return $results; + } + + public function checkSettingValidity($setting) + { + return isset($this->validSettings[$setting]); + } + + /* + * canModify expects an auth user object or a user ID and a loaded setting as input parameters + * check if the user can modify/remove the given entry + * returns true for site admins + * returns true for org admins if setting["User"]["org_id"] === $user["org_id"] + * returns true for any user if setting["user_id"] === $user["id"] + */ + public function checkAccess($user, $setting, $user_id = false) + { + if (is_numeric($user)) { + $user = $this->User->getAuthUser($user); + } + if (empty($setting) && !empty($user_id) && is_numeric($user_id)) { + $userToCheck = $this->User->find('first', array( + 'conditions' => array('User.id' => $user_id), + 'recursive' => -1 + )); + if (empty($userToCheck)) { + return false; + } + $setting = array( + 'User' => array( + 'org_id' => $userToCheck['User']['org_id'] + ), + 'UserSetting' => array( + 'user_id' => $userToCheck['User']['id'] + ) + ); + } + if ($user['Role']['perm_site_admin']) { + return true; + } else if ($user['Role']['perm_admin']) { + if ($user['org_id'] === $setting['User']['org_id']) { + return true; + } + } else { + if ( + $user['id'] === $setting['UserSetting']['user_id'] && + (!Configure::check('MISP.disableUserSelfManagement') || Configure::check('MISP.disableUserSelfManagement')) + ) { + return true; + } + } + return false; + } + + /* + * Check whether the event is something the user is interested (to be alerted on) + * + */ + public function checkPublishFilter($user, $event) + { + $rule = $this->find('first', array( + 'recursive' => -1, + 'conditions' => array( + 'UserSetting.user_id' => $user['id'], + 'UserSetting.setting' => 'publish_alert_filter' + ) + )); + // We should return true if no setting has been configured, or there's a setting with an empty value + if (empty($rule) || empty($rule['UserSetting']['value'])) { + return true; + } + // recursively evaluate the boolean tree to true/false and return the value + $result = $this->__recursiveConvert($rule['UserSetting']['value'], $event); + if (isset($result[0])) { + return $result[0]; + } else { + return false; + } + } + + /* + * Convert a complex rule set recursively + * takes as params a rule branch and an event to check against + * evaluate whether the rule set evaluates as true/false + * The full evaluation involves resolving the boolean branches + * valid boolean operators are OR, AND, NOT all capitalised as strings + */ + private function __recursiveConvert($rule, $event) + { + $toReturn = array(); + if (is_array($rule)) { + foreach ($rule as $k => $v) { + if (in_array($k, array('OR', 'NOT', 'AND'))) { + $parts = $this->__recursiveConvert($v, $event); + $temp = null; + foreach ($parts as $partValue) { + if ($temp === null) { + $temp = ($k === 'NOT') ? !$partValue : $partValue; + } else { + if ($k === 'OR') { + $temp = $temp || $partValue; + } elseif ($k === 'AND') { + $temp = $temp && $partValue; + } else { + $temp = $temp && !$partValue; + } + } + } + $toReturn []= $temp; + } else { + $toReturn []= $this->__checkEvent($k, $v, $event); + } + } + return $toReturn; + } else { + return false; + } + } + + /* + * Checks if an event matches the given rule + * valid filters: + * - AttributeTag.name + * - EventTag.name + * - Tag.name (checks against both event and attribute tags) + * - Orgc.uuid + * - Orgc.name + * Values passed can be used for direct string comparisons or alternatively + * as substring matches by encapsulating the string in a pair of "%" characters + * Each rule can take a list of values + */ + private function __checkEvent($rule, $lookup_values, $event) + { + if (!is_array($lookup_values)) { + $lookup_values = array($lookup_values); + } + foreach ($lookup_values as $k => $v) { + $lookup_values[$k] = mb_strtolower($v); + } + if ($rule === 'AttributeTag.name') { + $values = array_merge( + Hash::extract($event, 'Attribute.{n}.AttributeTag.{n}.Tag.name'), + Hash::extract($event, 'Object.{n}.Attribute.{n}.AttributeTag.{n}.Tag.name') + ); + } else if ($rule === 'EventTag.name') { + $values = Hash::extract($event, 'EventTag.{n}.Tag.name'); + } else if ($rule === 'Orgc.name') { + $values = array($event['Event']['Orgc']['name']); + } else if ($rule === 'Orgc.uuid') { + $values = array($event['Event']['Orgc']['uuid']); + } else if ($rule === 'Tag.name') { + $values = array_merge( + Hash::extract($event, 'Attribute.{n}.AttributeTag.{n}.Tag.name'), + Hash::extract($event, 'Object.{n}.Attribute.{n}.AttributeTag.{n}.Tag.name'), + Hash::extract($event, 'EventTag.{n}.Tag.name') + ); + } + if (!empty($values)) { + foreach ($values as $extracted_value) { + $extracted_value = mb_strtolower($extracted_value); + foreach ($lookup_values as $lookup_value) { + $lookup_value_trimmed = trim($lookup_value, "%"); + if (strlen($lookup_value_trimmed) != strlen($lookup_value)) { + if (strpos($extracted_value, $lookup_value_trimmed) !== false) { + return true; + } + } else { + if ($extracted_value === $lookup_value) { + return true; + } + } + } + } + } + return false; + } +} diff --git a/app/View/Elements/genericElements/IndexTable/Fields/actions.ctp b/app/View/Elements/genericElements/IndexTable/Fields/actions.ctp index 6f30cca1f..c68f37a8d 100644 --- a/app/View/Elements/genericElements/IndexTable/Fields/actions.ctp +++ b/app/View/Elements/genericElements/IndexTable/Fields/actions.ctp @@ -18,9 +18,7 @@ if (isset($action['postLink'])) { echo $this->Form->postLink( '', - array( - 'url' => $url - ), + $url, array( 'class' => $this->FontAwesome->getClass($action['icon']) . ' black ' . (empty($action['class']) ? '' : h($action['class'])), 'title' => empty($action['title']) ? '' : h($action['title']), diff --git a/app/View/Elements/genericElements/IndexTable/Fields/json.ctp b/app/View/Elements/genericElements/IndexTable/Fields/json.ctp index 458bd8a41..3683a959f 100644 --- a/app/View/Elements/genericElements/IndexTable/Fields/json.ctp +++ b/app/View/Elements/genericElements/IndexTable/Fields/json.ctp @@ -1,7 +1,12 @@ %s', - json_encode($data, JSON_PRETTY_PRINT) + '
', + h($k) ); ?> + diff --git a/app/View/Elements/genericElements/SideMenu/side_menu.ctp b/app/View/Elements/genericElements/SideMenu/side_menu.ctp index b3df86edf..e0ce01e60 100644 --- a/app/View/Elements/genericElements/SideMenu/side_menu.ctp +++ b/app/View/Elements/genericElements/SideMenu/side_menu.ctp @@ -446,6 +446,16 @@ 'url' => '/users/view/me', 'text' => __('My Profile') )); + echo $this->element('/genericElements/SideMenu/side_menu_link', array( + 'element_id' => 'user_settings_index_me', + 'url' => '/user_settings/index/user_id:me', + 'text' => __('My Settings') + )); + echo $this->element('/genericElements/SideMenu/side_menu_link', array( + 'element_id' => 'user_settings_set', + 'url' => '/user_settings/setSetting', + 'text' => __('Set Setting') + )); echo $this->element('/genericElements/SideMenu/side_menu_link', array( 'url' => '/users/dashboard', 'text' => __('Dashboard') @@ -668,6 +678,16 @@ )); } if ($isAdmin) { + echo $this->element('/genericElements/SideMenu/side_menu_link', array( + 'element_id' => 'user_settings_index', + 'url' => '/user_settings/index/user_id:all', + 'text' => __('User settings') + )); + echo $this->element('/genericElements/SideMenu/side_menu_link', array( + 'element_id' => 'user_settings_set', + 'url' => '/user_settings/setSetting', + 'text' => __('Set Setting') + )); echo $this->element('/genericElements/SideMenu/side_menu_link', array( 'element_id' => 'contact', 'url' => '/admin/users/email', @@ -913,7 +933,6 @@ } } break; - case 'decayingModel': if ($isAdmin) { if ($isSiteAdmin && ($menuItem === 'view' || $menuItem === 'index')) { diff --git a/app/View/Elements/global_menu.ctp b/app/View/Elements/global_menu.ctp index cfef2b6c1..c458adbbc 100755 --- a/app/View/Elements/global_menu.ctp +++ b/app/View/Elements/global_menu.ctp @@ -147,6 +147,14 @@ 'text' => __('My Profile'), 'url' => '/users/view/me' ), + array( + 'text' => __('My Settings'), + 'url' => '/user_settings/index/user_id:me' + ), + array( + 'text' => __('Set Setting'), + 'url' => '/user_settings/setSetting' + ), array( 'text' => __('Dashboard'), 'url' => '/users/dashboard' @@ -270,6 +278,14 @@ 'text' => __('List Users'), 'url' => '/admin/users/index' ), + array( + 'text' => __('List User Settings'), + 'url' => '/user_settings/index/user_id:all' + ), + array( + 'text' => __('Set User Setting'), + 'url' => '/user_settings/setSetting' + ), array( 'text' => __('Add User'), 'url' => '/admin/users/add' diff --git a/app/View/UserSettings/generic_field.ctp b/app/View/UserSettings/generic_field.ctp new file mode 100644 index 000000000..d6431dca6 --- /dev/null +++ b/app/View/UserSettings/generic_field.ctp @@ -0,0 +1,3 @@ + diff --git a/app/View/UserSettings/index.ctp b/app/View/UserSettings/index.ctp new file mode 100644 index 000000000..9035f7c6c --- /dev/null +++ b/app/View/UserSettings/index.ctp @@ -0,0 +1,103 @@ +element('/genericElements/IndexTable/index_table', array( + * 'top_bar' => ( + * // search/filter bar information compliant with ListTopBar + * ), + * 'data' => array( + // the actual data to be used + * ), + * 'fields' => array( + * // field list with information for the paginator + * ), + * 'title' => optional title, + * 'description' => optional description + * )); + * + */ + echo '
'; + echo $this->element('/genericElements/IndexTable/index_table', array( + 'data' => array( + 'data' => $data, + 'top_bar' => array( + 'children' => array( + array( + 'type' => 'simple', + 'children' => array( + array( + 'active' => $context === 'me', + 'url' => $baseurl . '/user_settings/index/user_id:me', + 'text' => __('Me'), + ), + array( + 'active' => $context === 'org', + 'url' => $baseurl . '/user_settings/index/user_id:org', + 'text' => __('Organisation'), + 'requirement' => $isAdmin + ), + array( + 'active' => $context === 'all', + 'url' => $baseurl . '/user_settings/index/user_id:all', + 'text' => __('All'), + 'requirement' => $isSiteAdmin + ) + ) + ) + ) + ), + 'fields' => array( + array( + 'name' => __('Id'), + 'sort' => 'id', + 'class' => 'short', + 'data_path' => 'UserSetting.id' + ), + array( + 'name' => __('User'), + 'sort' => 'User.email', + 'class' => 'short', + 'data_path' => 'User.email' + ), + array( + 'name' => __('Setting'), + 'class' => 'short', + 'sort' => 'type', + 'data_path' => 'UserSetting.setting' + ), + array( + 'name' => __('Value'), + 'sort' => 'type', + 'element' => 'json', + 'data_path' => 'UserSetting.value' + ) + ), + 'title' => __('User settings management'), + 'description' => __('Manage the individual user settings.'), + 'actions' => array( + array( + 'url' => '/user_settings/setSetting', + 'url_params_data_paths' => array( + 'UserSetting.user_id', + 'UserSetting.setting' + ), + 'icon' => 'edit' + ), + array( + 'url' => '/user_settings/delete', + 'url_params_data_paths' => array( + 'UserSetting.id' + ), + 'icon' => 'trash', + 'postLink' => true, + 'postLinkConfirm' => __('Are you sure you wish to delete this entry?') + ) + ) + ) + )); + echo '
'; + if ($context === 'me' || (!$isAdmin && !$isSiteAdmin)) { + echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'globalActions', 'menuItem' => 'user_settings_index_me')); + } else { + echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'admin', 'menuItem' => 'user_settings_index')); + } +?> diff --git a/app/View/UserSettings/set_setting.ctp b/app/View/UserSettings/set_setting.ctp new file mode 100644 index 000000000..633786201 --- /dev/null +++ b/app/View/UserSettings/set_setting.ctp @@ -0,0 +1,77 @@ +%s
%s%s
%s%s', + $this->Form->create('UserSetting'), + __('Set User Setting'), + sprintf( + '%s%s%s', + $this->Form->input( + 'user_id', + array( + 'div' => 'clear', + 'class' => 'input input-xxlarge', + 'options' => array($users), + 'disabled' => count($users) === 1 + ) + ), + $this->Form->input( + 'setting', + array( + 'div' => 'clear', + 'class' => 'input input-xxlarge', + 'options' => array_combine(array_keys($validSettings), array_keys($validSettings)) + ) + ), + $this->Form->input( + 'value', + array( + 'div' => 'clear', + 'class' => 'input input-xxlarge', + 'type' => 'textarea', + ) + ) + ), + $this->Form->button(__('Submit'), array('class' => 'btn btn-primary')), + $this->Form->end() + ); + echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'globalActions', 'menuItem' => 'user_settings_set')); +?> + diff --git a/app/View/UserSettings/view.ctp b/app/View/UserSettings/view.ctp new file mode 100644 index 000000000..e69de29bb diff --git a/app/webroot/js/misp.js b/app/webroot/js/misp.js index 9956774e4..e11db2308 100644 --- a/app/webroot/js/misp.js +++ b/app/webroot/js/misp.js @@ -4285,7 +4285,7 @@ function syntaxHighlightJson(json, indent) { if (typeof json == 'string') { json = JSON.parse(json); } - json = JSON.stringify(json, undefined, 2); + json = JSON.stringify(json, undefined, indent); json = json.replace(/&/g, '&').replace(//g, '>').replace(/(?:\r\n|\r|\n)/g, '
').replace(/ /g, ' '); return json.replace(/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g, function (match) { var cls = 'json_number';