mirror of https://github.com/MISP/MISP
fix: [Objects] editing refactored
- code made more readable - resolved an issue where objects could not be edited via the API without having the correct templatepull/5525/head
parent
38c6f05d1b
commit
03de8be19a
|
@ -360,41 +360,18 @@ class ObjectsController extends AppController
|
|||
|
||||
public function edit($id, $update_template_available=false)
|
||||
{
|
||||
if (Validation::uuid($id)) {
|
||||
$conditions = array('Object.uuid' => $id);
|
||||
} else {
|
||||
$conditions = array('Object.id' => $id);
|
||||
}
|
||||
if (!$this->userRole['perm_modify']) {
|
||||
throw new MethodNotAllowedException(__('You don\'t have permissions to edit objects.'));
|
||||
}
|
||||
$object = $this->MispObject->find('first', array(
|
||||
'conditions' => $conditions,
|
||||
'recursive' => -1,
|
||||
'contain' => array(
|
||||
'Attribute' => array(
|
||||
'conditions' => array(
|
||||
'Attribute.deleted' => 0
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
$id = $this->Toolbox->findIdByUuid($this->MispObject, $id);
|
||||
$object = $this->MispObject->fetchObjects($this->Auth->user(), array('conditions' => array('Object.id' => $id)));
|
||||
if (empty($object)) {
|
||||
throw new NotFoundException(__('Invalid object.'));
|
||||
}
|
||||
$id = $object['Object']['id'];
|
||||
$eventFindParams = array(
|
||||
'recursive' => -1,
|
||||
'fields' => array('Event.id', 'Event.uuid', 'Event.orgc_id'),
|
||||
'conditions' => array('Event.id' => $object['Object']['event_id'])
|
||||
);
|
||||
|
||||
$event = $this->MispObject->Event->find('first', $eventFindParams);
|
||||
if (empty($event) || (!$this->_isSiteAdmin() && $event['Event']['orgc_id'] != $this->Auth->user('org_id'))) {
|
||||
throw new NotFoundException(__('Invalid object.'));
|
||||
$object = $object[0];
|
||||
if ((!$this->_isSiteAdmin() && $object['Event']['orgc_id'] != $this->Auth->user('org_id'))) {
|
||||
throw new MethodNotAllowedException(__('Insufficient permissions to edit this object.'));
|
||||
}
|
||||
$event = $this->MispObject->Event->fetchEvent($this->Auth->user(), array('eventid' => $object['Event']['id'], 'metadata' => 1))[0];
|
||||
if (!$this->_isRest()) {
|
||||
$this->MispObject->Event->insertLock($this->Auth->user(), $event['Event']['id']);
|
||||
$this->MispObject->Event->insertLock($this->Auth->user(), $object['Event']['id']);
|
||||
}
|
||||
$template = $this->MispObject->ObjectTemplate->find('first', array(
|
||||
'conditions' => array(
|
||||
|
@ -406,130 +383,28 @@ class ObjectsController extends AppController
|
|||
'ObjectTemplateElement'
|
||||
)
|
||||
));
|
||||
if (empty($template)) {
|
||||
if (empty($template) && !$this->_isRest()) {
|
||||
$this->Flash->error('Object cannot be edited, no valid template found.');
|
||||
$this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id']));
|
||||
}
|
||||
|
||||
$newer_template = $this->MispObject->ObjectTemplate->find('first', array(
|
||||
'conditions' => array(
|
||||
'ObjectTemplate.uuid' => $object['Object']['template_uuid'],
|
||||
'ObjectTemplate.version >' => $object['Object']['template_version'],
|
||||
),
|
||||
'recursive' => -1,
|
||||
'contain' => array(
|
||||
'ObjectTemplateElement'
|
||||
),
|
||||
'order' => array('ObjectTemplate.version DESC')
|
||||
));
|
||||
if (!empty($newer_template)) {
|
||||
$newer_template_version = $newer_template['ObjectTemplate']['version'];
|
||||
// ignore IDs for comparison
|
||||
$cur_template_temp = Hash::remove(Hash::remove($template['ObjectTemplateElement'], '{n}.id'), '{n}.object_template_id');
|
||||
$newer_template_temp = Hash::remove(Hash::remove($newer_template['ObjectTemplateElement'], '{n}.id'), '{n}.object_template_id');
|
||||
|
||||
$template_difference = array();
|
||||
// check how current template is included in the newer
|
||||
foreach ($cur_template_temp as $cur_obj_rel) {
|
||||
$flag_sim = false;
|
||||
foreach ($newer_template_temp as $newer_obj_rel) {
|
||||
$tmp = Hash::diff($cur_obj_rel, $newer_obj_rel);
|
||||
if (count($tmp) == 0) {
|
||||
$flag_sim = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$flag_sim) {
|
||||
$template_difference[] = $cur_obj_rel;
|
||||
}
|
||||
}
|
||||
|
||||
$updateable_attribute = $object['Attribute'];
|
||||
$not_updateable_attribute = array();
|
||||
if (!empty($template_difference)) { // older template not completely embeded in newer
|
||||
foreach ($template_difference as $temp_diff_element) {
|
||||
foreach ($object['Attribute'] as $i => $attribute) {
|
||||
if (
|
||||
$attribute['object_relation'] == $temp_diff_element['object_relation']
|
||||
&& $attribute['type'] == $temp_diff_element['type']
|
||||
) { // This attribute cannot be merged automatically
|
||||
$attribute['merge-possible'] = false;
|
||||
$not_updateable_attribute[] = $attribute;
|
||||
unset($updateable_attribute[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->set('updateable_attribute', $updateable_attribute);
|
||||
$this->set('not_updateable_attribute', $not_updateable_attribute);
|
||||
if ($update_template_available) { // template version bump requested
|
||||
$template = $newer_template; // bump the template version
|
||||
}
|
||||
} else {
|
||||
$newer_template_version = false;
|
||||
$templateData = $this->MispObject->resolveUpdatedTemplate($template, $object, $update_template_available);
|
||||
$this->set('updateable_attribute', $templateData['updateable_attribute']);
|
||||
$this->set('not_updateable_attribute', $templateData['not_updateable_attribute']);
|
||||
if (isset($this->params['named']['revised_object'])) {
|
||||
$revisedData = $this->MispObject->reviseObject($this->params['named']['revised_object'], $object);
|
||||
$this->set('revised_object', $revisedData['revised_object_both']);
|
||||
$object = $revisedData['object'];
|
||||
}
|
||||
|
||||
if (isset($this->params['named']['revised_object'])) { // revised object data to be injected
|
||||
$revised_object = json_decode(base64_decode($this->params['named']['revised_object']), true);
|
||||
$revised_object_both = array('mergeable' => array(), 'notMergeable' => array());
|
||||
|
||||
// Loop through attributes to inject and perform the correct action
|
||||
// (inject, duplicate, add warnings, ...) when applicable
|
||||
foreach ($revised_object['Attribute'] as $attribute_to_inject) {
|
||||
$flag_no_collision = true;
|
||||
foreach ($object['Attribute'] as $attribute) {
|
||||
if (
|
||||
$attribute['object_relation'] == $attribute_to_inject['object_relation']
|
||||
&& $attribute['type'] == $attribute_to_inject['type']
|
||||
&& $attribute['value'] !== $attribute_to_inject['value']
|
||||
) { // Collision on value
|
||||
$multiple = !empty(Hash::extract($template['ObjectTemplateElement'], sprintf('{n}[object_relation=%s][type=%s][multiple=true]', $attribute['object_relation'], $attribute['type'])));
|
||||
if ($multiple) { // if multiple is set, check if an entry exists already
|
||||
$flag_entry_exists = false;
|
||||
foreach ($object['Attribute'] as $attr) {
|
||||
if (
|
||||
$attr['object_relation'] == $attribute_to_inject['object_relation']
|
||||
&& $attr['type'] == $attribute_to_inject['type']
|
||||
&& $attr['value'] === $attribute_to_inject['value']
|
||||
) {
|
||||
$flag_entry_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$flag_entry_exists) { // entry does no exists, can be duplicated
|
||||
$attribute_to_inject['is_multiple'] = true;
|
||||
$revised_object_both['mergeable'][] = $attribute_to_inject;
|
||||
$object['Attribute'][] = $attribute_to_inject;
|
||||
}
|
||||
} else { // Collision on value, multiple not set => propose overwrite
|
||||
$attribute_to_inject['current_value'] = $attribute['value'];
|
||||
$attribute_to_inject['merge-possible'] = true; // the user can still swap value
|
||||
$revised_object_both['notMergeable'][] = $attribute_to_inject;
|
||||
}
|
||||
$flag_no_collision = false;
|
||||
} else if (
|
||||
$attribute['object_relation'] == $attribute_to_inject['object_relation']
|
||||
&& $attribute['type'] == $attribute_to_inject['type']
|
||||
&& $attribute['value'] === $attribute_to_inject['value']
|
||||
) { // all good, they are basically the same, do nothing
|
||||
$revised_object_both['mergeable'][] = $attribute_to_inject;
|
||||
$flag_no_collision = false;
|
||||
}
|
||||
}
|
||||
if ($flag_no_collision) { // no collision, nor equalities => inject it straight away
|
||||
$revised_object_both['mergeable'][] = $attribute_to_inject;
|
||||
$object['Attribute'][] = $attribute_to_inject;
|
||||
}
|
||||
}
|
||||
$this->set('revised_object', $revised_object_both);
|
||||
if (!empty($templateData['template'])) {
|
||||
$template = $this->MispObject->prepareTemplate($templateData['template'], $object);
|
||||
}
|
||||
$template = $this->MispObject->prepareTemplate($template, $object);
|
||||
$enabledRows = false;
|
||||
|
||||
if ($this->request->is('post') || $this->request->is('put')) {
|
||||
if (isset($this->request->data['request'])) {
|
||||
$this->request->data = $this->request->data['request'];
|
||||
}
|
||||
if (empty($this->request->data['Object'])) {
|
||||
$this->request->data['Object'] = $this->request->data;
|
||||
}
|
||||
if (isset($this->request->data['Object']['data'])) {
|
||||
$this->request->data = json_decode($this->request->data['Object']['data'], true);
|
||||
}
|
||||
|
@ -542,32 +417,23 @@ class ObjectsController extends AppController
|
|||
// we pre-validate the attributes before we create an object at this point
|
||||
// This allows us to stop the process and return an error (API) or return
|
||||
// to the add form
|
||||
if (empty($error)) {
|
||||
if ($this->_isRest()) {
|
||||
if (is_numeric($objectToSave)) {
|
||||
$objectToSave = $this->MispObject->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('Object.id' => $id),
|
||||
'contain' => array(
|
||||
'Attribute' => array(
|
||||
'fields' => $this->MispObject->Attribute->defaultFields
|
||||
)
|
||||
)
|
||||
));
|
||||
if (!empty($objectToSave)) {
|
||||
$objectToSave['Object']['Attribute'] = $objectToSave['Attribute'];
|
||||
unset($objectToSave['Attribute']);
|
||||
}
|
||||
$this->MispObject->Event->unpublishEvent($object['Object']['event_id']);
|
||||
return $this->RestResponse->viewData($objectToSave, $this->response->type());
|
||||
} else {
|
||||
return $this->RestResponse->saveFailResponse('Objects', 'add', false, $id, $this->response->type());
|
||||
if ($this->_isRest()) {
|
||||
if (is_numeric($objectToSave)) {
|
||||
$objectToSave = $this->MispObject->fetchObjects($this->Auth->user(), array('conditions' => array('Object.id' => $id)));
|
||||
if (!empty($objectToSave)) {
|
||||
$objectToSave = $objectToSave[0];
|
||||
$objectToSave['Object']['Attribute'] = $objectToSave['Attribute'];
|
||||
unset($objectToSave['Attribute']);
|
||||
}
|
||||
$this->MispObject->Event->unpublishEvent($objectToSave['Object']['event_id']);
|
||||
return $this->RestResponse->viewData($objectToSave, $this->response->type());
|
||||
} else {
|
||||
$this->MispObject->Event->unpublishEvent($object['Object']['event_id']);
|
||||
$this->Flash->success('Object saved.');
|
||||
$this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id']));
|
||||
return $this->RestResponse->saveFailResponse('Objects', 'add', false, $id, $this->response->type());
|
||||
}
|
||||
} else {
|
||||
$this->MispObject->Event->unpublishEvent($object['Object']['event_id']);
|
||||
$this->Flash->success('Object saved.');
|
||||
$this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id']));
|
||||
}
|
||||
} else {
|
||||
$enabledRows = array();
|
||||
|
@ -601,7 +467,7 @@ class ObjectsController extends AppController
|
|||
$this->set('action', 'edit');
|
||||
$this->set('object', $object);
|
||||
$this->set('update_template_available', $update_template_available);
|
||||
$this->set('newer_template_version', $newer_template_version);
|
||||
$this->set('newer_template_version', empty($templateData['newer_template_version']) ? false : $templateData['newer_template_version']);
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
|
|
|
@ -839,4 +839,130 @@ class MispObject extends AppModel
|
|||
return $saved_object['Object']['id'];
|
||||
|
||||
}
|
||||
|
||||
public function resolveUpdatedTemplate($template, $object, $update_template_available = false)
|
||||
{
|
||||
$toReturn = array(
|
||||
'updateable_attribute' => false,
|
||||
'not_updateable_attribute' => false,
|
||||
'newer_template_version' => false,
|
||||
'template' => $template
|
||||
);
|
||||
if (!empty($template)) {
|
||||
$newer_template = $this->ObjectTemplate->find('first', array(
|
||||
'conditions' => array(
|
||||
'ObjectTemplate.uuid' => $object['Object']['template_uuid'],
|
||||
'ObjectTemplate.version >' => $object['Object']['template_version'],
|
||||
),
|
||||
'recursive' => -1,
|
||||
'contain' => array(
|
||||
'ObjectTemplateElement'
|
||||
),
|
||||
'order' => array('ObjectTemplate.version DESC')
|
||||
));
|
||||
if (!empty($newer_template)) {
|
||||
$toReturn['newer_template_version'] = $newer_template['ObjectTemplate']['version'];
|
||||
// ignore IDs for comparison
|
||||
$cur_template_temp = Hash::remove(Hash::remove($template['ObjectTemplateElement'], '{n}.id'), '{n}.object_template_id');
|
||||
$newer_template_temp = Hash::remove(Hash::remove($newer_template['ObjectTemplateElement'], '{n}.id'), '{n}.object_template_id');
|
||||
|
||||
$template_difference = array();
|
||||
// check how current template is included in the newer
|
||||
foreach ($cur_template_temp as $cur_obj_rel) {
|
||||
$flag_sim = false;
|
||||
foreach ($newer_template_temp as $newer_obj_rel) {
|
||||
$tmp = Hash::diff($cur_obj_rel, $newer_obj_rel);
|
||||
if (count($tmp) == 0) {
|
||||
$flag_sim = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$flag_sim) {
|
||||
$template_difference[] = $cur_obj_rel;
|
||||
}
|
||||
}
|
||||
|
||||
$toReturn['updateable_attribute'] = $object['Attribute'];
|
||||
$toReturn['not_updateable_attribute'] = array();
|
||||
} else {
|
||||
$toReturn['newer_template_version'] = false;
|
||||
}
|
||||
if (!empty($template_difference)) { // older template not completely embeded in newer
|
||||
foreach ($template_difference as $temp_diff_element) {
|
||||
foreach ($object['Attribute'] as $i => $attribute) {
|
||||
if (
|
||||
$attribute['object_relation'] == $temp_diff_element['object_relation']
|
||||
&& $attribute['type'] == $temp_diff_element['type']
|
||||
) { // This attribute cannot be merged automatically
|
||||
$attribute['merge-possible'] = false;
|
||||
$toReturn['not_updateable_attribute'][] = $attribute;
|
||||
unset($toReturn['updateable_attribute'][$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($update_template_available) { // template version bump requested
|
||||
$toReturn['template'] = $newer_template; // bump the template version
|
||||
}
|
||||
}
|
||||
return $toReturn;
|
||||
}
|
||||
|
||||
public function reviseObject($revised_object, $object) {
|
||||
$revised_object = json_decode(base64_decode($revised_object), true);
|
||||
$revised_object_both = array('mergeable' => array(), 'notMergeable' => array());
|
||||
|
||||
// Loop through attributes to inject and perform the correct action
|
||||
// (inject, duplicate, add warnings, ...) when applicable
|
||||
foreach ($revised_object['Attribute'] as $attribute_to_inject) {
|
||||
$flag_no_collision = true;
|
||||
foreach ($object['Attribute'] as $attribute) {
|
||||
if (
|
||||
$attribute['object_relation'] == $attribute_to_inject['object_relation']
|
||||
&& $attribute['type'] == $attribute_to_inject['type']
|
||||
&& $attribute['value'] !== $attribute_to_inject['value']
|
||||
) { // Collision on value
|
||||
$multiple = !empty(Hash::extract($template['ObjectTemplateElement'], sprintf('{n}[object_relation=%s][type=%s][multiple=true]', $attribute['object_relation'], $attribute['type'])));
|
||||
if ($multiple) { // if multiple is set, check if an entry exists already
|
||||
$flag_entry_exists = false;
|
||||
foreach ($object['Attribute'] as $attr) {
|
||||
if (
|
||||
$attr['object_relation'] == $attribute_to_inject['object_relation']
|
||||
&& $attr['type'] == $attribute_to_inject['type']
|
||||
&& $attr['value'] === $attribute_to_inject['value']
|
||||
) {
|
||||
$flag_entry_exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$flag_entry_exists) { // entry does no exists, can be duplicated
|
||||
$attribute_to_inject['is_multiple'] = true;
|
||||
$revised_object_both['mergeable'][] = $attribute_to_inject;
|
||||
$object['Attribute'][] = $attribute_to_inject;
|
||||
}
|
||||
} else { // Collision on value, multiple not set => propose overwrite
|
||||
$attribute_to_inject['current_value'] = $attribute['value'];
|
||||
$attribute_to_inject['merge-possible'] = true; // the user can still swap value
|
||||
$revised_object_both['notMergeable'][] = $attribute_to_inject;
|
||||
}
|
||||
$flag_no_collision = false;
|
||||
} else if (
|
||||
$attribute['object_relation'] == $attribute_to_inject['object_relation']
|
||||
&& $attribute['type'] == $attribute_to_inject['type']
|
||||
&& $attribute['value'] === $attribute_to_inject['value']
|
||||
) { // all good, they are basically the same, do nothing
|
||||
$revised_object_both['mergeable'][] = $attribute_to_inject;
|
||||
$flag_no_collision = false;
|
||||
}
|
||||
}
|
||||
if ($flag_no_collision) { // no collision, nor equalities => inject it straight away
|
||||
$revised_object_both['mergeable'][] = $attribute_to_inject;
|
||||
$object['Attribute'][] = $attribute_to_inject;
|
||||
}
|
||||
}
|
||||
return array(
|
||||
'object' => $object,
|
||||
'revised_object_both' => $revised_object_both
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue