Merge pull request #8278 from JakubOnderka/warninglist-import-export-test

new: [test] Warninglist import/export
pull/8347/head
Jakub Onderka 2022-05-06 20:39:07 +02:00 committed by GitHub
commit 12906c13b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 96 additions and 47 deletions

View File

@ -7,6 +7,7 @@ App::uses('JsonTool', 'Tools');
/**
* @property Server $Server
* @property Feed $Feed
* @property Warninglist $warninglist
* @property AdminSetting $AdminSetting
*/
class AdminShell extends AppShell
@ -19,6 +20,9 @@ class AdminShell extends AppShell
$parser->addSubcommand('updateJSON', array(
'help' => __('Update the JSON definitions of MISP.'),
));
$parser->addSubcommand('updateWarningLists', array(
'help' => __('Update the JSON definition of warninglists.'),
));
$parser->addSubcommand('setSetting', [
'help' => __('Set setting in PHP config file.'),
'parser' => [
@ -312,7 +316,6 @@ class AdminShell extends AppShell
public function updateWarningLists()
{
$this->ConfigLoad->execute();
$result = $this->Warninglist->update();
$success = count($result['success']);
$fails = count($result['fails']);

View File

@ -479,39 +479,70 @@ class RestResponseComponent extends Component
return [];
}
public function saveFailResponse($controller, $action, $id = false, $validationErrors, $format = false, $data = null)
/**
* @param string $controller
* @param string $action
* @param int|false $id
* @param mixed $validationErrors
* @param string|false $format
* @param mixed $data
* @return CakeResponse
* @throws Exception
*/
public function saveFailResponse($controller, $action, $id, $validationErrors, $format = false, $data = null)
{
$response = array();
$action = $this->__dissectAdminRouting($action);
$stringifiedAction = $action['action'];
if (isset(self::CONVERT_ACTION_TO_MESSAGE[$controller][$action['action']])) {
$stringifiedAction = self::CONVERT_ACTION_TO_MESSAGE[$controller][$action['action']];
if (isset(self::CONVERT_ACTION_TO_MESSAGE[$controller][$stringifiedAction])) {
$stringifiedAction = self::CONVERT_ACTION_TO_MESSAGE[$controller][$stringifiedAction];
}
$response['saved'] = false;
$response['name'] = 'Could not ' . $stringifiedAction . ' ' . Inflector::singularize($controller);
$response['message'] = $response['name'];
$message = 'Could not ' . $stringifiedAction . ' ' . Inflector::singularize($controller);
$response = [
'saved' => false,
'name' => $message,
'message' => $message,
'url' => $this->__generateURL($action, $controller, $id),
'errors' => $validationErrors,
];
if ($data !== null) {
$response['data'] = $data;
}
$response['url'] = $this->__generateURL($action, $controller, $id);
$response['errors'] = $validationErrors;
if ($id) {
$response['id'] = $id;
}
return $this->__sendResponse($response, 403, $format);
}
/**
* @param string $controller
* @param string $action
* @param int|false $id
* @param string|false $format
* @param string|false $message
* @param mixed $data
* @return CakeResponse
* @throws Exception
*/
public function saveSuccessResponse($controller, $action, $id = false, $format = false, $message = false, $data = null)
{
$action = $this->__dissectAdminRouting($action);
if (!$message) {
$message = Inflector::singularize($controller) . ' ' . $action['action'] . ((substr($action['action'], -1) == 'e') ? 'd' : 'ed');
$message = Inflector::singularize($controller) . ' ' . $action['action'] . ((substr($action['action'], -1) === 'e') ? 'd' : 'ed');
}
$response['saved'] = true;
$response['success'] = true;
$response['name'] = $message;
$response['message'] = $response['name'];
$response = [
'saved' => true,
'success' => true,
'name' => $message,
'message' => $message,
'url' => $this->__generateURL($action, $controller, $id),
];
if ($data !== null) {
$response['data'] = $data;
}
$response['url'] = $this->__generateURL($action, $controller, $id);
if ($id) {
$response['id'] = $id;
}
return $this->__sendResponse($response, 200, $format);
}

View File

@ -360,9 +360,7 @@ class WarninglistsController extends AppController
public function import()
{
if (!$this->request->is('post')) {
throw new MethodNotAllowedException(__('This function only accepts POST requests.'));
}
$this->request->allowMethod(['post']);
if (empty($this->request->data)) {
throw new BadRequestException(__('No valid data received.'));
@ -378,11 +376,11 @@ class WarninglistsController extends AppController
throw new BadRequestException(__('No valid data received: `list` field is not array'));
}
$id = $this->Warninglist->import($this->request->data);
if (is_int($id)) {
try {
$id = $this->Warninglist->import($this->request->data);
return $this->RestResponse->saveSuccessResponse('Warninglist', 'import', $id, false, __('Warninglist imported'));
} else {
return $this->RestResponse->saveFailResponse('Warninglist', 'import', false, $id);
} catch (Exception $e) {
return $this->RestResponse->saveFailResponse('Warninglist', 'import', false, $e->getMessage());
}
}

View File

@ -267,7 +267,7 @@ class Warninglist extends AppModel
public function update()
{
// Existing default warninglists
// Fetch existing default warninglists
$existingWarninglist = $this->find('all', [
'fields' => ['id', 'name', 'version', 'enabled'],
'recursive' => -1,
@ -276,7 +276,7 @@ class Warninglist extends AppModel
$existingWarninglist = array_column(array_column($existingWarninglist, 'Warninglist'), null, 'name');
$directories = glob(APP . 'files' . DS . 'warninglists' . DS . 'lists' . DS . '*', GLOB_ONLYDIR);
$updated = array('success' => [], 'fails' => []);
$result = ['success' => [], 'fails' => []];
foreach ($directories as $dir) {
$list = FileAccessTool::readJsonFromFile($dir . DS . 'list.json');
if (!isset($list['version'])) {
@ -289,19 +289,19 @@ class Warninglist extends AppModel
}
if (!isset($existingWarninglist[$list['name']]) || $list['version'] > $existingWarninglist[$list['name']]['version']) {
$current = isset($existingWarninglist[$list['name']]) ? $existingWarninglist[$list['name']] : [];
$result = $this->__updateList($list, $current);
if (is_numeric($result)) {
$updated['success'][$result] = array('name' => $list['name'], 'new' => $list['version']);
try {
$result = $this->__updateList($list, $current);
$result['success'][$result] = ['name' => $list['name'], 'new' => $list['version']];
if (!empty($current)) {
$updated['success'][$result]['old'] = $current['version'];
$result['success'][$result]['old'] = $current['version'];
}
} else {
$updated['fails'][] = array('name' => $list['name'], 'fail' => json_encode($result));
} catch (Exception $e) {
$result['fails'][] = ['name' => $list['name'], 'fail' => $e->getMessage()];
}
}
}
$this->regenerateWarninglistCaches();
return $updated;
return $result;
}
public function quickDelete($id)
@ -325,7 +325,7 @@ class Warninglist extends AppModel
/**
* Import single warninglist
* @param array $list
* @return array|int|string
* @return int Warninglist ID
* @throws Exception
*/
public function import(array $list)
@ -341,29 +341,30 @@ class Warninglist extends AppModel
}
$id = $this->__updateList($list, $existingWarninglist ? $existingWarninglist['Warninglist']: [], false);
if (is_int($id)) {
$this->regenerateWarninglistCaches($id);
}
$this->regenerateWarninglistCaches($id);
return $id;
}
/**
* @param array $list
* @param array $current
* @param array $existing
* @param bool $default
* @return array|int|string
* @return int Warninglist ID
* @throws Exception
*/
private function __updateList(array $list, array $current, $default = true)
private function __updateList(array $list, array $existing, $default = true)
{
$list['enabled'] = 0;
$warninglist = array();
if (!empty($current)) {
if ($current['enabled']) {
$warninglist = [];
if (!empty($existing)) {
if ($existing['enabled']) {
$list['enabled'] = 1;
}
$warninglist['Warninglist']['id'] = $current['id']; // keep list ID
$this->quickDelete($current['id']);
$warninglist['Warninglist']['id'] = $existing['id']; // keep list ID
// Delete all dependencies
$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');
foreach ($fieldsToSave as $fieldToSave) {
@ -374,7 +375,7 @@ class Warninglist extends AppModel
}
$this->create();
if (!$this->save($warninglist)) {
return $this->validationErrors;
throw new Exception("Could not save warninglist because of validation errors: " . json_encode($this->validationErrors));
}
$db = $this->getDataSource();
@ -404,13 +405,13 @@ class Warninglist extends AppModel
}
}
if (!$result) {
return 'Could not insert values.';
throw new Exception('Could not insert values.');
}
if (empty($list['matching_attributes'])) {
$list['matching_attributes'] = ['ALL'];
}
$values = array();
$values = [];
foreach ($list['matching_attributes'] as $type) {
$values[] = array('type' => $type, 'warninglist_id' => $warninglistId);
}

View File

@ -690,6 +690,10 @@ class TestComprehensive(unittest.TestCase):
wl = request(self.admin_misp_connector, 'POST', 'warninglists/add', data=warninglist)
check_response(wl)
exported = request(self.admin_misp_connector, 'GET', f'warninglists/export/{wl["Warninglist"]["id"]}')
self.assertIn('name', exported)
self.assertEqual('Test', exported['name'])
check_response(self.admin_misp_connector.enable_warninglist(wl["Warninglist"]["id"]))
response = self.admin_misp_connector.values_in_warninglist("1.2.3.4")
@ -717,9 +721,21 @@ class TestComprehensive(unittest.TestCase):
response = self.admin_misp_connector.values_in_warninglist("2.3.4.5")
self.assertEqual(0, len(response))
# Update by importing
response = request(self.admin_misp_connector, 'POST', f'warninglists/import', exported)
check_response(response)
response = request(self.admin_misp_connector, 'POST', f'warninglists/delete/{wl["Warninglist"]["id"]}')
check_response(response)
# Create new warninglist by importing under different name
exported["name"] = "Test2"
response = request(self.admin_misp_connector, 'POST', f'warninglists/import', exported)
check_response(response)
response = request(self.admin_misp_connector, 'POST', f'warninglists/delete/{response["id"]}')
check_response(response)
def test_protected_event(self):
event = create_simple_event()
event = check_response(self.admin_misp_connector.add_event(event))