new: [recovery] script added mock method among other changes

- also added proposals
- add/accept/discard should be fully supported now
pull/6327/head
iglocska 2020-09-18 13:42:52 +02:00
parent 1ad73c307f
commit c8b61175e8
No known key found for this signature in database
GPG Key ID: BEA224F1FEF113AC
4 changed files with 285 additions and 33 deletions

View File

@ -5624,14 +5624,19 @@ class EventsController extends AppController
}
}
public function recoverEvent($id)
public function recoverEvent($id, $mock = false)
{
if ($this->request->is('post')) {
$this->loadModel('Log');
$result = $this->Log->recoverDeletedEvent($id);
$result = $this->Log->recoverDeletedEvent($id, $mock);
$message = __('Recovery complete. Event #%s recovered, using %s log entries.', $id, $result);
if ($this->_isRest()) {
$results = $this->Event->fetchEvent($this->Auth->user(), ['eventid' => $id]);
if ($mock) {
$results = $this->Log->mockLog;
} else {
$results = $this->Event->fetchEvent($this->Auth->user(), ['eventid' => $id]);
}
return $this->RestResponse->viewData($results, $this->response->type());
} else {
$this->Flash->success(__('Recovery complete. Event #%s recovered, using %s log entries.', $id, $result));
}
@ -5641,6 +5646,10 @@ class EventsController extends AppController
$this->Flash->error(__('This action is only accessible via POST requests.'));
}
}
$this->redirect(['action' => 'showPotentialUnintendedDeletions']);
if ($mock) {
$this->set('data', $this->Log->mockLog);
} else {
$this->redirect(['action' => 'restoreDeletedEvents']);
}
}
}

View File

@ -480,8 +480,12 @@ class Log extends AppModel
return $deleted_events;
}
public function recoverDeletedEvent($id)
public function recoverDeletedEvent($id, $mock = false)
{
if ($mock) {
$this->mockRecovery = true;
$this->mockLog = [];
}
$objectMap = [];
$logEntries = [];
$this->__recoverDeletedEventContainer($id, $objectMap, $logEntries);
@ -490,6 +494,7 @@ class Log extends AppModel
$this->__recoverDeletedObjectReferences($id, $objectMap, $logEntries);
$this->__recoverDeletedTagConnectors($id, $objectMap, $logEntries, 'Event');
$this->__recoverDeletedTagConnectors($id, $objectMap, $logEntries, 'Attribute');
$this->__recoverDeletedProposals($id, $objectMap, $logEntries);
ksort($logEntries);
foreach ($logEntries as $logEntry) {
$this->{'__executeRecovery' . $logEntry['model']}($logEntry, $id);
@ -696,6 +701,47 @@ class Log extends AppModel
}
}
private function __recoverDeletedProposals($id, &$objectMap, &$logEntries)
{
$logs = $this->find('all', [
'recursive' => -1,
'conditions' => [
'model' => 'ShadowAttribute',
'title LIKE ' => '%: to Event (' . $id . '): %',
'action' => ['add']
]
]);
if (empty($logs)) {
return;
}
foreach ($logs as $log) {
$objectMap['ShadowAttribute'][$log['Log']['model_id']] = true;
}
$logs = $this->find('all', [
'recursive' => -1,
'conditions' => [
'model' => 'ShadowAttribute',
'model_id' => array_keys($objectMap['ShadowAttribute']),
'action' => ['add', 'accept', 'delete']
]
]);
foreach ($logs as $log) {
$logEntries[$log['Log']['id']] = [
'model_id' => $log['Log']['model_id'],
'model' => $log['Log']['model'],
'action' => $log['Log']['action'],
'data' => array_merge(
$this->changeParser($log['Log']['change']),
[
'timestamp' => strtotime($log['Log']['created']),
'id' => $log['Log']['model_id']
]
)
];
$objectMap['ShadowAttribute'][$log['Log']['model_id']] = true;
}
}
private function __executeRecoveryEvent($logEntry, $id)
{
@ -707,8 +753,12 @@ class Log extends AppModel
}
switch($logEntry['action']) {
case 'add':
$this->Event->create();
$this->Event->save($logEntry['data']);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'Event', 'action' => 'add', 'data' => $logEntry['data']];
} else {
$this->Event->create();
$this->Event->save($logEntry['data']);
}
break;
case 'edit':
case 'publish':
@ -725,17 +775,31 @@ class Log extends AppModel
$event['Event'][$field] = $value;
}
}
$this->Event->save($event);
$this->Event->create();
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'Event', 'action' => 'edit', 'data' => $event];
} else {
$this->Event->save($event);
}
}
break;
case 'add_tag':
$tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
$this->Event->EventTag->create();
$this->Event->EventTag->save([
'tag_id' => $tag_id,
'event_id' => $logEntry['data']['id'],
'local' => !empty($logEntry['data']['local'])
]);
$this->Event->create();
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'EventTag', 'action' => 'add', 'data' => [
'tag_id' => $tag_id,
'event_id' => $logEntry['data']['id'],
'local' => !empty($logEntry['data']['local'])
]];
} else {
$this->Event->EventTag->save([
'tag_id' => $tag_id,
'event_id' => $logEntry['data']['id'],
'local' => !empty($logEntry['data']['local'])
]);
}
break;
case 'remove_tag':
$tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
@ -747,7 +811,11 @@ class Log extends AppModel
]
]);
if (!empty($et)) {
$this->Event->EventTag->delete($et['EventTag']['id']);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'EventTag', 'action' => 'delete', 'data' => $et['EventTag']['id']];
} else {
$this->Event->EventTag->delete($et['EventTag']['id']);
}
}
break;
}
@ -769,8 +837,12 @@ class Log extends AppModel
}
switch($logEntry['action']) {
case 'add':
$this->Attribute->create();
$this->Attribute->save($logEntry['data']);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'Attribute', 'action' => 'add', 'data' => $logEntry['data']];
} else {
$this->Attribute->create();
$this->Attribute->save($logEntry['data']);
}
break;
case 'edit':
$attribute = $this->Attribute->find('first', [
@ -781,7 +853,11 @@ class Log extends AppModel
foreach ($logEntry['data'] as $field => $value) {
$attribute['Attribute'][$field] = $value;
}
$this->Attribute->save($attribute);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'Attribute', 'action' => 'edit', 'data' => $attribute];
} else {
$this->Attribute->save($attribute);
}
}
break;
case 'delete':
@ -792,17 +868,31 @@ class Log extends AppModel
if (!empty($attribute)) {
$attribute['Attribute']['deleted'] = 1;
$attribute['Attribute']['timestamp'] = $logEntry['data']['timestamp'];
$this->Attribute->save($attribute);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'Attribute', 'action' => 'delete', 'data' => $attribute];
} else {
$this->Attribute->save($attribute);
}
}
break;
case 'add_tag':
$tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
$this->Attribute->AttributeTag->create();
$this->Attribute->AttributeTag->save([
'tag_id' => $tag_id,
'attribute_id' => $logEntry['data']['id'],
'event_id' => $id,
'local' => !empty($logEntry['data']['local'])
]);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'AttributeTag', 'action' => 'add', 'data' => [
'tag_id' => $tag_id,
'attribute_id' => $logEntry['data']['id'],
'event_id' => $id,
'local' => !empty($logEntry['data']['local'])
]];
} else {
$this->Attribute->AttributeTag->create();
$this->Attribute->AttributeTag->save([
'tag_id' => $tag_id,
'attribute_id' => $logEntry['data']['id'],
'event_id' => $id,
'local' => !empty($logEntry['data']['local'])
]);
}
break;
case 'remove_tag':
$tag_id = $logEntry['data']['tag_type'] === 'galaxy' ? $this->GalaxyCluster->getTagIdByClusterId($logEntry['data']['tag_id']) : $logEntry['data']['tag_id'];
@ -815,10 +905,103 @@ class Log extends AppModel
]
]);
if (!empty($at)) {
$this->Attribute->AttributeTag->delete($at['AttributeTag']['id']);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'AttributeTag', 'action' => 'delete', 'data' => $at['AttributeTag']['id']];
} else {
$this->Attribute->AttributeTag->delete($at['AttributeTag']['id']);
}
}
break;
}
}
private function __executeRecoveryShadowAttribute($logEntry, $id)
{
if (empty($this->Attribute)) {
$this->Attribute = ClassRegistry::init('Attribute');
}
if (empty($this->ShadowAttribute)) {
$this->ShadowAttribute = ClassRegistry::init('ShadowAttribute');
}
if (!empty($logEntry['data']['value1'])) {
$logEntry['data']['value'] = $logEntry['data']['value1'];
if (!empty($logEntry['data']['value2'])) {
$logEntry .= '|' . $logEntry['data']['value2'];
}
}
switch($logEntry['action']) {
case 'add':
$logEntry['data']['value'] = $logEntry['data']['value1'];
if (!empty($logEntry['data']['value2'])) {
$logEntry['data']['value'] .= '|' . $logEntry['data']['value2'];
}
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'add', 'data' => $logEntry['data']];
} else {
$this->ShadowAttribute->create();
$this->ShadowAttribute->save($logEntry['data']);
}
break;
case 'delete':
$shadow_attribute = $this->ShadowAttribute->find('first', [
'recursive' => -1,
'conditions' => ['ShadowAttribute.id' => $logEntry['model_id']]
]);
if (!empty($shadow_attribute)) {
$shadow_attribute['ShadowAttribute']['deleted'] = 1;
$shadow_attribute['ShadowAttribute']['timestamp'] = $logEntry['data']['timestamp'];
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'delete', 'data' => $attribute];
} else {
$this->ShadowAttribute->save($attribute);
}
}
break;
case 'accept':
$shadow_attribute = $this->ShadowAttribute->find('first', [
'recursive' => -1,
'conditions' => ['ShadowAttribute.id' => $logEntry['model_id']]
]);
if (!empty($shadow_attribute['ShadowAttribute']['old_id'])) {
$attribute = $this->Attribute->find('first', [
'conditions' => ['Attribute.id' => $shadow_attribute['ShadowAttribute']['old_id']],
'recursive' => -1
]);
if (!empty($shadow_attribute['ShadowAttribute']['proposal_to_delete'])) {
$attribute['Attribute']['deleted'] = 1;
} else {
foreach(['category', 'type', 'value', 'to_ids', 'comment', 'first_seen', 'last_seen'] as $field) {
if (isset($shadow_attribute['ShadowAttribute'][$field])) {
$attribute['Attribute'][$field] = $shadow_attribute['ShadowAttribute'][$field];
}
}
}
$attribute['Attribute']['timestamp'] = $logEntry['data']['timestamp'];
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'Attribute', 'action' => 'edit', 'data' => $attribute];
} else {
$this->Attribute->save($attribute);
}
} else {
$this->Attribute->create();
$attribute = $shadow_attribute['ShadowAttribute'];
if (isset($attribute['id'])) {
unset($attribute['id']);
}
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'Attribute', 'action' => 'add', 'data' => $attribute];
} else {
$this->Attribute->save($attribute);
}
}
$shadow_attribute['ShadowAttribute']['deleted'] = 1;
$shadow_attribute['ShadowAttribute']['timestamp'] = $logEntry['data']['timestamp'];
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'ShadowAttribute', 'action' => 'delete', 'data' => $shadow_attribute];
} else {
$this->ShadowAttribute->save($shadow_attribute);
}
break;
break;
}
}
@ -829,8 +1012,12 @@ class Log extends AppModel
}
switch($logEntry['action']) {
case 'add':
$this->ObjectReference->create();
$this->ObjectReference->save($logEntry['data']);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'ObjectReference', 'action' => 'add', 'data' => $logEntry['data']];
} else {
$this->ObjectReference->create();
$this->ObjectReference->save($logEntry['data']);
}
break;
case 'edit':
$objectRef = $this->ObjectReference->find('first', [
@ -841,7 +1028,11 @@ class Log extends AppModel
foreach ($logEntry['data'] as $field => $value) {
$object['ObjectReference'][$field] = $value;
}
$this->ObjectReference->save($object);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'ObjectReference', 'action' => 'edit', 'data' => $object];
} else {
$this->ObjectReference->save($object);
}
}
break;
}
@ -854,8 +1045,12 @@ class Log extends AppModel
}
switch($logEntry['action']) {
case 'add':
$this->MispObject->create();
$this->MispObject->save($logEntry['data']);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'MispObject', 'action' => 'add', 'data' => $logEntry['data']];
} else {
$this->MispObject->create();
$this->MispObject->save($logEntry['data']);
}
break;
case 'edit':
$object = $this->MispObject->find('first', [
@ -866,7 +1061,11 @@ class Log extends AppModel
foreach ($logEntry['data'] as $field => $value) {
$object['Object'][$field] = $value;
}
$this->MispObject->save($object);
if (!empty($this->mockRecovery)) {
$this->mockLog[] = ['model' => 'MispObject', 'action' => 'add', 'data' => $object];
} else {
$this->MispObject->save($object);
}
}
break;
}

View File

@ -0,0 +1,32 @@
<?php
echo '<div class="index">';
echo '<a href="' . $baseurl . '/events/restore_deleted_events">' . __('Return to the event restoration index') . '</a>';
echo $this->element('/genericElements/IndexTable/index_table', [
'data' => [
'skip_pagination' => true,
'data' => $data,
'fields' => [
[
'name' => __('Model'),
'class' => 'short',
'data_path' => 'model'
],
[
'name' => __('Action'),
'class' => 'short',
'data_path' => 'action'
],
[
'name' => __('Data'),
'class' => 'short',
'data_path' => 'data',
'element' => 'json'
],
],
'title' => __('Recovery process log'),
'description' => __('Below is a list of actions the recovery process would take in order to restore the event.')
]
]);
echo '</div>';
echo $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'admin', 'menuItem' => 'restore_deleted_events']);
?>

View File

@ -51,8 +51,20 @@
[
'url' => $baseurl . '/events/recoverEvent',
'url_params_data_paths' => [
'event_id',
'event_id'
],
'title' => __('Mock the recovery process and output the potential changes'),
'postLink' => 1,
'postLinkConfirm' => __('Are you sure you want to mock the recovery of the event? No data will be modified, but the request might take some time.'),
'icon' => 'flask'
],
[
'url' => $baseurl . '/events/recoverEvent',
'url_params_data_paths' => [
'event_id'
],
'title' => __('Execute the recovery process'),
'postLink' => 1,
'postLinkConfirm' => __('Are you sure you want to attempt to recover the event?'),
'icon' => 'trash-restore'