new: [UI] Allow to create object from freetext

pull/8613/head
Jakub Onderka 2022-10-03 15:31:56 +02:00
parent 119000bf3e
commit 9153234885
6 changed files with 340 additions and 31 deletions

View File

@ -436,6 +436,7 @@ class ACLComponent extends Component
'groupAttributesIntoObject' => array('perm_add'),
'revise_object' => array('perm_add'),
'view' => array('*'),
'createFromFreetext' => ['perm_add'],
),
'objectReferences' => array(
'add' => array('perm_add'),

View File

@ -1240,6 +1240,124 @@ class ObjectsController extends AppController
}
}
public function createFromFreetext($eventId)
{
$this->request->allowMethod(['post']);
$event = $this->MispObject->Event->find('first', array(
'recursive' => -1,
'fields' => array('Event.id', 'Event.uuid', 'Event.orgc_id', 'Event.user_id', 'Event.publish_timestamp'),
'conditions' => array('Event.id' => $eventId)
));
if (empty($event)) {
throw new NotFoundException(__('Invalid event.'));
}
if (!$this->__canModifyEvent($event)) {
throw new ForbiddenException(__('You do not have permission to do that.'));
}
$requestData = $this->request->data['Object'];
$selectedTemplateId = $requestData['selectedTemplateId'];
$template = $this->MispObject->ObjectTemplate->find('first', array(
'recursive' => -1,
'conditions' => array(
'ObjectTemplate.id' => $selectedTemplateId,
'ObjectTemplate.active' => true,
),
'contain' => ['ObjectTemplateElement'],
));
if (empty($template)) {
throw new NotFoundException(__('Invalid template.'));
}
if (isset($requestData['selectedObjectRelationMapping'])) {
$distribution = $requestData['distribution'];
$sharingGroupId = $requestData['sharing_group_id'] ?? 0;
$comment = $requestData['comment'];
if ($distribution == 4) {
$sg = $this->MispObject->SharingGroup->fetchSG($sharingGroupId, $this->Auth->user());
if (empty($sg)) {
throw new NotFoundException(__('Invalid sharing group.'));
}
} else {
$sharingGroupId = 0;
}
$attributes = $this->_jsonDecode($requestData['attributes']);
$selectedObjectRelationMapping = $this->_jsonDecode($requestData['selectedObjectRelationMapping']);
// Attach object relation to attributes and fix tag format
foreach ($attributes as $k => &$attribute) {
$attribute['object_relation'] = $selectedObjectRelationMapping[$k];
if (!empty($attribute['tags'])) {
$attribute['Tag'] = [];
foreach (explode(",", $attribute['tags']) as $tagName) {
$attribute['Tag'][] = [
'name' => trim($tagName),
];
}
unset($attribute['tags']);
}
}
$object = [
'Object' => [
'event_id' => $eventId,
'distribution' => $distribution,
'sharing_group_id' => $sharingGroupId,
'comment' => $comment,
'Attribute' => $attributes,
],
];
$object = $this->MispObject->fillObjectDataFromTemplate($object, $template);
$result = $this->MispObject->captureObject($object, $eventId, $this->Auth->user(), true, false, $event);
if ($result === true) {
return $this->RestResponse->saveSuccessResponse('Objects', 'Created from Attributes', $result, 'json');
} else {
$error = __('Failed to create an Object from Attributes. Error: ') . PHP_EOL . h($result);
return $this->RestResponse->saveFailResponse('Objects', 'Created from Attributes', false, $error, 'json');
}
} else {
$attributes = $this->_jsonDecode($requestData['attributes']);
$processedAttributes = [];
foreach ($attributes as $attribute) {
if ($attribute['type'] === 'ip-src/ip-dst') {
$types = array('ip-src', 'ip-dst');
} elseif ($attribute['type'] === 'ip-src|port/ip-dst|port') {
$types = array('ip-src|port', 'ip-dst|port');
} else {
$types = [$attribute['type']];
}
foreach ($types as $type) {
$attribute['type'] = $type;
$processedAttributes[] = $attribute;
}
}
$attributeTypes = array_column($processedAttributes, 'type');
$conformityResult = $this->MispObject->ObjectTemplate->checkTemplateConformityBasedOnTypes($template, $attributeTypes);
if ($conformityResult['valid'] !== true || !empty($conformityResult['invalidTypes'])) {
throw new NotFoundException(__('Invalid template.'));
}
$objectRelations = [];
foreach ($template['ObjectTemplateElement'] as $templateElement) {
$objectRelations[$templateElement['type']][] = $templateElement;
}
$distributionData = $this->MispObject->Event->Attribute->fetchDistributionData($this->Auth->user());
$this->set('event', $event);
$this->set('distributionData', $distributionData);
$this->set('distributionLevels', $this->MispObject->Attribute->distributionLevels);
$this->set('template', $template);
$this->set('objectRelations', $objectRelations);
$this->set('attributes', $processedAttributes);
}
}
private function __objectIdToConditions($id)
{
if (is_numeric($id)) {

View File

@ -452,7 +452,37 @@ class MispObject extends AppModel
return false;
}
public function saveObject(array $object, $eventId, $template = false, $user, $errorBehaviour = 'drop', $breakOnDuplicate = false)
/**
* @param array $object
* @param array $template
* @return array
*/
public function fillObjectDataFromTemplate(array $object, array $template)
{
$templateFields = array(
'name' => 'name',
'meta-category' => 'meta-category',
'description' => 'description',
'template_version' => 'version',
'template_uuid' => 'uuid'
);
foreach ($templateFields as $objectField => $templateField) {
$object['Object'][$objectField] = $template['ObjectTemplate'][$templateField];
}
return $object;
}
/**
* @param array $object
* @param int $eventId
* @param array $template
* @param array $user
* @param string $errorBehaviour
* @param bool $breakOnDuplicate
* @return array|array[]|bool|int|mixed|string
* @throws Exception
*/
public function saveObject(array $object, $eventId, $template = false, array $user, $errorBehaviour = 'drop', $breakOnDuplicate = false)
{
$templateFields = array(
'name' => 'name',

View File

@ -204,12 +204,18 @@
</table>
<span>
<div class="btn-toolbar" style="float:left;">
<button class="btn btn-primary" onclick="freetextImportResultsSubmit(<?= $event['Event']['id']; ?>, <?= count($resultArray) ?>);"><?= __('Submit %s', $scope);?></button>
<button class="btn btn-primary" onclick="freetextImportResultsSubmit(<?= $event['Event']['id']; ?>);"><?= __('Submit %s', $scope);?></button>
<div class="btn-group createObject" style="display: none">
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<?= __('Create object') ?>
<span class="caret"></span>
</a>
<?php
echo $this->Form->create('Object', ['url' => $baseurl . '/objects/createFromFreetext/' . $event['Event']['id'], 'class' => 'hidden']);
echo $this->Form->input('selectedTemplateId', ['label' => false]);
echo $this->Form->input('attributes', ['label' => false]);
echo $this->Form->end();
?>
<a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
<?= __('Create object') ?>
<span class="caret"></span>
</a>
<ul class="dropdown-menu">
</ul>
</div>
@ -239,7 +245,7 @@
endforeach;
?>
</select>
<span role="button" tabindex="0" aria-label="<?php echo __('Apply changes to all applicable resolved attributes');?>" title="<?php echo __('Apply changes to all applicable resolved attributes');?>" class="btn btn-inverse" onClick="changeFreetextImportExecute();"><?php echo __('Change all');?></span><br />
<span role="button" tabindex="0" aria-label="<?php echo __('Apply changes to all applicable resolved attributes');?>" title="<?php echo __('Apply changes to all applicable resolved attributes');?>" class="btn btn-inverse" onClick="changeFreetextImportExecute();"><?php echo __('Change all');?></span><br>
<?php endif; ?>
<input type="text" id="changeComments" style="margin-left:50px;margin-top:10px;width:446px;" placeholder="<?php echo __('Update all comment fields');?>">
<span role="button" tabindex="0" aria-label="<?php echo __('Change all');?>" title="<?php echo __('Change all');?>" class="btn btn-inverse" onClick="changeFreetextImportCommentExecute();"><?php echo __('Change all');?></span>
@ -250,7 +256,7 @@
var options = <?php echo json_encode($optionsRearranged);?>;
var typeCategoryMapping = <?php echo json_encode($typeCategoryMapping); ?>;
$(function() {
possibleObjectTemplates();
freetextPossibleObjectTemplates();
popoverStartup();
$('.typeToggle').on('change', function() {
var currentId = $(this).attr('id');
@ -258,7 +264,7 @@
currentId = currentId.replace('Type', 'Category');
var currentOptions = typeCategoryMapping[selected];
possibleObjectTemplates();
freetextPossibleObjectTemplates();
/*
// Coming soon - restrict further if a list of categories is passed by the modules / freetext import tool

View File

@ -0,0 +1,127 @@
<div class="index">
<h3><?php echo __('Create object');?></h3>
<?= $this->Form->create('Object', array('url' => $baseurl . '/objects/createFromFreetext/' . $event['Event']['id'])); ?>
<dl style="margin-top: 10px;">
<dt><?php echo __('Object Template');?></dt>
<dd><a href="<?php echo $baseurl . '/ObjectTemplates/view/' . h($template['ObjectTemplate']['id']) ?>"><?php echo Inflector::humanize(h($template['ObjectTemplate']['name'])) . ' v' . h($template['ObjectTemplate']['version']); ?></a></dd>
<dt><?php echo __('Description');?></dt>
<dd><?php echo h($template['ObjectTemplate']['description']); ?></dd>
<dt><?php echo __('Meta category');?></dt>
<dd><?php echo h($template['ObjectTemplate']['meta-category']); ?></dd>
<dt><?php echo __('Distribution');?></dt>
<dd>
<?php echo $this->Form->input('Object.distribution', array(
'class' => 'Object_distribution_select',
'options' => $distributionData['levels'],
'default' => $distributionData['initial'],
'label' => false,
'style' => 'margin-bottom:5px;',
'div' => false
)); ?>
<?php echo $this->Form->input('Object.sharing_group_id', array(
'class' => 'Object_sharing_group_id_select',
'options' => $distributionData['sgs'],
'label' => false,
'div' => false,
'style' => 'display:none;margin-bottom:5px;',
'value' => 0
)); ?>
<dt><?php echo __('Comment');?></dt>
<dd>
<?php echo $this->Form->input('Object.comment', array(
'type' => 'textarea',
'style' => 'height:20px;width:400px;',
'required' => false,
'allowEmpty' => true,
'label' => false,
'div' => false
)); ?>
<div class="hidden">
<?php
echo $this->Form->input('selectedTemplateId', array('type' => 'hidden', 'value' => $template['ObjectTemplate']['id']));
echo $this->Form->input('attributes', array('type' => 'hidden', 'value' => JsonTool::encode($attributes)));
echo $this->Form->input('selectedObjectRelationMapping', ['value' => '', 'label' => false]);
echo $this->Form->end();
?>
</div>
</dl>
<div style="border: 2px solid #3465a4; border-radius: 3px; overflow: hidden;">
<table class="table table-striped table-condensed" style="margin-bottom: 0px;">
<thead>
<tr>
<th><?php echo __('Name :: Type'); ?></th>
<th><?php echo __('Category'); ?></th>
<th><?php echo __('Value'); ?></th>
<th><?php echo __('To IDS'); ?></th>
<th><?php echo __('Disable Correlation'); ?></th>
<th><?php echo __('Comment'); ?></th>
<th><?php echo __('Distribution'); ?></th>
</tr>
</thead>
<tbody id="attributeMappingBody">
<?php foreach ($attributes as $attribute): ?>
<tr>
<td>
<span style="display: block;">
<select class="attributeMapping" style="margin-bottom: 5px;">
<?php foreach ($objectRelations[$attribute['type']] as $object_relation): ?>
<option title="<?= h($object_relation['description']); ?>"><?= h($object_relation['object_relation']); ?></option>
<?php endforeach; ?>
</select>
:: <?= h($attribute['type']); ?>
</span>
<i class="objectRelationDescription apply_css_arrow"><?= h($objectRelations[$attribute['type']][0]['description']); ?></i>
</td>
<td><?= h($attribute['category']); ?></td>
<td style="white-space: nowrap;"><?= h($attribute['value']); ?></td>
<td><?= $attribute['to_ids'] ? __('Yes') : __('No') ?></td>
<td><?= $attribute['disable_correlation'] ? __('Yes') : __('No') ?></td>
<td><?= h($attribute['comment']) ?></td>
<td><?= h($distributionLevels[$attribute['distribution']]); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div style="margin-top: 15px; text-align: center;">
<button class="btn btn-primary" onclick="submitCreateFromFreetext();"><?= __('Create Object'); ?></button>
</div>
</div>
<script>
$(".Object_distribution_select").change(function() {
checkAndEnable($(this).parent().find('.Object_sharing_group_id_select'), $(this).val() == 4);
});
$(".attributeMapping").change(function() {
var $select = $(this);
var text = $select.find(":selected").attr('title');
$select.parent().parent().find('.objectRelationDescription').text(text);
});
function submitCreateFromFreetext() {
var $form = $('#ObjectCreateFromFreetextForm');
var attribute_mapping = [];
$('#attributeMappingBody').find('tr').each(function() {
attribute_mapping.push($(this).find('.attributeMapping').val());
});
$('#ObjectSelectedObjectRelationMapping').val(JSON.stringify(attribute_mapping));
xhr({
type: "post",
data: $form.serialize(),
url: $form.attr('action'),
success: function(data) {
if (data.saved) {
window.location = baseurl + '/events/view/' + <?= $event['Event']['id'] ?>;
} else {
showMessage('fail', data.errors);
}
}
})
}
</script>
<?= $this->element('/genericElements/SideMenu/side_menu', ['menuList' => 'event', 'menuItem' => 'freetextResults']);

View File

@ -2772,9 +2772,10 @@ function importChoiceSelect(url, elementId, ajax) {
}
}
function freetextImportResultsSubmit(event_id, count) {
function freetextSerializeAttributes() {
var attributeArray = [];
for (var i = 0; i < count; i++) {
$('.freetext_row').each(function() {
var i = $(this).data('row');
if ($('#Attribute' + i + 'Save').val() == 1) {
attributeArray.push({
value:$('#Attribute' + i + 'Value').val(),
@ -2790,7 +2791,12 @@ function freetextImportResultsSubmit(event_id, count) {
tags:$('#Attribute' + i + 'Tags').val()
})
}
}
});
return attributeArray;
}
function freetextImportResultsSubmit(event_id, count) {
var attributeArray = freetextSerializeAttributes();
$("#AttributeJsonObject").val(JSON.stringify(attributeArray));
var formData = $(".mainForm").serialize();
xhr({
@ -2808,20 +2814,37 @@ function freetextRemoveRow(id, event_id) {
$('#Attribute' + id + 'Save').attr("value", "0");
if ($(".freetext_row:visible").length == 0) {
window.location = baseurl + "/events/" + event_id;
} else {
freetextPossibleObjectTemplates();
}
}
function possibleObjectTemplates() {
function freetextCreateObject(objectId) {
var attributeArray = freetextSerializeAttributes();
$('#ObjectSelectedTemplateId').val(objectId);
$('#ObjectAttributes').val(JSON.stringify(attributeArray));
$('#ObjectFreeTextImportForm').submit();
}
function freetextPossibleObjectTemplates() {
var allTypes = [];
$('.freetext_row').each(function () {
var rowId = $(this).data('row');
if ($('#Attribute' + rowId + 'Save').val() === "1") {
allTypes.push($(this).find('.typeToggle').val());
var type = $(this).find('.typeToggle').val();
if (type === 'ip-src/ip-dst') {
allTypes.push('ip-src', 'ip-dst');
} else if (type === 'ip-src|port/ip-dst|port') {
allTypes.push('ip-src|port', 'ip-dst|port');
} else {
allTypes.push(type);
}
}
});
if (allTypes.length < 2) {
$('.createObject').hide();
return;
}
$.ajax({
@ -2830,24 +2853,28 @@ function possibleObjectTemplates() {
success: function (data) {
if (data.length === 0) {
$('.createObject').hide();
} else {
var $menu = $('.createObject ul');
$menu.find('li').remove();
$.each(data, function (i, template) {
var a = document.createElement('a');
a.href = '#';
a.textContent = template.name;
a.title = template.description;
var li = document.createElement('li');
li.appendChild(a);
$menu.append(li);
});
$('.createObject').show();
return;
}
var $menu = $('.createObject ul');
$menu.find('li').remove();
$.each(data, function (i, template) {
var a = document.createElement('a');
a.href = '#';
a.onclick = function () {
freetextCreateObject(template['id']);
};
a.textContent = template.name;
a.title = template.description;
var li = document.createElement('li');
li.appendChild(a);
$menu.append(li);
});
$('.createObject').show();
},
type: "post",
url: baseurl + "/objectTemplates/possibleObjectTemplates",