diff --git a/.gitignore b/.gitignore index e83c4920b..a2e10cfc1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ /app/tmp/cache/views/myapp* /app/files/* !/app/files/empty +/app/tmp/files/* +!/app/tmp/files/empty /app/webroot/img/logo.png /app/Config/bootstrap.php /app/Config/database.php diff --git a/app/Controller/TemplatesController.php b/app/Controller/TemplatesController.php index fc47b2429..61c967fc8 100644 --- a/app/Controller/TemplatesController.php +++ b/app/Controller/TemplatesController.php @@ -1,6 +1,8 @@ Security->unlockedActions = array('saveElementSorting', 'populateEventFromTemplate'); + $this->Security->unlockedActions = array('saveElementSorting', 'populateEventFromTemplate', 'uploadFile', 'deleteTemporaryFile'); } public function fetchFormFromTemplate($id) { @@ -275,20 +277,22 @@ class TemplatesController extends AppController { $this->set('template_id', $template_id); $this->set('event_id', $event_id); if ($this->request->is('post')) { + $errors = array(); + $this->set('template', $this->request->data); $result = $this->Event->Attribute->checkTemplateAttributes($template, $this->request->data, $event_id, $event['Event']['distribution']); - $errors = $result['errors']; - $attributes = $result['attributes']; - - if (empty($errors) && empty($this->request->data['Template']['modify'])) { - $this->set('template', $this->request->data); - $this->set('attributes', $attributes); - $this->set('distributionLevels', $this->Event->distributionLevels); - $this->render('populate_event_from_template_attributes'); - } else { - $this->set('template', $this->request->data); - $this->set('errors', $errors); + if (isset($this->request->data['Template']['modify']) || !empty($result['errors'])) { + $fileArray = $this->request->data['Template']['fileArray']; + $this->set('fileArray', $fileArray); + $this->set('errors', $result['errors']); $this->set('templateData', $template); $this->set('validTypeGroups', $this->Event->Attribute->validTypeGroups); + } else { + $this->set('errors', $result['errors']); + $this->set('attributes', $result['attributes']); + $fileArray = $this->request->data['Template']['fileArray']; + $this->set('fileArray', $fileArray); + $this->set('distributionLevels', $this->Event->distributionLevels); + $this->render('populate_event_from_template_attributes'); } } else { $this->set('templateData', $template); @@ -303,17 +307,42 @@ class TemplatesController extends AppController { 'conditions' => array('id' => $event_id), 'recursive' => -1, 'fields' => array('id', 'orgc', 'distribution'), + 'contain' => 'EventTag', )); if (empty($event)) throw new MethodNotAllowedException('Event not found or you are not authorised to edit it.'); if (!$this->_isSiteAdmin()) { if ($event['Event']['orgc'] != $this->Auth->user('org')) throw new MethodNotAllowedException('Event not found or you are not authorised to edit it.'); } + + $template = $this->Template->find('first', array( + 'id' => $template_id, + 'recursive' => -1, + 'contain' => 'TemplateTag', + 'fields' => 'id', + )); + + foreach ($template['TemplateTag'] as $tag) { + $exists = false; + foreach ($event['EventTag'] as $eventTag) { + if ($eventTag['tag_id'] == $tag['tag_id']) $exists = true; + } + if (!$exists) { + $this->Event->EventTag->create(); + $this->Event->EventTag->save(array('event_id' => $event_id, 'tag_id' => $tag['tag_id'])); + } + } if (isset($this->request->data['Template']['attributes'])) { $attributes = unserialize($this->request->data['Template']['attributes']); $this->loadModel('Attribute'); $fails = 0; - foreach($attributes as $k => $attribute) { + foreach($attributes as $k => &$attribute) { + if (isset($attribute['data'])) { + $file = new File(APP . 'tmp/files/' . $attribute['data']); + $content = $file->read(); + $attribute['data'] = base64_encode($content); + $file->delete(); + } $this->Attribute->create(); if (!$this->Attribute->save(array('Attribute' => $attribute))) $fails++; } @@ -328,4 +357,65 @@ class TemplatesController extends AppController { throw new MethodNotAllowedException(); } } + + public function uploadFile($elementId, $batch) { + $this->layout = 'iframe'; + $this->set('batch', $batch); + $this->set('element_id', $elementId); + if ($this->request->is('get')) { + $this->set('element_id', $elementId); + } else if ($this->request->is('post')) { + $fileArray = array(); + $filenames = array(); + $tmp_names = array(); + $element_ids = array(); + $result = array(); + $added = 0; + $failed = 0; + // filename checks + foreach ($this->request->data['Template']['file'] as $k => $file) { + if ($file['size'] > 0 && $file['error'] == 0) { + if (preg_match('@^[\w\-. ]+$@', $file['name'])) { + $fn = $this->Template->generateRandomFileName(); + move_uploaded_file($file['tmp_name'], APP . 'tmp/files/' . $fn); + $filenames[] =$file['name']; + $fileArray[] = array('filename' => $file['name'], 'tmp_name' => $fn, 'element_id' => $elementId); + $added++; + } else $failed++; + } else $failed ++; + } + $result = $added . ' files uploaded.'; + if ($failed) { + $result .= ' ' . $failed . ' files either failed to upload, or were empty.'; + $this->set('upload_error', true); + } else { + $this->set('upload_error', false); + } + + $this->set('result', $result); + $this->set('filenames', $filenames); + $this->set('fileArray', json_encode($fileArray)); + } + } + + private function __combineArrays($array, $array2) { + foreach ($array2 as $element) { + if (!in_array($element, $array)) { + $array[] = $element; + } + } + return $array; + } + + public function deleteTemporaryFile($filename) { + if (!$this->request->is('post')) throw new MethodNotAllowedException('This action is restricted to accepting POST requests only.'); + //if (!$this->request->is('ajax')) throw new MethodNotAllowedException('This action is only accessible through AJAX.'); + $this->autoRender = false; + if (preg_match('/^[a-zA-Z0-9]{12}$/', $filename)) { + $file = new File(APP . 'tmp/files/' . $filename); + if ($file->exists()) { + $file->delete(); + } + } + } } diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index 20d216426..43f30e611 100755 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -1361,23 +1361,27 @@ class Attribute extends AppModel { } - public function checkTemplateAttributes($template, $data, $event_id, $distribution) { + public function checkTemplateAttributes($template, &$data, $event_id, $distribution) { $result = array(); $errors = array(); $attributes = array(); + $files = array(); + $savedFiles = array(); + if (isset($data['Template']['fileArray'])) $fileArray = json_decode($data['Template']['fileArray'], true); foreach ($template['TemplateElement'] as $element) { if ($element['element_definition'] == 'attribute') { $result = $this->__resolveElementAttribute($element['TemplateElementAttribute'][0], $data['Template']['value_' . $element['id']]); } else if ($element['element_definition'] == 'file') { $temp = array(); - foreach ($data['Template']['file_' . $element['id']] as $fileArray) { - foreach ($fileArray as $k => $file) { - if (($file['name'] != '' && $file['size'] > 0) || ($k + 1 == count($fileArray))) { - $temp[] = $file; - } - } + if (isset($fileArray)) { + foreach ($fileArray as $fileArrayElement) { + if ($fileArrayElement['element_id'] == $element['id']) { + $temp[] = $fileArrayElement; + } + } } $result = $this->__resolveElementFile($element['TemplateElementFile'][0], $temp); + if ($element['TemplateElementFile'][0]['mandatory'] && empty($temp) && empty($errors[$element['id']])) $errors[$element['id']] = 'Error: This field is mandatory.'; } if ($element['element_definition'] == 'file' || $element['element_definition'] == 'attribute') { if ($result['errors']) { @@ -1431,11 +1435,11 @@ class Attribute extends AppModel { return array('attributes' => $results, 'errors' => $errors); } - private function __resolveElementFile($element, $value) { + private function __resolveElementFile($element, $files) { $attributes = array(); $errors = null; $results = array(); - $count = count($value); + $count = count($files); $element['complex'] = false; if ($element['malware']) { $element['type'] = 'malware-sample'; @@ -1444,48 +1448,41 @@ class Attribute extends AppModel { $element['type'] = 'attachment'; $element['to_ids'] = false; } - if ($count == 1 && $value[0]['size'] == 0) { - if ($element['mandatory']) $errors = 'This field is mandatory.'; - } else { - if ($count > 1) unset($value[$count-1]); - foreach ($value as $v) { - if (!($v['size'] > 0 && $v['error'] == 0)) { - $errors = 'File upload failed or the file was empty.'; - } else if (!preg_match('@^[\w\-. ]+$@', $v['name'])) { - $errors = 'Filename not allowed.'; - } else { - if ($element['malware']) { - $malwareName = $v['name'] . '|' . hash_file('md5', $v['tmp_name']); - $attributes[] = $this->__createAttribute($element, $malwareName); - $file = new File($v['tmp_name']); - if (!$file->exists()) { - $errors = 'File cannot be read.'; - } else { - $content = $file->read(); - $attributes[count($attributes) - 1]['data'] = base64_encode($content); - $element['type'] = 'filename|sha256'; - $sha256 = $v['name'] . '|' . (hash_file('sha256', $v['tmp_name'])); - $attributes[] = $this->__createAttribute($element, $sha256); - $element['type'] = 'filename|sha1'; - $sha1 = $v['name'] . '|' . (hash_file('sha1', $v['tmp_name'])); - $attributes[] = $this->__createAttribute($element, $sha1); - } - } else { - $attributes[] = $this->__createAttribute($element, $v['name']); - $file = new File($v['tmp_name']); - if (!$file->exists()) { - $errors = 'File cannot be read.'; - } else { - $content = $file->read(); - $attributes[count($attributes) - 1]['data'] = base64_encode($content); - } - } - } - } + foreach ($files as $file) { + if (!preg_match('@^[\w\-. ]+$@', $file['filename'])) { + $errors = 'Filename not allowed.'; + continue; + } + if ($element['malware']) { + $malwareName = $file['filename'] . '|' . hash_file('md5', APP . 'tmp/files/' . $file['tmp_name']); + $tmp_file = new File(APP . 'tmp/files/' . $file['tmp_name']); + if (!$tmp_file->exists()) { + $errors = 'File cannot be read.'; + } else { + $attributes[] = $this->__createAttribute($element, $malwareName); + $content = $tmp_file->read(); + $attributes[count($attributes) - 1]['data'] = $file['tmp_name']; + $element['type'] = 'filename|sha256'; + $sha256 = $file['filename'] . '|' . (hash_file('sha256', APP . 'tmp/files/' . $file['tmp_name'])); + $attributes[] = $this->__createAttribute($element, $sha256); + $element['type'] = 'filename|sha1'; + $sha1 = $file['filename'] . '|' . (hash_file('sha1', APP . 'tmp/files/' . $file['tmp_name'])); + $attributes[] = $this->__createAttribute($element, $sha1); + } + } else { + $attributes[] = $this->__createAttribute($element, $file['filename']); + $tmp_file = new File(APP . 'tmp/files/' . $file['tmp_name']); + if (!$tmp_file->exists()) { + $errors = 'File cannot be read.'; + } else { + $content = $tmp_file->read(); + $attributes[count($attributes) - 1]['data'] = $file['tmp_name']; + } + } } - return array('attributes' => $attributes, 'errors' => $errors); + return array('attributes' => $attributes, 'errors' => $errors, 'files' => $files); } - + private function __createAttribute($element, $value) { $attribute = array( 'comment' => $element['name'], diff --git a/app/Model/Event.php b/app/Model/Event.php index ad01ea5aa..9ad0460a4 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -1150,45 +1150,46 @@ class Event extends AppModel { // But only do this if it is allowed in the bootstrap.php file. // if ($eventIsPrivate) { - $conditions = array('User.autoalert' => 1, 'User.gpgkey =' => "", 'User.org =' => $event['Event']['org']); + $conditions = array('User.autoalert' => 1, 'User.gpgkey =' => "", 'User.org =' => $event['Event']['org']); } else { - $conditions = array('User.autoalert' => 1, 'User.gpgkey =' => ""); + $conditions = array('User.autoalert' => 1, 'User.gpgkey =' => ""); } - if ('false' == Configure::read('GnuPG.onlyencrypted')) { - $alertUsers = $this->User->find('all', array( - 'conditions' => $conditions, - 'recursive' => 0, - )); - $max = count($alertUsers); - foreach ($alertUsers as $k => &$user) { - // prepare the the unencrypted email - $Email = new CakeEmail(); - $Email->from(Configure::read('MISP.email')); - $Email->to($user['User']['email']); - $Email->subject("[" . Configure::read('MISP.org') . " " . Configure::read('MISP.name') . "] Event " . $id . " - " . $subject . $event['ThreatLevel']['name'] . " - TLP Amber"); - $Email->emailFormat('text'); // both text or html - // send it - $Email->send($bodySigned); - $Email->reset(); - if ($processId) { - $this->Job->id = $processId; - $this->Job->saveField('progress', $k / $max * 50); - } + if ('false' == Configure::read('GnuPG.onlyencrypted')) { + $alertUsers = $this->User->find('all', array( + 'conditions' => $conditions, + 'recursive' => 0, + )); + $max = count($alertUsers); + foreach ($alertUsers as $k => &$user) { + // prepare the the unencrypted email + $Email = new CakeEmail(); + $Email->from(Configure::read('MISP.email')); + $Email->to($user['User']['email']); + $Email->subject("[" . Configure::read('MISP.org') . " " . Configure::read('MISP.name') . "] Event " . $id . " - " . $subject . $event['ThreatLevel']['name'] . " - TLP Amber"); + $Email->emailFormat('text'); // both text or html + // send it + $Email->send($bodySigned); + $Email->reset(); + if ($processId) { + $this->Job->id = $processId; + $this->Job->saveField('progress', $k / $max * 50); } } - // - // Build a list of the recipients that wish to receive encrypted mails. - // - if ($eventIsPrivate) { - $conditions = array('User.autoalert' => 1, 'User.gpgkey !=' => "", 'User.org =' => $event['Event']['org']); - } else { - $conditions = array('User.autoalert' => 1, 'User.gpgkey !=' => ""); - } - $alertUsers = $this->User->find('all', array( - 'conditions' => $conditions, - 'recursive' => 0, - ) - ); + } + // + // Build a list of the recipients that wish to receive encrypted mails. + // + if ($eventIsPrivate) { + $conditions = array('User.autoalert' => 1, 'User.gpgkey !=' => "", 'User.org =' => $event['Event']['org']); + } else { + $conditions = array('User.autoalert' => 1, 'User.gpgkey !=' => ""); + } + $alertUsers = $this->User->find('all', array( + 'conditions' => $conditions, + 'recursive' => 0, + ) + ); + $max = count($alertUsers); // encrypt the mail for each user and send it separately foreach ($alertUsers as $k => &$user) { // send the email diff --git a/app/Model/Template.php b/app/Model/Template.php index 1afa4c9aa..d9b08f2e2 100644 --- a/app/Model/Template.php +++ b/app/Model/Template.php @@ -65,4 +65,15 @@ class Template extends AppModel { return false; } } + + public function generateRandomFileName() { + $length = 12; + $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $charLen = strlen($characters) - 1; + $fn = ''; + for ($p = 0; $p < $length; $p++) { + $fn .= $characters[rand(0, $charLen)]; + } + return $fn; + } } diff --git a/app/View/Elements/templateElements/populateTemplateFile.ctp b/app/View/Elements/templateElements/populateTemplateFile.ctp index d412ab73b..1ff5787de 100644 --- a/app/View/Elements/templateElements/populateTemplateFile.ctp +++ b/app/View/Elements/templateElements/populateTemplateFile.ctp @@ -1,25 +1,30 @@ -
-
+
+ + (*) + +
Description:

-
File:
-
 

+
File:
+
+   +

+ +
> + +
\ No newline at end of file diff --git a/app/View/Layouts/iframe.ctp b/app/View/Layouts/iframe.ctp new file mode 100644 index 000000000..da911f5c7 --- /dev/null +++ b/app/View/Layouts/iframe.ctp @@ -0,0 +1,23 @@ + +Html->css('bootstrap'); +echo $this->Html->css('main'); +echo $this->Html->script('jquery-2.1.0.min'); +echo $content_for_layout; ?> \ No newline at end of file diff --git a/app/View/Templates/populate_event_from_template.ctp b/app/View/Templates/populate_event_from_template.ctp index 419c100d6..129b411d9 100644 --- a/app/View/Templates/populate_event_from_template.ctp +++ b/app/View/Templates/populate_event_from_template.ctp @@ -11,6 +11,11 @@ foreach ($templateData['TemplateElement'] as $k => $element) { echo $this->element('templateElements/populateTemplate' . ucfirst($element['element_definition']), array('element' => $element['TemplateElement' . ucfirst($element['element_definition'])][0], 'k' => $k, 'element_id' => $element['id'], 'value' => '')); } + echo $this->Form->input('fileArray', array( + 'label' => false, + 'style' => 'display:none;', + 'value' => '[]', + )); ?> Form->button('Add', array('class' => 'btn btn-primary')); echo $this->Form->end(); ?> + \ No newline at end of file diff --git a/app/View/Templates/populate_event_from_template_attributes.ctp b/app/View/Templates/populate_event_from_template_attributes.ctp index 002b8de08..c2a44f5ca 100644 --- a/app/View/Templates/populate_event_from_template_attributes.ctp +++ b/app/View/Templates/populate_event_from_template_attributes.ctp @@ -56,6 +56,16 @@ endforeach;?> 'type' => 'hidden', 'value' => true, )); + echo $this->Form->input('errors', array( + 'label' => false, + 'type' => 'hidden', + 'value' => serialize($errors), + )); + echo $this->Form->input('fileArray', array( + 'label' => false, + 'type' => 'hidden', + 'value' => $fileArray, + )); ?> Form->create('', array('type' => 'file')); +echo $this->Form->end(); +?> \ No newline at end of file diff --git a/app/View/Templates/upload_file.ctp b/app/View/Templates/upload_file.ctp new file mode 100644 index 000000000..d56b3697f --- /dev/null +++ b/app/View/Templates/upload_file.ctp @@ -0,0 +1,32 @@ +Html->script('ajaxification'); +if ($batch == 'yes') { + $buttonText = 'Upload Files'; + $multiple = true; +} else { + $multiple = false; + if (isset($filenames)) { + $buttonText = 'Replace File'; + } else { + $buttonText = 'Upload File'; + } +} +?> +
+ Form->create('', array('id' => 'upload_' . $element_id, 'type' => 'file')); + echo $this->Form->input('file.', array('id' => 'upload_' . $element_id . '_file', 'type' => 'file', 'label' => false, 'multiple' => $multiple, 'onChange' => 'this.form.submit()')); + echo $this->Form->end(); + ?> +
+ + \ No newline at end of file diff --git a/app/tmp/files/empty b/app/tmp/files/empty new file mode 100644 index 000000000..e69de29bb diff --git a/app/webroot/js/ajaxification.js b/app/webroot/js/ajaxification.js index 53525156a..c89619003 100644 --- a/app/webroot/js/ajaxification.js +++ b/app/webroot/js/ajaxification.js @@ -297,7 +297,6 @@ function deleteSelectedAttributes(event) { }); $('#AttributeIds').attr('value', JSON.stringify(selected)); var formData = $('#delete_selected').serialize(); - console.log(formData); $.ajax({ data: formData, cache: false, @@ -503,7 +502,6 @@ function recoverValuesFromPersistance(formPersistanceArray) { function handleValidationErrors(responseArray, context, contextNamingConvention) { for (var k in responseArray) { var elementName = k.charAt(0).toUpperCase() + k.slice(1); - console.log("#" + contextNamingConvention + elementName); $("#" + contextNamingConvention + elementName).parent().addClass("error"); $("#" + contextNamingConvention + elementName).parent().append("
" + responseArray[k] + "
"); } @@ -536,7 +534,13 @@ function updateHistogram(selected) { }); } -function showMessage(success, message) { +function showMessage(success, message, context) { + if (typeof context !== "undefined") { + $("#ajax_" + success, window.parent.document).html(message); + var duration = 1000 + (message.length * 40); + $("#ajax_" + success + "_container", window.parent.document).fadeIn("slow"); + $("#ajax_" + success + "_container", window.parent.document).delay(duration).fadeOut("slow"); + } $("#ajax_" + success).html(message); var duration = 1000 + (message.length * 40); $("#ajax_" + success + "_container").fadeIn("slow"); @@ -758,33 +762,97 @@ function getTemplateChoicePopup(id) { function resizePopoverBody() { var bodyheight = $(window).height(); bodyheight = 3 * bodyheight / 4 - 150; - console.log(bodyheight); $("#popover_choice_main").css({"max-height": bodyheight}); } +function populateTemplateHiddenFileDiv(files) { + $('#TemplateFileArray').val(JSON.stringify(files)); +} -function populateTemplateBindChangeEvent(name, filenameContainer, e, i, batch) { - $(name).change( - function(){ - if (batch == 'yes') { - var files = $(this)[0].files; - for (var i = 0; i < files.length; i++) { - $(filenameContainer).append('
' + files[i].name + 'x
'); - } - $(name).hide(); - i++; - populateTemplateCreateFileUpload(e, i, batch); - } else { - $(filenameContainer).html('' + $(this).val() + ''); - } +function populateTemplateFileBubbles() { + var fileObjectArray = JSON.parse($('#TemplateFileArray').val()); + fileObjectArray.forEach(function(entry) { + templateAddFileBubble(entry.element_id, false, entry.filename, entry.tmp_name, 'yes'); }); } -function populateTemplateCreateFileUpload(e, i, batch) { - if (batch == 'yes') { - $('#file_container_' + e).append(''); - } else { - $('#file_container_' + e).append(''); +function templateFileHiddenAdd(files, element_id, batch) { + var fileArray = $.parseJSON($('#TemplateFileArray', window.parent.document).val()); + var contained = false; + for (var j=0; j< files.length; j++) { + for (var i=0; i< fileArray.length; i++) { + if (fileArray[i].filename == files[j].filename) { + contained = true; + } + if (batch == 'no' && fileArray[i].element_id == element_id) { + templateDeleteFileBubble(fileArray[i].filename, fileArray[i].tmp_name, fileArray[i].element_id, 'iframe', batch); + contained = false; + var removeId = i; + } + } + if (batch == 'no') fileArray.splice(removeId, 1); + if (contained == false) { + fileArray.push(files[j]); + templateAddFileBubble(element_id, true, files[j].filename, files[j].tmp_name, batch); + $('#TemplateFileArray', window.parent.document).val(JSON.stringify(fileArray)); + } } - populateTemplateBindChangeEvent('#Template' + e + 'File' + i, '#filenames_' + e, e, i, batch); -} \ No newline at end of file +} + +function templateAddFileBubble(element_id, iframe, filename, tmp_name, batch) { + if (batch == 'no') { + if (iframe == true) { + $('#filenames_' + element_id, window.parent.document).html('
' + filename + 'x
'); + } else { + $('#filenames_' + element_id).html('
' + filename + 'x
'); + } + } else { + if (iframe == true) { + $('#filenames_' + element_id, window.parent.document).append('
' + filename + 'x
'); + } else { + $('#filenames_' + element_id).append('
' + filename + 'x
'); + } + } +} + +function templateDeleteFileBubble(filename, tmp_name, element_id, context, batch) { + $(".loading").show(); + $.ajax({ + type:"post", + cache: false, + url:"/templates/deleteTemporaryFile/" + tmp_name, + }); + var c = this; + if (context == 'iframe') { + $('#' + tmp_name + '_container', window.parent.document).remove(); + var oldArray = JSON.parse($('#TemplateFileArray', window.parent.document).val()); + } else { + $('#' + tmp_name + '_container').remove(); + var oldArray = JSON.parse($('#TemplateFileArray').val()); + } + var newArray = []; + oldArray.forEach(function(entry) { + if (batch == 'no') { + if (entry.element_id != element_id) { + newArray.push(entry); + } + } else { + if (entry.tmp_name != tmp_name) { + newArray.push(entry); + } + } + }); + if (batch == 'no') { + $('#fileUploadButton_' + element_id, $('#iframe_' + element_id).contents()).html('Upload File'); + } + if (context == 'iframe') { + $('#TemplateFileArray', window.parent.document).val(JSON.stringify(newArray)); + } else { + $('#TemplateFileArray').val(JSON.stringify(newArray)); + } + $(".loading").hide(); +} + +function templateFileUploadTriggerBrowse(id) { + $('#upload_' + id + '_file').click(); +}