diff --git a/.gitmodules b/.gitmodules index 52a945b69..49eaadd89 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "libraries/misp-taxonomies"] path = libraries/misp-taxonomies url = https://github.com/MISP/misp-taxonomies +[submodule "libraries/misp-warninglists"] + path = libraries/misp-warninglists + url = https://github.com/MISP/misp-warninglists diff --git a/libraries/misp-warninglists b/libraries/misp-warninglists new file mode 160000 index 000000000..2c7d29985 --- /dev/null +++ b/libraries/misp-warninglists @@ -0,0 +1 @@ +Subproject commit 2c7d29985ef77b568c0cfe3f035414db61f8b74c diff --git a/phpstan.neon b/phpstan.neon index 2d3a720f5..7edb97ee6 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,6 +3,10 @@ parameters: fileExtensions: - php - ctp + paths: + - src + - tests + - templates ignoreErrors: - message: '#Undefined variable: \$this#' path: %currentWorkingDirectory%/templates diff --git a/src/Controller/Component/ACLComponent.php b/src/Controller/Component/ACLComponent.php index 07b861a10..4217ca317 100644 --- a/src/Controller/Component/ACLComponent.php +++ b/src/Controller/Component/ACLComponent.php @@ -279,6 +279,20 @@ class ACLComponent extends Component 'view' => ['*'], 'preview_entries' => ['*'] ], + 'Warninglists' => [ + 'checkValue' => ['*'], + 'delete' => ['perm_warninglist'], + 'enableWarninglist' => ['perm_warninglist'], + 'getToggleField' => ['perm_warninglist'], + 'index' => ['*'], + 'toggleEnable' => ['perm_warninglist'], + 'update' => [], // TODO shouldn't perm_warninglist be allowed to update? + 'view' => ['*'], + 'edit' => ['perm_warninglist'], + 'add' => ['perm_warninglist'], + 'export' => ['*'], + 'import' => ['perm_warninglist'], + ], 'ObjectTemplates' => [ 'activate' => [], 'add' => ['perm_object_template'], diff --git a/src/Controller/Component/Navigation/NoticelistsNavigation.php b/src/Controller/Component/Navigation/NoticelistsNavigation.php index 6d41567a6..1cfe51972 100644 --- a/src/Controller/Component/Navigation/NoticelistsNavigation.php +++ b/src/Controller/Component/Navigation/NoticelistsNavigation.php @@ -3,14 +3,18 @@ namespace App\Controller\Component\Navigation; class NoticelistsNavigation extends BaseNavigation { - function addRoutes() + public function addRoutes() { - $this->bcf->addRoute('Noticelists', 'update', [ - 'label' => __('Update Noticelists'), - 'url' => '/noticelists/update', - 'icon' => 'circle-up', - 'isPOST' => true, - ]); + $this->bcf->addRoute( + 'Noticelists', + 'update', + [ + 'label' => __('Update Noticelists'), + 'url' => '/noticelists/update', + 'icon' => 'circle-up', + 'isPOST' => true, + ] + ); } public function addActions() diff --git a/src/Controller/OrganisationsController.php b/src/Controller/OrganisationsController.php index d1342cc58..81c4b8c4c 100644 --- a/src/Controller/OrganisationsController.php +++ b/src/Controller/OrganisationsController.php @@ -50,11 +50,6 @@ class OrganisationsController extends AppController 'contextFilters' => [ 'custom' => $customContextFilters, ], - 'afterFind' => function ($entity) { - $entity->setVirtual(['user_count']); - - return $entity; - }, 'contain' => $this->containFields, 'statisticsFields' => $this->statisticsFields, ] diff --git a/src/Controller/WarninglistsController.php b/src/Controller/WarninglistsController.php new file mode 100644 index 000000000..e9965109e --- /dev/null +++ b/src/Controller/WarninglistsController.php @@ -0,0 +1,501 @@ + 60, + 'maxLimit' => 9999, + 'contain' => ['WarninglistTypes'], + 'order' => [ + 'Warninglist.id' => 'DESC', + ], + ]; + + /** + * index page for warninglists + * + * @return \Cake\Http\Response|null|void Renders view + */ + public function index() + { + $params = [ + 'filters' => ['name', 'description', 'type'], + 'quickFilters' => ['name'], + ]; + + $this->CRUD->index($params); + + $responsePayload = $this->CRUD->getResponsePayload(); + if (!empty($responsePayload)) { + return $responsePayload; + } + + // $filters = $this->harvestParameters(['value', 'enabled']); + // if (!empty($filters['value'])) { + // $this->paginate['conditions'] = [ + // 'OR' => [ + // 'LOWER(Warninglist.name) LIKE' => '%' . strtolower($filters['value']) . '%', + // 'LOWER(Warninglist.description) LIKE' => '%' . strtolower($filters['value']) . '%', + // 'LOWER(Warninglist.type)' => strtolower($filters['value']), + // ] + // ]; + // } + // if (isset($filters['enabled'])) { + // $this->paginate['conditions'][] = ['Warninglist.enabled' => $filters['enabled']]; + // } + + // if ($this->ParamHandler->isRest()) { + // unset($this->paginate['limit']); + // $warninglists = $this->Warninglist->find('all', $this->paginate); + // } else { + // $warninglists = $this->paginate(); + // } + // foreach ($warninglists as &$warninglist) { + // $validAttributes = array_column($warninglist['WarninglistType'], 'type'); + // $warninglist['Warninglist']['valid_attributes'] = implode(', ', $validAttributes); + // unset($warninglist['WarninglistType']); + // } + // if ($this->ParamHandler->isRest()) { + // return $this->RestResponse->viewData(['Warninglists' => $warninglists], $this->response->getType()); + // } + + // $this->set('warninglists', $warninglists); + // $this->set('passedArgsArray', $filters); + $this->set('possibleCategories', $this->Warninglists->categories()); + } + + // public function update() + // { + // if (!$this->request->is('post')) { + // throw new MethodNotAllowedException(__('This action is only accessible via POST requests.')); + // } + // $result = $this->Warninglist->update(); + // $this->Log = ClassRegistry::init('Log'); + // $fails = 0; + // $successes = 0; + // if (!empty($result)) { + // if (isset($result['success'])) { + // foreach ($result['success'] as $id => $success) { + // if (isset($success['old'])) { + // $change = $success['name'] . ': updated from v' . $success['old'] . ' to v' . $success['new']; + // } else { + // $change = $success['name'] . ' v' . $success['new'] . ' installed'; + // } + // $this->Log->create(); + // $this->Log->saveOrFailSilently(array( + // 'org' => $this->Auth->user('Organisation')['name'], + // 'model' => 'Warninglist', + // 'model_id' => $id, + // 'email' => $this->Auth->user('email'), + // 'action' => 'update', + // 'user_id' => $this->Auth->user('id'), + // 'title' => __('Warning list updated'), + // 'change' => $change, + // )); + // $successes++; + // } + // } + // if (isset($result['fails'])) { + // foreach ($result['fails'] as $id => $fail) { + // $this->Log->create(); + // $this->Log->saveOrFailSilently(array( + // 'org' => $this->Auth->user('Organisation')['name'], + // 'model' => 'Warninglist', + // 'model_id' => $id, + // 'email' => $this->Auth->user('email'), + // 'action' => 'update', + // 'user_id' => $this->Auth->user('id'), + // 'title' => __('Warning list failed to update'), + // 'change' => __('%s could not be installed/updated. Error: %s', $fail['name'], $fail['fail']), // TODO: needs to be optimized for non-SVO languages + // )); + // $fails++; + // } + // } + // } else { + // $this->Log->create(); + // $this->Log->saveOrFailSilently(array( + // 'org' => $this->Auth->user('Organisation')['name'], + // 'model' => 'Warninglist', + // 'model_id' => 0, + // 'email' => $this->Auth->user('email'), + // 'action' => 'update', + // 'user_id' => $this->Auth->user('id'), + // 'title' => __('Warninglist update (nothing to update)'), + // 'change' => __('Executed an update of the warning lists, but there was nothing to update.'), + // )); + // } + // if ($successes == 0 && $fails == 0) { + // $flashType = 'info'; + // $message = __('All warninglists are up to date already.'); + // } elseif ($successes == 0) { + // $flashType = 'error'; + // $message = __('Could not update any of the warning lists'); + // } else { + // $flashType = 'success'; + // $message = __('Successfully updated %s warninglists.', $successes); + // if ($fails != 0) { + // $message .= __(' However, could not update %s warninglists.', $fails); // TODO: non-SVO languages need to be considered + // } + // } + // if ($this->_isRest()) { + // return $this->RestResponse->saveSuccessResponse('Warninglist', 'update', false, $this->response->type(), $message); + // } else { + // $this->Flash->{$flashType}($message); + // $this->redirect(array('controller' => 'warninglists', 'action' => 'index')); + // } + // } + + // public function add() + // { + // $types = array_combine($this->Warninglist->validate['type']['rule'][1], $this->Warninglist->validate['type']['rule'][1]); + // $this->set('possibleTypes', $types); + // $this->set('possibleCategories', $this->Warninglist->categories()); + + // $this->loadModel('Attribute'); + // $this->set('matchingAttributes', array_combine(array_keys($this->Attribute->typeDefinitions), array_keys($this->Attribute->typeDefinitions))); + + // $this->CRUD->add([ + // 'beforeSave' => function (array $warninglist) { + // if (isset($warninglist['Warninglist']['entries'])) { + // $entries = $this->Warninglist->parseFreetext($warninglist['Warninglist']['entries']); + // unset($warninglist['Warninglist']['entries']); + // $warninglist['WarninglistEntry'] = $entries; + // } + // if (empty($warninglist['WarninglistEntry'])) { + // $warninglist['Warninglist']['entries'] = ''; // Make model validation fails + // } + // if (empty($warninglist['Warninglist']['matching_attributes'])) { + // $warninglist['Warninglist']['matching_attributes'] = ['ALL']; + // } + // if (isset($warninglist['Warninglist']['matching_attributes']) && is_array($warninglist['Warninglist']['matching_attributes'])) { + // $warninglist['WarninglistType'] = []; + // foreach ($warninglist['Warninglist']['matching_attributes'] as $attribute) { + // $warninglist['WarninglistType'][] = ['type' => $attribute]; + // } + // } + // $warninglist['Warninglist']['default'] = 0; + // return $warninglist; + // }, + // ]); + // if ($this->restResponsePayload) { + // return $this->restResponsePayload; + // } + // } + + // public function edit($id = null) + // { + // $types = array_combine($this->Warninglist->validate['type']['rule'][1], $this->Warninglist->validate['type']['rule'][1]); + // $this->set('possibleTypes', $types); + // $this->set('possibleCategories', $this->Warninglist->categories()); + + // $this->loadModel('Attribute'); + // $this->set('matchingAttributes', array_combine(array_keys($this->Attribute->typeDefinitions), array_keys($this->Attribute->typeDefinitions))); + + // $this->CRUD->edit($id, [ + // 'conditions' => ['default' => 0], // it is not possible to edit default warninglist + // 'contain' => ['WarninglistEntry', 'WarninglistType'], + // 'fields' => ['name', 'description', 'type', 'category', 'entries', 'matching_attributes'], + // 'redirect' => ['action' => 'view', $id], + // 'beforeSave' => function (array $warninglist) { + // if (isset($warninglist['Warninglist']['entries'])) { + // $entries = $this->Warninglist->parseFreetext($warninglist['Warninglist']['entries']); + // unset($warninglist['Warninglist']['entries']); + // $warninglist['WarninglistEntry'] = $entries; + // } + // if (empty($warninglist['WarninglistEntry'])) { + // $warninglist['Warninglist']['entries'] = ''; // Make model validation fails + // } + // if (isset($warninglist['Warninglist']['matching_attributes']) && is_array($warninglist['Warninglist']['matching_attributes'])) { + // $warninglist['WarninglistType'] = []; + // foreach ($warninglist['Warninglist']['matching_attributes'] as $attribute) { + // $warninglist['WarninglistType'][] = ['type' => $attribute]; + // } + // } + // $warninglist['Warninglist']['version']++; + // return $warninglist; + // }, + // ]); + // if ($this->restResponsePayload) { + // return $this->restResponsePayload; + // } + + // if (isset($this->request->data['WarninglistEntry'])) { + // $entries = []; + // foreach ($this->request->data['WarninglistEntry'] as $entry) { + // $value = $entry['value']; + // if ($entry['comment']) { + // $value .= ' # ' . $entry['comment']; + // } + // $entries[] = $value; + // } + // $this->request->data['Warninglist']['entries'] = implode("\n", $entries); + // } + + // if (isset($this->request->data['WarninglistType'])) { + // $attributes = array_column($this->request->data['WarninglistType'], 'type'); + // $this->request->data['Warninglist']['matching_attributes'] = $attributes; + // } + + // $this->render('add'); + // } + + // /* + // * toggle warninglists on or offset + // * Simply POST an ID or a list of IDs to toggle the current state + // * To control what state the warninglists should have after execution instead of just blindly toggling them, simply pass the enabled flag + // * Example: + // * {"id": [5, 8], "enabled": 1} + // * Alternatively search by a substring in the warninglist's named, such as: + // * {"name": ["%alexa%", "%iana%"], "enabled": 1} + // */ + // public function toggleEnable() + // { + // if (!$this->request->is('post')) { + // return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => __('This function only accepts POST requests.'))), 'status' => 200, 'type' => 'json')); + // } + // if (isset($this->request->data['Warninglist']['data'])) { + // $id = $this->request->data['Warninglist']['data']; + // } else { + // if (!empty($this->request->data['id'])) { + // $id = $this->request->data['id']; + // } elseif (!empty($this->request->data['name'])) { + // if (!is_array($this->request->data['name'])) { + // $names = [$this->request->data['name']]; + // } else { + // $names = $this->request->data['name']; + // } + // $conditions = array(); + // foreach ($names as $name) { + // $conditions['OR'][] = ['LOWER(Warninglist.name] LIKE' => strtolower($name)); + // } + // $id = $this->Warninglist->find('column', array( + // 'conditions' => $conditions, + // 'fields' => ['Warninglist.id'] + // )); + // } + // } + // if (isset($this->request->data['enabled'])) { + // $enabled = $this->request->data['enabled']; + // } + // if (empty($id)) { + // return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => __('Warninglist not found.'))), 'status' => 200, 'type' => 'json')); + // } + // $currentState = $this->Warninglist->find('all', ['conditions' => ['id' => $id], 'recursive' => -1]); + // if (empty($currentState)) { + // return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => __('Warninglist(s) not found.'))), 'status' => 200, 'type' => 'json')); + // } + // $success = 0; + // foreach ($currentState as $warningList) { + // if (isset($enabled)) { + // $warningList['Warninglist']['enabled'] = $enabled; + // $message = $enabled ? 'enabled' : 'disabled'; + // } else { + // if ($warningList['Warninglist']['enabled']) { + // $warningList['Warninglist']['enabled'] = 0; + // $message = 'disabled'; + // } else { + // $warningList['Warninglist']['enabled'] = 1; + // $message = 'enabled'; + // } + // if (!isset($enabled) && count($currentState) > 1) { + // $message = 'toggled'; + // } + // } + // if ($this->Warninglist->save($warningList)) { + // $success += 1; + // } + // $this->Warninglist->regenerateWarninglistCaches($warningList['Warninglist']['id']); + // } + // if ($success) { + // return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => $success . __(' warninglist(s) ') . $message)), 'status' => 200, 'type' => 'json')); // TODO: non-SVO lang considerations + // } else { + // return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => __('Warninglist(s) could not be toggled.'))), 'status' => 200, 'type' => 'json')); + // } + // } + + // public function enableWarninglist($id, $enable = false) + // { + // $this->Warninglist->id = $id; + // if (!$this->Warninglist->exists()) { + // throw new NotFoundException(__('Invalid Warninglist.')); + // } + // // DBMS interoperability: convert boolean false to integer 0 so cakephp doesn't try to insert an empty string into the database + // if ($enable === false) { + // $enable = 0; + // } + // $this->Warninglist->saveField('enabled', $enable); + // $this->Warninglist->regenerateWarninglistCaches($id); + // if ($enable === 0) { + // $this->Flash->success(__('Warninglist disabled')); + // } + // else { + // $this->Flash->success(__('Warninglist enabled')); + // } + // $this->redirect(array('controller' => 'warninglists', 'action' => 'view', $id)); + // } + + // public function getToggleField() + // { + // if (!$this->request->is('ajax')) { + // throw new MethodNotAllowedException(__('This action is available via AJAX only.')); + // } + // $this->layout = false; + // $this->render('ajax/getToggleField'); + // } + + // public function view($id) + // { + // if (!is_numeric($id)) { + // throw new NotFoundException(__('Invalid ID.')); + // } + // $warninglist = $this->Warninglist->find('first', array( + // 'contain' => ['WarninglistEntry', 'WarninglistType'], + // 'conditions' => ['id' => $id]) + // ); + // if (empty($warninglist)) { + // throw new NotFoundException(__('Warninglist not found.')); + // } + // if ($this->IndexFilter->isCsv()) { + // $csv = []; + // foreach ($warninglist['WarninglistEntry'] as $entry) { + // $line = $entry['value']; + // if ($entry['comment']) { + // $line .= ';' . $entry['comment']; + // } + // $csv[] = $line; + // } + // return $this->RestResponse->viewData(implode("\n", $csv), 'csv'); + // } + // if ($this->_isRest()) { + // $warninglist['Warninglist']['WarninglistEntry'] = $warninglist['WarninglistEntry']; + // $warninglist['Warninglist']['WarninglistType'] = $warninglist['WarninglistType']; + // return $this->RestResponse->viewData(['Warninglist' => $warninglist['Warninglist']], $this->response->type()); + // } + + // $this->set('warninglist', $warninglist); + // $this->set('possibleCategories', $this->Warninglist->categories()); + // } + + // public function import() + // { + // $this->request->allowMethod(['post']); + + // if (empty($this->request->data)) { + // throw new BadRequestException(__('No valid data received.')); + // } + + // foreach (['name', 'type', 'version', 'description', 'matching_attributes', 'list'] as $filed) { + // if (!isset($this->request->data[$filed])) { + // throw new BadRequestException(__('No valid data received: field `%s` is missing.', $filed)); + // } + // } + + // if (!is_array($this->request->data['list'])) { + // throw new BadRequestException(__('No valid data received: `list` field is not array')); + // } + + // try { + // $id = $this->Warninglist->import($this->request->data); + // return $this->RestResponse->saveSuccessResponse('Warninglist', 'import', $id, false, __('Warninglist imported')); + // } catch (Exception $e) { + // return $this->RestResponse->saveFailResponse('Warninglist', 'import', false, $e->getMessage()); + // } + // } + + // public function export($id = null) + // { + // if (empty($id)) { + // throw new NotFoundException(__('Warninglist not found.')); + // } + // $warninglist = $this->Warninglist->find('first', [ + // 'contain' => ['WarninglistType'], + // 'conditions' => ['id' => $id], + // ]); + // if (empty($warninglist)) { + // throw new NotFoundException(__('Warninglist not found.')); + // } + // $matchingAttributes = array_column($warninglist['WarninglistType'], 'type'); + // $list = $this->Warninglist->WarninglistEntry->find('column', [ + // 'conditions' => ['warninglist_id' => $warninglist['Warninglist']['id']], + // 'fields' => ['value'], + // ]); + // $output = [ + // 'name' => $warninglist['Warninglist']['name'], + // 'type' => $warninglist['Warninglist']['type'], + // 'version' => $warninglist['Warninglist']['version'], + // 'description' => $warninglist['Warninglist']['description'], + // 'matching_attributes' => $matchingAttributes, + // 'list' => $list, + // ]; + // return $this->RestResponse->viewData($output, 'json'); + // } + + // public function delete($id) + // { + // if ($this->request->is('post')) { + // $id = (int)$id; + // $result = $this->Warninglist->quickDelete($id); + // if ($result) { + // $this->Flash->success(__('Warninglist successfully deleted.')); + // } else { + // $this->Flash->error(__('Warninglist could not be deleted.')); + // } + // $this->redirect(['controller' => 'warninglists', 'action' => 'index']); + // } else { + // if ($this->request->is('ajax')) { + // $this->set('id', $id); + // $this->render('ajax/delete_confirmation'); + // } else { + // throw new MethodNotAllowedException(__('This function can only be reached via AJAX.')); + // } + // } + // } + + // public function checkValue() + // { + // if ($this->request->is('post')) { + // if (empty($this->request->data)) { + // throw new NotFoundException(__('No valid data received.')); + // } + // $data = $this->request->data; + // if (is_array($data) && isset($data['Warninglist'])) { + // $data = $data['Warninglist']; + // } + // if (!is_array($data)) { + // $data = [$data]; + // } + // if (array_key_exists('[]', $data)) { + // $data = $data['[]']; + // } + + // $hits = array(); + // $warninglists = $this->Warninglist->getEnabled(); + // foreach ($data as $dataPoint) { + // $dataPoint = trim($dataPoint); + // foreach ($warninglists as $warninglist) { + // $values = $this->Warninglist->getFilteredEntries($warninglist); + // $result = $this->Warninglist->checkValue($values, $dataPoint, '', $warninglist['Warninglist']['type']); + // if ($result !== false) { + // $hits[$dataPoint][] = [ + // 'id' => $warninglist['Warninglist']['id'], + // 'name' => $warninglist['Warninglist']['name'], + // 'matched' => $result[0], + // ]; + // } + // } + // } + // if ($this->_isRest()) { + // return $this->RestResponse->viewData($hits, $this->response->type()); + // } + // $this->set('hits', $hits); + // $this->set('data', $data); + // } else { + // if ($this->_isRest()) { + // return $this->RestResponse->describe('Warninglists', 'checkValue', false, $this->response->type()); + // } + // } + // } +} diff --git a/src/Model/Entity/AppModel.php b/src/Model/Entity/AppModel.php index 3e3067e8c..25e158974 100644 --- a/src/Model/Entity/AppModel.php +++ b/src/Model/Entity/AppModel.php @@ -1,13 +1,15 @@ _accessibleOnNew ?? []; @@ -64,9 +76,10 @@ class AppModel extends Entity $tag = [ 'id' => $tag['id'], 'name' => $tag['name'], - 'colour' => $tag['colour'] + 'colour' => $tag['colour'], ]; } + return $tags; } @@ -77,13 +90,13 @@ class AppModel extends Entity $alignmentDataToKeep = [ 'individual' => [ 'id', - 'email' + 'email', ], 'organisation' => [ 'id', 'uuid', - 'name' - ] + 'name', + ], ]; foreach ($alignments as $alignment) { foreach (array_keys($alignmentDataToKeep) as $type) { @@ -97,6 +110,7 @@ class AppModel extends Entity } } } + return $rearrangedAlignments; } @@ -106,15 +120,35 @@ class AppModel extends Entity $this->organisation = [ 'id' => $this->organisation['id'], 'name' => $this->organisation['name'], - 'uuid' => $this->organisation['uuid'] + 'uuid' => $this->organisation['uuid'], ]; } if (in_array('individual', $typesToRearrange) && isset($this->individual)) { $this->individual = [ 'id' => $this->individual['id'], 'email' => $this->individual['email'], - 'uuid' => $this->individual['uuid'] + 'uuid' => $this->individual['uuid'], ]; } } + + /** + * @param string $field The field name to add + * @param \App\Model\Entity\AppModel $model The model to use + * @param array $conditions The conditions for the query + */ + public function addCountField($field, AppModel $model, array $conditions) + { + $dataSource = ConnectionManager::get('default')->config['datasource']; + $subQuery = $dataSource->buildStatement( + [ + 'fields' => ['COUNT(*)'], + 'table' => $dataSource->fullTableName($model), + 'alias' => $model->alias, + 'conditions' => $conditions, + ], + $model + ); + $this->virtualFields[$field] = $subQuery; + } } diff --git a/src/Model/Entity/Warninglist.php b/src/Model/Entity/Warninglist.php index ac0d3e307..e5c092f2b 100644 --- a/src/Model/Entity/Warninglist.php +++ b/src/Model/Entity/Warninglist.php @@ -1,16 +1,31 @@ get('WarninglistEntries'); + $entry_count = $warninglist_entries_table->find('all') + ->where(['warninglist_id' => $this->id]) + ->count(); + + return $entry_count; + } } diff --git a/src/Model/Entity/WarninglistEntry.php b/src/Model/Entity/WarninglistEntry.php index 24f24a1ee..5694d2b91 100644 --- a/src/Model/Entity/WarninglistEntry.php +++ b/src/Model/Entity/WarninglistEntry.php @@ -1,10 +1,8 @@ true, - 'propertyName' => 'Warninglist' + 'propertyName' => 'Warninglist', ] ); $this->setDisplayField('value'); } + /** + * validationDefault + * + * @param mixed $validator Validator + * @return \Cake\Validation\Validator + */ public function validationDefault(Validator $validator): Validator { $validator ->notEmptyString('value') ->requirePresence(['value', 'warninglist_id'], 'create'); + return $validator; } } diff --git a/src/Model/Table/WarninglistTypesTable.php b/src/Model/Table/WarninglistTypesTable.php new file mode 100644 index 000000000..70df7ddce --- /dev/null +++ b/src/Model/Table/WarninglistTypesTable.php @@ -0,0 +1,44 @@ +addBehavior('AuditLog'); + $this->belongsTo( + 'Warninglist', + [ + 'dependent' => true, + 'propertyName' => 'Warninglist', + ] + ); + $this->setDisplayField('value'); + } + + /** + * validationDefault + * + * @param mixed $validator Validator + * @return \Cake\Validation\Validator + */ + public function validationDefault(Validator $validator): Validator + { + $validator + ->notEmptyString('type') + ->requirePresence(['type', 'warninglist_id'], 'create'); + + return $validator; + } +} diff --git a/src/Model/Table/WarninglistsTable.php b/src/Model/Table/WarninglistsTable.php index 1a6a298d8..66a0d1c66 100644 --- a/src/Model/Table/WarninglistsTable.php +++ b/src/Model/Table/WarninglistsTable.php @@ -1,54 +1,81 @@ showForAll = Configure::read('MISP.warning_for_all'); $this->hasMany( 'WarninglistEntries', [ 'dependent' => true, - 'propertyName' => 'WarninglistEntry' + 'propertyName' => 'WarninglistEntry', ] ); $this->hasMany( 'WarninglistTypes', [ 'dependent' => true, - 'propertyName' => 'WarninglistType' + 'propertyName' => 'WarninglistType', ] ); - parent::initialize($config); + + $this->setTable('warninglists'); + $this->setDisplayField('name'); + $this->setPrimaryKey('id'); } + /** + * buildRules + * + * @param \Cake\ORM\RulesChecker $rules the rules + * @return \Cake\ORM\RulesChecker + */ public function buildRules(RulesChecker $rules): RulesChecker { $rules->add($rules->isUnique(['name'])); + return $rules; } + /** + * validationDefault + * + * @param \Cake\Validation\Validator $validator the validator + * @return \Cake\Validation\Validator + */ public function validationDefault(Validator $validator): Validator { $validator @@ -71,11 +98,12 @@ class WarninglistsTable extends AppTable ] ) ->requirePresence(['name', 'description', 'entries'], 'create'); + return $validator; } // TODO: [3.x-MIGRATION] re-implement this as a custom validation rule - // public function beforeValidate($options = array()) + // public function beforeValidate($options = []) // { // if (isset($this->data['WarninglistEntry'])) { // if ($this->data['Warninglist']['type'] === 'cidr') { @@ -103,7 +131,7 @@ class WarninglistsTable extends AppTable /** * Attach warninglist matches to attributes or proposals with IDS mark. * - * @param array $attributes + * @param array $attributes the attributes to attach to * @return array Warninglist ID => name */ public function attachWarninglistToAttributes(array &$attributes) @@ -133,6 +161,7 @@ class WarninglistsTable extends AppTable if (!empty($eventWarnings)) { $this->assignComments($attributes); } + return $eventWarnings; } @@ -218,7 +247,9 @@ class WarninglistsTable extends AppTable /** * Assign comments to warninglist hits. - * @param array $attributes + * + * @param array $attributes the attributes to assign comments to + * @return void */ private function assignComments(array &$attributes) { @@ -233,19 +264,24 @@ class WarninglistsTable extends AppTable $conditions = []; foreach ($toFetch as $warninglistId => $values) { - $conditions[] = ['AND' => [ - 'warninglist_id' => $warninglistId, - 'value' => array_unique($values), - ]]; + $conditions[] = [ + 'AND' => [ + 'warninglist_id' => $warninglistId, + 'value' => array_unique($values), + ] + ]; } - $entries = $this->WarninglistEntry->find('all', [ - 'conditions' => [ - 'OR' => $conditions, - 'comment !=' => '', - ], - 'fields' => ['value', 'warninglist_id', 'comment'], - ]); + $entries = $this->WarninglistEntry->find( + 'all', + [ + 'conditions' => [ + 'OR' => $conditions, + 'comment !=' => '', + ], + 'fields' => ['value', 'warninglist_id', 'comment'], + ] + ); if (empty($entries)) { return; } @@ -267,14 +303,22 @@ class WarninglistsTable extends AppTable } } + /** + * update the warninglists + * + * @return array the result of the update + */ public function update() { // Fetch existing default warninglists - $existingWarninglist = $this->find('all', [ - 'fields' => ['id', 'name', 'version', 'enabled'], - 'recursive' => -1, - 'conditions' => ['default' => 1], - ])->toArray(); + $existingWarninglist = $this->find( + 'all', + [ + 'fields' => ['id', 'name', 'version', 'enabled'], + 'recursive' => -1, + 'conditions' => ['default' => 1], + ] + )->toArray(); $existingWarninglist = array_column(array_column($existingWarninglist, 'Warninglist'), null, 'name'); $directories = glob(APP . '..' . DS . 'libraries' . DS . 'misp-warninglists' . DS . 'lists' . DS . '*', GLOB_ONLYDIR); @@ -306,40 +350,52 @@ class WarninglistsTable extends AppTable if (!empty($result['success']) || !empty($result['fails'])) { $this->regenerateWarninglistCaches(); } + return $result; } + /** + * quickDelete + * + * @param mixed $id the id of the list to delete + * @return array the result of the deletion + */ public function quickDelete($id) { $result = $this->WarninglistEntry->deleteAll( - array('WarninglistEntry.warninglist_id' => $id), + ['WarninglistEntry.warninglist_id' => $id], false ); if ($result) { $result = $this->WarninglistType->deleteAll( - array('WarninglistType.warninglist_id' => $id), + ['WarninglistType.warninglist_id' => $id], false ); } if ($result) { $result = $this->delete($id, false); } + return $result; } /** * Import single warninglist - * @param array $list + * + * @param array $list a list to import * @return int Warninglist ID - * @throws Exception + * @throws \Exception */ public function import(array $list) { - $existingWarninglist = $this->find('first', [ - 'fields' => ['id', 'name', 'version', 'enabled', 'default'], - 'recursive' => -1, - 'conditions' => ['name' => $list['name']], - ]); + $existingWarninglist = $this->find( + 'first', + [ + 'fields' => ['id', 'name', 'version', 'enabled', 'default'], + 'recursive' => -1, + 'conditions' => ['name' => $list['name']], + ] + ); if ($existingWarninglist && $existingWarninglist['Warninglist']['default']) { throw new Exception('It is not possible to modify default warninglist.'); @@ -352,11 +408,11 @@ class WarninglistsTable extends AppTable } /** - * @param array $list - * @param array $existing - * @param bool $default + * @param array $list List to update + * @param array $existing Existing list + * @param bool $default Whether the list is default * @return int Warninglist ID - * @throws Exception + * @throws \Exception */ private function __updateList(array $list, array $existing, $default = true) { @@ -371,7 +427,7 @@ class WarninglistsTable extends AppTable $this->WarninglistEntry->deleteAll(['WarninglistEntry.warninglist_id' => $existing['id']], false); $this->WarninglistType->deleteAll(['WarninglistType.warninglist_id' => $existing['id']], false); } - $fieldsToSave = array('name', 'version', 'description', 'type', 'enabled'); + $fieldsToSave = ['name', 'version', 'description', 'type', 'enabled']; foreach ($fieldsToSave as $fieldToSave) { $warninglist['Warninglist'][$fieldToSave] = $list[$fieldToSave]; } @@ -380,7 +436,7 @@ class WarninglistsTable extends AppTable } $this->create(); if (!$this->save($warninglist)) { - throw new Exception("Could not save warninglist because of validation errors: " . json_encode($this->validationErrors)); + throw new Exception('Could not save warninglist because of validation errors: ' . json_encode($this->validationErrors)); } $db = $this->getDataSource(); @@ -418,7 +474,7 @@ class WarninglistsTable extends AppTable } $values = []; foreach ($list['matching_attributes'] as $type) { - $values[] = array('type' => $type, 'warninglist_id' => $warninglistId); + $values[] = ['type' => $type, 'warninglist_id' => $warninglistId]; } $this->WarninglistType->saveMany($values); @@ -428,9 +484,10 @@ class WarninglistsTable extends AppTable /** * Regenerate the warninglist caches, but if an ID is passed along, only regen the entries for the given ID. * This allows us to enable/disable a single warninglist without regenerating all caches. - * @param int|null $id + * + * @param int|null $id the ID of the warninglist to regenerate * @return bool - * @throws RedisException + * @throws \RedisException */ public function regenerateWarninglistCaches($id = null) { @@ -453,26 +510,34 @@ class WarninglistsTable extends AppTable if ($id && $warninglist['Warninglist']['id'] != $id) { continue; } - $entries = $this->WarninglistEntry->find('column', array( - 'conditions' => array('warninglist_id' => $warninglist['Warninglist']['id']), - 'fields' => array('value') - )); + $entries = $this->WarninglistEntry->find( + 'column', + [ + 'conditions' => ['warninglist_id' => $warninglist['Warninglist']['id']], + 'fields' => ['value'], + ] + ); $this->cacheWarninglistEntries($entries, $warninglist['Warninglist']['id']); } + return true; } /** * Get enable warninglists and cache them. - * @return array + * + * @return array the warning lists */ private function getEnabledAndCacheWarninglist() { - $warninglists = $this->find('all', [ - 'contain' => ['WarninglistType'], - 'conditions' => ['enabled' => 1], - 'fields' => ['id', 'name', 'type', 'category'], - ]); + $warninglists = $this->find( + 'all', + [ + 'contain' => ['WarninglistType'], + 'conditions' => ['enabled' => 1], + 'fields' => ['id', 'name', 'type', 'category'], + ] + ); // Convert type to array foreach ($warninglists as &$warninglist) { @@ -491,6 +556,13 @@ class WarninglistsTable extends AppTable return $warninglists; } + /** + * cacheWarninglistEntries + * + * @param mixed $warninglistEntries the entries + * @param mixed $id the id of the warninglist + * @return bool true if the cache was successful + */ private function cacheWarninglistEntries(array $warninglistEntries, $id) { try { @@ -508,12 +580,13 @@ class WarninglistsTable extends AppTable $redis->sAdd($key, $entry); } } + return true; } /** * @return array - * @throws JsonException + * @throws \JsonException */ public function getEnabled() { @@ -533,11 +606,14 @@ class WarninglistsTable extends AppTable } $this->enabledCache = $warninglists; + return $warninglists; } /** - * @param int $id + * get warninglist entries (from cache if possible) and cache if needed + * + * @param int $id the ID of the warninglist * @return array */ private function getWarninglistEntries($id) @@ -550,18 +626,22 @@ class WarninglistsTable extends AppTable } catch (Exception $e) { } - $entries = $this->WarninglistEntry->find('column', array( - 'conditions' => array('warninglist_id' => $id), - 'fields' => array('WarninglistEntry.value') - )); + $entries = $this->WarninglistEntry->find( + 'column', + [ + 'conditions' => ['warninglist_id' => $id], + 'fields' => ['WarninglistEntry.value'], + ] + ); $this->cacheWarninglistEntries($entries, $id); + return $entries; } /** * For 'hostname', 'string' and 'cidr' warninglist type, values are just in keys to save memory. * - * @param array $warninglist + * @param array $warninglist the warninglist * @return array */ public function getFilteredEntries(array $warninglist) @@ -579,13 +659,13 @@ class WarninglistsTable extends AppTable $output[$v] = true; } $values = $output; - } else if ($warninglist['Warninglist']['type'] === 'string') { + } elseif ($warninglist['Warninglist']['type'] === 'string') { $output = []; foreach ($values as $v) { $output[$v] = true; } $values = $output; - } else if ($warninglist['Warninglist']['type'] === 'cidr') { + } elseif ($warninglist['Warninglist']['type'] === 'cidr') { $values = new CidrTool($values); } @@ -595,7 +675,7 @@ class WarninglistsTable extends AppTable } /** - * @param array $object + * @param array $object the object to check * @param array|null $warninglists If null, all enabled warninglists will be used * @return array */ @@ -610,25 +690,26 @@ class WarninglistsTable extends AppTable if (in_array('ALL', $list['types'], true) || in_array($object['type'], $list['types'], true)) { $result = $this->checkValue($this->getFilteredEntries($list), $object['value'], $object['type'], $list['Warninglist']['type']); if ($result !== false) { - $object['warnings'][] = array( + $object['warnings'][] = [ 'match' => $result[0], 'value' => $result[1], 'warninglist_id' => $list['Warninglist']['id'], 'warninglist_name' => $list['Warninglist']['name'], 'warninglist_category' => $list['Warninglist']['category'], - ); + ]; } } } } + return $object; } /** - * @param array|CidrTool $listValues - * @param string $value - * @param string $type - * @param string $listType + * @param array|\App\Lib\Tools\CidrTool $listValues the values of the list + * @param string $value the value to check + * @param string $type the type of the attribute + * @param string $listType the type of the list * @return array|false [Matched value, attribute value that matched] */ public function checkValue($listValues, $value, $type, $listType) @@ -636,7 +717,7 @@ class WarninglistsTable extends AppTable if ($type === 'malware-sample' || strpos($type, '|') !== false) { $value = explode('|', $value, 2); } else { - $value = array($value); + $value = [$value]; } foreach ($value as $v) { if ($listType === 'cidr') { @@ -656,14 +737,15 @@ class WarninglistsTable extends AppTable return [$result, $v]; } } + return false; } /** * Check for exact match. * - * @param array $listValues - * @param string $value + * @param array $listValues the values of the list + * @param string $value the value to check * @return false */ private function __evalString($listValues, $value) @@ -671,9 +753,17 @@ class WarninglistsTable extends AppTable if (isset($listValues[$value])) { return $value; } + return false; } + /** + * Check for substring match. + * + * @param array $listValues the values of the list + * @param string $value the value to check + * @return false + */ private function __evalSubString($listValues, $value) { foreach ($listValues as $listValue) { @@ -681,9 +771,17 @@ class WarninglistsTable extends AppTable return $listValue; } } + return false; } + /** + * Check for hostname match. + * + * @param array $listValues the values of the list + * @param string $value the value to check + * @return false + */ private function __evalHostname($listValues, $value) { // php's parse_url is dumb, so let's use some hacky workarounds @@ -711,9 +809,17 @@ class WarninglistsTable extends AppTable return $rebuilt; } } + return false; } + /** + * Check for regex match. + * + * @param array $listValues the values of the list + * @param string $value the value to check + * @return false + */ private function __evalRegex($listValues, $value) { foreach ($listValues as $listValue) { @@ -721,6 +827,7 @@ class WarninglistsTable extends AppTable return $listValue; } } + return false; } @@ -729,10 +836,13 @@ class WarninglistsTable extends AppTable */ public function fetchTLDLists() { - $tldLists = $this->find('column', array( - 'conditions' => array('name IN' => Warninglist::TLDS), - 'fields' => array('id') - )); + $tldLists = $this->find( + 'column', + [ + 'conditions' => ['name IN' => Warninglist::TLDS], + 'fields' => ['id'], + ] + ); $tlds = []; foreach ($tldLists as $warninglistId) { $tlds = array_merge($tlds, $this->getWarninglistEntries($warninglistId)); @@ -741,6 +851,7 @@ class WarninglistsTable extends AppTable if (!in_array('onion', $tlds, true)) { $tlds[] = 'onion'; } + return $tlds; } @@ -749,19 +860,23 @@ class WarninglistsTable extends AppTable */ public function fetchSecurityVendorDomains() { - $securityVendorList = $this->find('column', array( - 'conditions' => array('name' => 'List of known domains used by automated malware analysis services & security vendors'), - 'fields' => array('id') - )); + $securityVendorList = $this->find( + 'column', + [ + 'conditions' => ['name' => 'List of known domains used by automated malware analysis services & security vendors'], + 'fields' => ['id'], + ] + ); $domains = []; foreach ($securityVendorList as $warninglistId) { $domains = array_merge($domains, $this->getWarninglistEntries($warninglistId)); } + return $domains; } /** - * @param array $attribute + * @param array $attribute the attribute to filter * @param array|null $warninglists If null, all enabled warninglists will be used * @return bool */ @@ -779,33 +894,35 @@ class WarninglistsTable extends AppTable } } } + return true; } - public function missingTldLists() - { - $missingTldLists = array(); - foreach (Warninglist::TLDS as $tldList) { - $temp = $this->find('first', array( - 'recursive' => -1, - 'conditions' => array('Warninglist.name' => $tldList), - 'fields' => array('Warninglist.id') - )); - if (empty($temp)) { - $missingTldLists[] = $tldList; - } - } - return $missingTldLists; - } + // public function missingTldLists() + // { + // $missingTldLists = []; + // foreach (Warninglist::TLDS as $tldList) { + // $temp = $this->find('first', [ + // 'recursive' => -1, + // 'conditions' => ['Warninglist.name' => $tldList], + // 'fields' => ['Warninglist.id'], + // ]); + // if (empty($temp)) { + // $missingTldLists[] = $tldList; + // } + // } + + // return $missingTldLists; + // } /** - * @param array|null $data - * @param bool $validate - * @param array $fieldList + * @param array|null $data the data to save + * @param bool $validate whether to validate the data + * @param array $fieldList the fields to save * @return array|bool|mixed|null - * @throws Exception + * @throws \Exception */ - public function save($data = null, $validate = true, $fieldList = array()) + public function save($data = null, $validate = true, $fieldList = []) { $db = $this->getDataSource(); $transactionBegun = $db->begin(); @@ -825,7 +942,7 @@ class WarninglistsTable extends AppTable $this->WarninglistEntry->deleteAll(['warninglist_id' => $id], false); $entriesToInsert = []; foreach ($data['WarninglistEntry'] as $entry) { - $entriesToInsert[] = [$entry['value'], isset($entry['comment']) ? $entry['comment'] : null, $id]; + $entriesToInsert[] = [$entry['value'], $entry['comment'] ?? null, $id]; } $db->insertMulti( $this->WarninglistEntry->table, @@ -864,7 +981,10 @@ class WarninglistsTable extends AppTable } /** - * @param bool $created + * post full save actions + * + * @param bool $created True for a new creation, false for an edit + * @param array $data the data that was saved * @return void */ private function afterFullSave($created, array $data) @@ -874,17 +994,20 @@ class WarninglistsTable extends AppTable } if ($this->pubToZmq('warninglist')) { - $warninglist = $this->find('first', [ - 'conditions' => ['id' => $data['Warninglist']['id']], - 'contains' => ['WarninglistEntry', 'WarninglistType'], - ]); + $warninglist = $this->find( + 'first', + [ + 'conditions' => ['id' => $data['Warninglist']['id']], + 'contains' => ['WarninglistEntry', 'WarninglistType'], + ] + ); $pubSubTool = $this->getPubSubTool(); $pubSubTool->warninglist_save($warninglist, $created ? 'add' : 'edit'); } } /** - * @param string $input + * @param string $input the input to parse * @return array */ public function parseFreetext($input) @@ -896,15 +1019,21 @@ class WarninglistsTable extends AppTable $entries = []; foreach (explode("\n", trim($input)) as $entry) { - $valueAndComment = explode("#", $entry, 2); + $valueAndComment = explode('#', $entry, 2); $entries[] = [ 'value' => trim($valueAndComment[0]), 'comment' => count($valueAndComment) === 2 ? trim($valueAndComment[1]) : null, ]; } + return $entries; } + /** + * get the categories + * + * @return array + */ public function categories() { return [ diff --git a/templates/Users/view.php b/templates/Users/view.php index 552519723..def1aa998 100644 --- a/templates/Users/view.php +++ b/templates/Users/view.php @@ -64,7 +64,7 @@ $fields = [ 'type' => 'boolean', 'pill' => true ], - + ]; echo $this->element( '/genericElements/SingleViews/single_view', diff --git a/templates/Warninglists/index.php b/templates/Warninglists/index.php new file mode 100644 index 000000000..0c15385a7 --- /dev/null +++ b/templates/Warninglists/index.php @@ -0,0 +1,175 @@ + __('ID'), + 'sort' => 'id', + 'class' => 'short', + 'data_path' => 'id', + // 'element' => 'links', + // 'url' => $baseurl . '/warninglists/view/%s', + ], + [ + 'name' => __('Name'), + 'sort' => 'name', + 'data_path' => 'name', + ], + [ + 'name' => __('Version'), + 'sort' => 'version', + 'class' => 'short', + 'data_path' => 'version', + ], + [ + 'name' => __('Description'), + 'data_path' => 'description', + ], + [ + 'name' => __('Category'), + 'sort' => 'category', + 'class' => 'short', + 'element' => 'custom', + 'function' => function (array|\App\Model\Entity\Warninglist $row) use ($possibleCategories) { + return $possibleCategories[$row['category']]; + }, + ], + [ + 'name' => __('Type'), + 'sort' => 'type', + 'class' => 'short', + 'data_path' => 'type', + ], + [ + 'name' => __('Entries'), + 'sort' => 'warninglist_entry_count', + 'class' => 'short', + 'data_path' => 'warninglist_entry_count', + ], + [ + 'name' => __('Default'), + 'sort' => 'default', + 'class' => 'short', + 'element' => 'boolean', + 'data_path' => 'default', + ], + [ + 'name' => __('Enabled'), + 'sort' => 'enabled', + 'class' => 'short', + 'element' => 'toggle', + 'data_path' => 'enabled', + 'url' => '/warninglists/toggleEnable', + 'url_params_vars' => [['datapath' => ['id']]], + 'requirement' => $loggedUser['Role']['perm_site_admin'], + ], +]; + +// echo '
'; +// if ($isSiteAdmin) { +// echo '
'; +// echo $this->Form->create('Warninglist', ['url' => $baseurl . '/warninglists/toggleEnable']); +// echo $this->Form->input('data', ['label' => false, 'style' => 'display:none;']); +// echo $this->Form->end(); +// echo '
'; +// } +echo $this->element( + '/genericElements/IndexTable/index_table', + [ + 'data' => [ + 'data' => $data, + 'top_bar' => [ + 'children' => [ + // FIXME chri filtering + // [ + // 'type' => 'simple', + // 'children' => [ + // [ + // 'url' => $baseurl . '/warninglists/index', + // 'text' => __('All'), + // 'active' => !isset($passedArgsArray['enabled']), + // ], + // [ + // 'url' => $baseurl . '/warninglists/index/enabled:1', + // 'text' => __('Enabled'), + // 'active' => isset($passedArgsArray['enabled']) && $passedArgsArray['enabled'] === '1', + // ], + // [ + // 'url' => $baseurl . '/warninglists/index/enabled:0', + // 'text' => __('Disabled'), + // 'active' => isset($passedArgsArray['enabled']) && $passedArgsArray['enabled'] === '0', + // ], + // ], + // ], + [ + 'type' => 'search', + 'button' => __('Filter'), + 'placeholder' => __('Enter value to search'), + 'data' => '', + 'searchKey' => 'value', + 'allowFilering' => true, + ], + ], + ], + 'title' => __('Warninglists'), + 'primary_id_path' => 'id', + 'fields' => $fields, + 'actions' => [ + [ + 'url' => '/warninglists/view', + 'url_params_data_paths' => ['id'], + 'icon' => 'eye', + ], + // [ + // 'title' => __('Enable'), + // 'icon' => 'play', + // 'onclick' => sprintf('toggleSetting(%s, \'%s\', \'%s\')', 'event', 'warninglist_enable', '[onclick_params_data_path]'), + // 'onclick_params_data_path' => 'id', + // 'complex_requirement' => [ + // 'function' => function ($row, $options) use ($loggedUser) { + // return $loggedUser['Role']['perm_site_admin'] && !$options['datapath']['enabled']; + // }, + // 'options' => [ + // 'datapath' => [ + // 'orgc' => 'Event.orgc_id', + // 'enabled' => 'enabled', + // ], + // ], + // ], + // ], + // [ + // 'title' => __('Disable'), + // 'icon' => 'stop', + // 'onclick' => sprintf('toggleSetting(%s, \'%s\', \'%s\')', 'event', 'warninglist_enable', '[onclick_params_data_path]'), + // 'onclick_params_data_path' => 'id', + // 'complex_requirement' => [ + // 'function' => function ($row, $options) use ($loggedUser) { + // return $loggedUser['Role']['perm_site_admin'] && $options['datapath']['enabled']; + // }, + // 'options' => [ + // 'datapath' => [ + // 'enabled' => 'enabled', + // ], + // ], + // ], + // ], + [ + 'open_modal' => '/warninglists/edit/[onclick_params_data_path]', + 'modal_params_data_path' => 'id', + 'icon' => 'edit', + 'complex_requirement' => [ + 'function' => function ($row) use ($loggedUser) { + return $row['default'] == 0 && ($loggedUser['Role']['perm_warninglist'] || $loggedUser['Role']['perm_site_admin']); + }, + ], + ], + [ + 'open_modal' => '/warninglists/delete/[onclick_params_data_path]', + 'modal_params_data_path' => 'id', + 'icon' => 'trash', + 'requirement' => $loggedUser['Role']['perm_site_admin'], + ], + ], + ], + ] +); +// echo '
'; +// echo $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'warninglist', 'menuItem' => 'index']); diff --git a/templates/Warninglists/view.php b/templates/Warninglists/view.php new file mode 100644 index 000000000..f81828f64 --- /dev/null +++ b/templates/Warninglists/view.php @@ -0,0 +1,105 @@ +element( + 'genericElements/SingleViews/single_view', + [ + 'title' => __('Warninglist View'), + 'data' => $entity, + 'fields' => [ + [ + 'key' => __('ID'), + 'path' => 'id' + ], + [ + 'key' => __('Name'), + 'path' => 'name' + ], + [ + 'key' => __('Description'), + 'path' => 'description' + ], + [ + 'key' => __('Version'), + 'path' => 'version' + ], + [ + 'key' => __('Category'), + 'path' => 'category', + 'function' => function (array|\App\Model\Entity\Warninglist $row) use ($possibleCategories) { + return $possibleCategories[$row['category']]; + } + ], + [ + 'key' => __('Type'), + 'path' => 'type' + ], + [ + 'key' => __('Accepted attribute types'), + 'path' => 'type' // FIXME + ] + ], + 'children' => [ + [ + 'url' => '/warninglists/preview_entries/{{0}}', + 'url_params' => ['id'], + 'title' => __('Values'), + 'elementId' => 'preview_entries_container' + ] + ] + ] +); + + +// $types = implode(', ', array_column($warninglist['WarninglistType'], 'type')); +// $table_data = [ +// ['key' => __('ID'), 'value' => $entity['id']], +// ['key' => __('Name'), 'value' => $entity['name']], +// ['key' => __('Description'), 'value' => $entity['description']], +// ['key' => __('Version'), 'value' => $entity['version']], +// ['key' => __('Category'), 'value' => $possibleCategories[$entity['category']]], +// ['key' => __('Type'), 'value' => $entity['type']], +// ['key' => __('Accepted attribute types'), 'value' => $types], +// [ +// 'key' => __('Enabled'), +// 'boolean' => $entity['enabled'], +// 'html' => $me['Role']['perm_warninglist'] ? sprintf( +// ' %s', +// $baseurl, +// h($warninglist['Warninglist']['id']), +// $entity['enabled'] ? '' : '/1', +// $entity['enabled'] ? __('Disable') : __('Enable'), +// $entity['enabled'] ? __('Disable') : __('Enable') +// ) : '', +// ], +// ]; + +// $values = []; +// foreach ($warninglist['WarninglistEntry'] as $entry) { +// $value = '' . h($entry['value']) . ''; +// if ($entry['comment']) { +// $value .= ' # ' . h($entry['comment']) . ''; +// } +// $values[] = $value; +// } + +// echo '
'; +// echo sprintf( +// '
%s

%s

', +// sprintf( +// '

%s

%s', +// h($warninglist['Warninglist']['name']), +// $this->element('genericElements/viewMetaTable', ['table_data' => $table_data]) +// ), +// __('Values') +// ); +// echo implode('
', $values); +// echo '
'; +// echo $this->element( +// '/genericElements/SideMenu/side_menu', +// [ +// 'menuList' => 'warninglist', +// 'menuItem' => 'view', +// 'id' => $entity['id'], +// 'isDefault' => $entity['default'] == 1, +// ] +// ); diff --git a/tests/Fixture/WarninglistEntriesFixture.php b/tests/Fixture/WarninglistEntriesFixture.php new file mode 100644 index 000000000..d91584113 --- /dev/null +++ b/tests/Fixture/WarninglistEntriesFixture.php @@ -0,0 +1,89 @@ +records = [ + // enabled + [ + 'id' => self::WARNINGLIST_ENTRY_1_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_CIDR_1_ID, + 'value' => '1.1.1.0/24', + 'comment' => '', + ], + [ + 'id' => self::WARNINGLIST_ENTRY_2_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_CIDR_1_ID, + 'value' => '2.2.0.0/16', + 'comment' => '', + ], + [ + 'id' => self::WARNINGLIST_ENTRY_3_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_CIDR_1_ID, + 'value' => '1.1.1.0/32', + 'comment' => '', + ], + // disabled + [ + 'id' => self::WARNINGLIST_ENTRY_4_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_CIDR_2_ID, + 'value' => '4.4.4.0/24', + 'comment' => '', + ], + // hostname + [ + 'id' => self::WARNINGLIST_ENTRY_5_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_HOSTNAME_ID, + 'value' => 'vm.misp-project.org', + 'comment' => '', + ], + // substr + [ + 'id' => self::WARNINGLIST_ENTRY_6_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_SUBSTR_ID, + 'value' => 'misp', + 'comment' => '', + ], + // str + [ + 'id' => self::WARNINGLIST_ENTRY_7_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_STR_ID, + 'value' => 'vm.misp-project.org', + 'comment' => '', + ], + // regex + [ + 'id' => self::WARNINGLIST_ENTRY_8_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_REGEX_ID, + 'value' => '/misp-[a-z]\.org$/', + 'comment' => '', + ], + ]; + + // TODO import warninglists from files to have larger datasets + + parent::init(); + } +} diff --git a/tests/Fixture/WarninglistTypesFixture.php b/tests/Fixture/WarninglistTypesFixture.php new file mode 100644 index 000000000..1f9a70665 --- /dev/null +++ b/tests/Fixture/WarninglistTypesFixture.php @@ -0,0 +1,84 @@ +records = [ + // CIDR + [ + 'id' => self::WARNINGLIST_TYPE_1_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_CIDR_1_ID, + 'type' => 'ip-src', + ], + [ + 'id' => self::WARNINGLIST_TYPE_2_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_CIDR_1_ID, + 'type' => 'ip-dst', + ], + [ + 'id' => self::WARNINGLIST_TYPE_3_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_CIDR_1_ID, + 'type' => 'domain|ip', + ], + // HOSTNAME + [ + 'id' => self::WARNINGLIST_TYPE_4_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_HOSTNAME_ID, + 'type' => 'url', + ], + [ + 'id' => self::WARNINGLIST_TYPE_5_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_HOSTNAME_ID, + 'type' => 'domain|ip', + ], + [ + 'id' => self::WARNINGLIST_TYPE_6_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_HOSTNAME_ID, + 'type' => 'hostname', + ], + [ + 'id' => self::WARNINGLIST_TYPE_7_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_HOSTNAME_ID, + 'type' => 'domain', + ], + // SUBSTR + [ + 'id' => self::WARNINGLIST_TYPE_8_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_SUBSTR_ID, + 'type' => 'domain', + ], + // STR + [ + 'id' => self::WARNINGLIST_TYPE_9_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_STR_ID, + 'type' => 'domain', + ], + // REGEX + [ + 'id' => self::WARNINGLIST_TYPE_10_ID, + 'warninglist_id' => WarninglistsFixture::WARNINGLIST_REGEX_ID, + 'type' => 'domain', + ], + ]; + + parent::init(); + } +} diff --git a/tests/Fixture/WarninglistsFixture.php b/tests/Fixture/WarninglistsFixture.php new file mode 100644 index 000000000..26e6902a2 --- /dev/null +++ b/tests/Fixture/WarninglistsFixture.php @@ -0,0 +1,98 @@ +records = [ + [ + 'id' => self::WARNINGLIST_CIDR_1_ID, + 'name' => self::WARNINGLIST_CIDR_1_NAME, + 'type' => 'cidr', + 'description' => 'test disabled cidr warninglist description', + 'version' => 1, + 'enabled' => false, + 'default' => false, + 'category' => Warninglist::CATEGORY_FALSE_POSITIVE, + ], + [ + 'id' => self::WARNINGLIST_CIDR_2_ID, + 'name' => self::WARNINGLIST_CIDR_2_NAME, + 'type' => 'cidr', + 'description' => 'test enabled cidr warninglist description', + 'version' => 1, + 'enabled' => true, + 'default' => false, + 'category' => Warninglist::CATEGORY_KNOWN, + ], + [ + 'id' => self::WARNINGLIST_HOSTNAME_ID, + 'name' => self::WARNINGLIST_HOSTNAME_NAME, + 'type' => 'hostname', + 'description' => 'test enabled hostname warninglist description', + 'version' => 1, + 'enabled' => true, + 'default' => false, + 'category' => Warninglist::CATEGORY_FALSE_POSITIVE, + ], + [ + 'id' => self::WARNINGLIST_SUBSTR_ID, + 'name' => self::WARNINGLIST_SUBSTR_NAME, + 'type' => 'hostname', + 'description' => 'test enabled substring warninglist description', + 'version' => 1, + 'enabled' => true, + 'default' => false, + 'category' => Warninglist::CATEGORY_FALSE_POSITIVE, + ], + [ + 'id' => self::WARNINGLIST_STR_ID, + 'name' => self::WARNINGLIST_STR_NAME, + 'type' => 'hostname', + 'description' => 'test enabled string warninglist description', + 'version' => 1, + 'enabled' => true, + 'default' => false, + 'category' => Warninglist::CATEGORY_FALSE_POSITIVE, + ], + [ + 'id' => self::WARNINGLIST_REGEX_ID, + 'name' => self::WARNINGLIST_REGEX_NAME, + 'type' => 'hostname', + 'description' => 'test enabled regex warninglist description', + 'version' => 1, + 'enabled' => true, + 'default' => false, + 'category' => Warninglist::CATEGORY_FALSE_POSITIVE, + ], + ]; + parent::init(); + } +} diff --git a/tests/TestCase/Api/Warninglists/IndexWarninglistsApiTest.php b/tests/TestCase/Api/Warninglists/IndexWarninglistsApiTest.php new file mode 100644 index 000000000..66028ba6a --- /dev/null +++ b/tests/TestCase/Api/Warninglists/IndexWarninglistsApiTest.php @@ -0,0 +1,44 @@ +skipOpenApiValidations(); + $this->setAuthToken(AuthKeysFixture::ADMIN_API_KEY); + $this->get(self::ENDPOINT); + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"name": "%s"', WarninglistsFixture::WARNINGLIST_CIDR_1_NAME)); + } + + public function testIndexWarninglistsAsUser(): void + { + $this->skipOpenApiValidations(); + $this->setAuthToken(AuthKeysFixture::REGULAR_USER_API_KEY); + $this->get(self::ENDPOINT); + $this->assertResponseOk(); + $this->assertResponseContains(sprintf('"name": "%s"', WarninglistsFixture::WARNINGLIST_CIDR_1_NAME)); + } +} diff --git a/tests/TestCase/Model/Table/WarninglistsTableTest.php b/tests/TestCase/Model/Table/WarninglistsTableTest.php new file mode 100644 index 000000000..f5a2acf38 --- /dev/null +++ b/tests/TestCase/Model/Table/WarninglistsTableTest.php @@ -0,0 +1,100 @@ +getTableLocator()->exists('Warninglists') ? [] : ['className' => WarninglistsTable::class]; + $this->Warninglists = $this->getTableLocator()->get('Warninglists', $config); + + $this->user = [ + 'id' => UsersFixture::USER_REGULAR_USER_ID, + 'org_id' => OrganisationsFixture::ORGANISATION_A_ID, + 'email' => UsersFixture::USER_REGULAR_USER_EMAIL, + ]; + } + + /** + * tearDown method + * + * @return void + */ + protected function tearDown(): void + { + unset($this->Warninglists); + + parent::tearDown(); + } + + /** + * Test initialize method + * + * @return void + */ + public function testInitialize(): void + { + $this->markTestIncomplete('Not implemented yet.'); + } + + /** + * Test validationDefault method + * + * @return void + */ + public function testValidationDefault(): void + { + $this->markTestIncomplete('Not implemented yet.'); + } + + + public function testParseFreetext(): void + { + $faker = \Faker\Factory::create(); + $items = []; + for ($i = 0; $i < 10; $i++) { + $items[] = $faker->domainName() . " #" . $faker->sentence(); + } + $items[] = ""; // empty to verify trim + $items[] = " # "; // empty to verify trim + $freetext = implode("\n", $items); + $result = WarninglistsTable::parseFreetext($freetext); + $this->assertEquals(count($result), 10); + } +}