Merge branch 'develop' of github.com:MISP/MISP into eventgraph-node-coloring

pull/7180/head
mokaddem 2021-03-09 10:49:22 +01:00
commit f95babb980
No known key found for this signature in database
GPG Key ID: 164C473F627A06FA
21 changed files with 778 additions and 475 deletions

View File

@ -29,6 +29,13 @@ class EventShell extends AppShell
],
)
));
$parser->addSubcommand('testEventNotificationEmail', [
'help' => __('Generate event notification email in EML format.'),
'arguments' => [
'event_id' => ['help' => __('Event ID'), 'required' => true],
'user_id' => ['help' => __('User ID'), 'required' => true],
],
]);
return $parser;
}
@ -469,6 +476,34 @@ class EventShell extends AppShell
$this->Job->save($job);
}
public function testEventNotificationEmail()
{
list($eventId, $userId) = $this->args;
$user = $this->getUser($userId);
$eventForUser = $this->Event->fetchEvent($user, [
'eventid' => $eventId,
'includeAllTags' => true,
'includeEventCorrelations' => true,
'noEventReports' => true,
'noSightings' => true,
'metadata' => Configure::read('MISP.event_alert_metadata_only') ?: false,
]);
if (empty($eventForUser)) {
$this->error("Event with ID $eventId not exists or given user don't have permission to access it.");
}
$emailTemplate = $this->Event->prepareAlertEmail($eventForUser[0], $user);
App::uses('SendEmail', 'Tools');
App::uses('GpgTool', 'Tools');
$sendEmail = new SendEmail(GpgTool::initializeGpg());
$sendEmail->setTransport('Debug');
$result = $sendEmail->sendToUser(['User' => $user], null, $emailTemplate);
echo $result['contents']['headers'] . "\n\n" . $result['contents']['message'] . "\n";
}
/**
* @param int $userId
* @return array

View File

@ -2678,7 +2678,12 @@ class AttributesController extends AppController
}
}
} else {
$tag = $this->Event->EventTag->Tag->find('first', array('recursive' => -1, 'conditions' => $conditions));
$conditions = array('LOWER(Tag.name)' => strtolower(trim($tag_id)));
if (!$this->_isSiteAdmin()) {
$conditions['Tag.org_id'] = array('0', $this->Auth->user('org_id'));
$conditions['Tag.user_id'] = array('0', $this->Auth->user('id'));
}
$tag = $this->Attribute->AttributeTag->Tag->find('first', array('recursive' => -1, 'conditions' => $conditions));
if (empty($tag)) {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag.')), 'status'=>200, 'type' => 'json'));
}

View File

@ -48,7 +48,15 @@ class RestResponseComponent extends Component
'mandatory' => array('returnFormat'),
'optional' => array('page', 'limit', 'value' , 'type', 'category', 'org', 'tags', 'date', 'last', 'eventid', 'withAttachments', 'uuid', 'publish_timestamp', 'timestamp', 'attribute_timestamp', 'enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'includeEventTags', 'event_timestamp', 'threat_level_id', 'eventinfo', 'includeProposals', 'includeDecayScore', 'includeFullModel', 'decayingModel', 'excludeDecayed', 'score', 'first_seen', 'last_seen'),
'params' => array()
)
),
'addTag' => array(
'description' => "Add a tag or a tag collection to an attribute.",
'mandatory' => array('attribute', 'tag')
),
'removeTag' => array(
'description' => "Remove a tag from an attribute.",
'mandatory' => array('attribute', 'tag')
),
),
'Community' => array(
'requestAccess' => array(
@ -80,7 +88,15 @@ class RestResponseComponent extends Component
'mandatory' => array('returnFormat'),
'optional' => array('page', 'limit', 'value', 'type', 'category', 'org', 'tag', 'tags', 'searchall', 'date', 'last', 'eventid', 'withAttachments', 'metadata', 'uuid', 'published', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'sgReferenceOnly', 'eventinfo', 'excludeLocalTags', 'threat_level_id'),
'params' => array()
)
),
'addTag' => array(
'description' => "Add a tag or a tag collection to an event.",
'mandatory' => array('event', 'tag')
),
'removeTag' => array(
'description' => "Remove a tag from an event.",
'mandatory' => array('event', 'tag')
),
),
'EventGraph' => array(
'add' => array(
@ -270,10 +286,9 @@ class RestResponseComponent extends Component
'optional' => array('name', 'colour', 'exportable', 'hide_tag', 'org_id', 'user_id'),
'params' => array('tag_id')
),
'removeTag' => array(
'description' => "POST a request object in JSON format to this API to create detach a tag from an event. #FIXME Function does not exists",
'mandatory' => array('event', 'tag'),
'params' => array('tag_id')
'removeTagFromObject' => array(
'description' => "Untag an event or attribute. Tag can be the id or the name.",
'mandatory' => array('uuid', 'tag')
),
'attachTagToObject' => array(
'description' => "Attach a Tag to an object, refenced by an UUID. Tag can either be a tag id or a tag name.",
@ -944,6 +959,13 @@ class RestResponseComponent extends Component
'values' => array(1 => 'True', 0 => 'False' ),
'help' => __('Should the warning list be enforced. Adds `blocked` field for matching attributes')
),
'event' => array(
'input' => 'number',
'type' => 'integer',
'operators' => array('equal', 'not_equal'),
'validation' => array('min' => 0, 'step' => 1),
'help' => __('Event id')
),
'event_id' => array(
'input' => 'number',
'type' => 'integer',
@ -1671,12 +1693,6 @@ class RestResponseComponent extends Component
'operators' => array('equal'),
'help' => __('Not supported (warninglist->checkvalues) expect an array')
),
'event' => array(
'input' => 'text',
'type' => 'string',
'operators' => array('equal'),
'help' => __('Not supported (removeTag)')
),
'push_rules' => array(
'input' => 'text',
'type' => 'string',
@ -1801,6 +1817,9 @@ class RestResponseComponent extends Component
case "last_seen":
$this->__overwriteSeen($scope, $action, $fieldsConstraint[$field]);
break;
case "attribute":
$this->__overwriteAttribute($scope, $action, $fieldsConstraint[$field]);
break;
default:
break;
}
@ -1882,6 +1901,11 @@ class RestResponseComponent extends Component
$field['help'] = __('Also supports array of tags');
}
}
private function __overwriteAttribute($scope, $action, &$field){
if ($action == 'addTag' || $action == 'removeTag') {
$field['help'] = __('Attribute id');
}
}
private function __overwriteNationality($scope, $action, &$field) {
$field['values'] = ClassRegistry::init("Organisation")->getCountries();
}

View File

@ -838,27 +838,7 @@ class EventsController extends AppController
return $this->RestResponse->viewData($export->eventIndex($events), 'csv');
}
$user = $this->Auth->user();
$user = $this->Event->User->fillKeysToUser($user);
if (empty($user['gpgkey']) && Configure::read('GnuPG.onlyencrypted')) {
// No GnuPG
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
// No GnuPG and No SMIME
$this->Flash->info(__('No X.509 certificate or GnuPG key set in your profile. To receive emails, submit your public certificate or GnuPG key in your profile.'));
} elseif (!Configure::read('SMIME.enabled')) {
$this->Flash->info(__('No GnuPG key set in your profile. To receive emails, submit your public key in your profile.'));
}
} elseif ($this->Auth->user('autoalert') && empty($user['gpgkey']) && Configure::read('GnuPG.bodyonlyencrypted')) {
// No GnuPG & autoalert
if ($this->Auth->user('autoalert') && Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
// No GnuPG and No SMIME & autoalert
$this->Flash->info(__('No X.509 certificate or GnuPG key set in your profile. To receive attributes in emails, submit your public certificate or GnuPG key in your profile.'));
} elseif (!Configure::read('SMIME.enabled')) {
$this->Flash->info(__('No GnuPG key set in your profile. To receive attributes in emails, submit your public key in your profile.'));
}
}
$this->__noKeyNotification();
$this->set('events', $events);
$this->set('eventDescriptions', $this->Event->fieldDescriptions);
$this->set('analysisLevels', $this->Event->analysisLevels);
@ -876,6 +856,34 @@ class EventsController extends AppController
}
}
private function __noKeyNotification()
{
$onlyEncrypted = Configure::read('GnuPG.onlyencrypted');
$bodyOnlyEncrypted = Configure::read('GnuPG.bodyonlyencrypted');
if (!$onlyEncrypted && !$bodyOnlyEncrypted) {
return;
}
$user = $this->Event->User->fillKeysToUser($this->Auth->user());
if (!empty($user['gpgkey'])) {
return; // use has PGP key
}
if ($onlyEncrypted) {
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
$this->Flash->info(__('No X.509 certificate or PGP key set in your profile. To receive emails, submit your public certificate or PGP key in your profile.'));
} elseif (!Configure::read('SMIME.enabled')) {
$this->Flash->info(__('No PGP key set in your profile. To receive emails, submit your public key in your profile.'));
}
} elseif ($bodyOnlyEncrypted && $user['autoalert']) {
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
$this->Flash->info(__('No X.509 certificate or PGP key set in your profile. To receive attributes in emails, submit your public certificate or PGP key in your profile.'));
} elseif (!Configure::read('SMIME.enabled')) {
$this->Flash->info(__('No PGP key set in your profile. To receive attributes in emails, submit your public key in your profile.'));
}
}
}
public function filterEventIndex()
{
$passedArgsArray = array();
@ -1224,6 +1232,11 @@ class EventsController extends AppController
$this->render('/Elements/eventattribute');
}
/**
* @param array $event
* @param bool $continue
* @param int $fromEvent
*/
private function __viewUI($event, $continue, $fromEvent)
{
$this->loadModel('Taxonomy');
@ -1278,9 +1291,9 @@ class EventsController extends AppController
// set the pivot data
$this->helpers[] = 'Pivot';
if ($continue) {
$data = $this->__continuePivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date'], $fromEvent);
$this->__continuePivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date'], $fromEvent);
} else {
$data = $this->__startPivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date']);
$this->__startPivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date']);
}
$pivot = $this->Session->read('pivot_thread');
$this->__arrangePivotVertical($pivot);
@ -1626,24 +1639,61 @@ class EventsController extends AppController
$this->__viewUI($event, $continue, $fromEvent);
}
/**
* @param int $id
* @param string $info
* @param string $date
*/
private function __startPivoting($id, $info, $date)
{
$this->Session->write('pivot_thread', null);
$initial_pivot = array('id' => $id, 'info' => $info, 'date' => $date, 'depth' => 0, 'height' => 0, 'children' => array(), 'deletable' => true);
$this->Session->write('pivot_thread', $initial_pivot);
$initialPivot = [
'id' => $id,
'info' => $info,
'date' => $date,
'depth' => 0,
'height' => 0,
'children' => [],
'deletable' => true,
];
$this->Session->write('pivot_thread', $initialPivot);
}
/**
* @param int $id
* @param string $info
* @param string $date
* @param int $fromEvent
*/
private function __continuePivoting($id, $info, $date, $fromEvent)
{
$pivot = $this->Session->read('pivot_thread');
$newPivot = array('id' => $id, 'info' => $info, 'date' => $date, 'depth' => null, 'children' => array(), 'deletable' => true);
if (!is_array($pivot)) {
$this->__startPivoting($id, $info, $date);
return;
}
$newPivot = [
'id' => $id,
'info' => $info,
'date' => $date,
'depth' => null,
'children' => [],
'deletable' => true,
];
if (!$this->__checkForPivot($pivot, $id)) {
$pivot = $this->__insertPivot($pivot, $fromEvent, $newPivot, 0);
}
$this->Session->write('pivot_thread', $pivot);
}
private function __insertPivot($pivot, $oldId, $newPivot, $depth)
/**
* @param array $pivot
* @param int $oldId
* @param array $newPivot
* @param int $depth
* @return array
*/
private function __insertPivot(array $pivot, $oldId, array $newPivot, $depth)
{
$depth++;
if ($pivot['id'] == $oldId) {
@ -1659,7 +1709,12 @@ class EventsController extends AppController
return $pivot;
}
private function __checkForPivot($pivot, $id)
/**
* @param array $pivot
* @param int $id
* @return bool
*/
private function __checkForPivot(array $pivot, $id)
{
if ($id == $pivot['id']) {
return true;
@ -5319,7 +5374,7 @@ class EventsController extends AppController
throw new MethodNotAllowedException('This endpoint requires a POST request.');
}
$event = $this->Event->fetchSimpleEvent($this->Auth->User(), $id);
$event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id);
if (empty($event)) {
throw new MethodNotAllowedException(__('Invalid Event'));
}

View File

@ -3,6 +3,36 @@ App::uses('CakeEmail', 'Network/Email');
class SendEmailException extends Exception {}
class CakeEmailBody
{
/** @var string|null */
public $html;
/** @var string|null */
public $text;
public function __construct($text = null, $html = null)
{
$this->html = $html;
$this->text = $text;
}
/**
* @return string
*/
public function format()
{
if (!empty($this->html) && !empty($this->text)) {
return 'both';
}
if (!empty($this->html)) {
return 'html';
}
return 'text';
}
}
/**
* Class CakeEmailExtended
*
@ -14,7 +44,7 @@ class SendEmailException extends Exception {}
class CakeEmailExtended extends CakeEmail
{
/**
* @var MimeMultipart|MessagePart
* @var MimeMultipart|MessagePart|CakeEmailBody
*/
private $body;
@ -30,7 +60,7 @@ class CakeEmailExtended extends CakeEmail
$headers['Content-Type'] = $this->body->getContentType();
} else if ($this->body instanceof MessagePart) {
$headers = array_merge($headers, $this->body->getHeaders());
} else {
} else if ($this->_emailFormat !== 'both') { // generate correct content-type header for 'text' or 'html' format
$headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->boundary() . '"';
}
@ -71,18 +101,40 @@ class CakeEmailExtended extends CakeEmail
return $this->body->render();
} else if ($this->body instanceof MessagePart) {
return $this->body->render(false);
} else if ($this->body instanceof CakeEmailBody) {
return $this->_render([]); // @see _renderTemplates
}
return $this->_render($this->_wrap($this->body));
throw new InvalidArgumentException("Expected that body is instance of MimeMultipart, MessagePart or CakeEmailBody, " . gettype($this->body) . " given.");
}
// This is hack how to force CakeEmail to always generate multipart message.
protected function _renderTemplates($content)
{
if (!$this->body instanceof CakeEmailBody) {
throw new InvalidArgumentException("Expected instance of CakeEmailBody, " . gettype($this->body) . " given.");
}
$this->_boundary = md5(uniqid());
$output = parent::_renderTemplates($content);
$output[''] = '';
return $output;
$rendered = [];
if (!empty($this->body->text)) {
$rendered['text'] = $this->body->text;
}
if (!empty($this->body->html)) {
$rendered['html'] = $this->body->html;
}
foreach ($rendered as $type => $content) {
$content = str_replace(array("\r\n", "\r"), "\n", $content);
$content = $this->_encodeString($content, $this->charset);
$content = $this->_wrap($content);
$content = implode("\n", $content);
$rendered[$type] = rtrim($content, "\n");
}
// This is hack how to force CakeEmail to always generate multipart message.
$rendered[''] = '';
return $rendered;
}
protected function _render($content)
@ -101,7 +153,7 @@ class CakeEmailExtended extends CakeEmail
if ($content !== null) {
throw new InvalidArgumentException("Content must be null for CakeEmailExtended.");
}
return parent::send($this->body);
return parent::send();
}
public function __toString()
@ -252,11 +304,12 @@ class MessagePart
class SendEmail
{
/**
* @var CryptGpgExtended
*/
/** @var CryptGpgExtended */
private $gpg;
/** @var string|null */
private $transport;
/**
* @param CryptGpgExtended|null $gpg
*/
@ -271,6 +324,14 @@ class SendEmail
}
}
/**
* @param string $transport
*/
public function setTransport($transport)
{
$this->transport = $transport;
}
/**
* @param array $params
* @return array|bool
@ -285,7 +346,8 @@ class SendEmail
}
}
$params['body'] = str_replace('\n', PHP_EOL, $params['body']); // TODO: Why this?
$body = str_replace('\n', PHP_EOL, $params['body']); // TODO: Why this?
$body = new CakeEmailBody($body);
$attachments = array();
if (!empty($params['requestor_gpgkey'])) {
@ -306,10 +368,14 @@ class SendEmail
$email->returnPath(Configure::read('MISP.email'));
$email->to($params['to']);
$email->subject($params['subject']);
$email->emailFormat('text');
$email->body($params['body']);
$email->emailFormat($body->format());
$email->body($body);
$email->attachments($attachments);
if ($this->transport) {
$email->transport($this->transport);
}
$mock = false;
if (!empty(Configure::read('MISP.disable_emailing')) || !empty($params['mock'])) {
$email->transport('Debug');
@ -352,16 +418,20 @@ class SendEmail
/**
* @param array $user
* @param string $subject
* @param string $body
* @param string|null $bodyWithoutEncryption
* @param SendEmailTemplate|string $body
* @param string|false $bodyWithoutEncryption
* @param array $replyToUser
* @return bool True if e-mail is encrypted, false if not.
* @return array
* @throws Crypt_GPG_BadPassphraseException
* @throws Crypt_GPG_Exception
* @throws SendEmailException
*/
public function sendToUser(array $user, $subject, $body, $bodyWithoutEncryption = null, array $replyToUser = array())
public function sendToUser(array $user, $subject, $body, $bodyWithoutEncryption = false, array $replyToUser = array())
{
if ($body instanceof SendEmailTemplate && $bodyWithoutEncryption !== false) {
throw new InvalidArgumentException("When body is instance of SendEmailTemplate, \$bodyWithoutEncryption must be false.");
}
if (Configure::read('MISP.disable_emailing')) {
throw new SendEmailException('Emailing is currently disabled on this instance.');
}
@ -378,14 +448,36 @@ class SendEmail
throw new SendEmailException('Encrypted messages are enforced and the message could not be encrypted for this user as no valid encryption key was found.');
}
// If 'bodyonlyencrypted' is enabled and the user has no encryption key, use the alternate body (if it exists)
if (Configure::read('GnuPG.bodyonlyencrypted') && !$canEncryptSmime && !$canEncryptGpg && $bodyWithoutEncryption) {
$body = $bodyWithoutEncryption;
// If 'GnuPG.bodyonlyencrypted' is enabled and the user has no encryption key, use the alternate body
$hideDetails = Configure::read('GnuPG.bodyonlyencrypted') && !$canEncryptSmime && !$canEncryptGpg;
if ($body instanceof SendEmailTemplate) {
$body->set('canEncryptSmime', $canEncryptSmime);
$body->set('canEncryptGpg', $canEncryptGpg);
$bodyContent = $body->render($hideDetails);
$subject = $body->subject() ?: $subject; // Get generated subject from template
} else {
if ($hideDetails && $bodyWithoutEncryption) {
$body = $bodyWithoutEncryption;
}
$bodyContent = new CakeEmailBody($body);
}
$body = str_replace('\n', PHP_EOL, $body); // TODO: Why this?
$email = $this->create($user, $subject, $bodyContent, [], $replyToUser);
$email = $this->create($user, $subject, $body, array(), $replyToUser);
if ($this->transport) {
$email->transport($this->transport);
}
// Generate `In-Reply-To` and `References` headers to group emails
if ($body instanceof SendEmailTemplate && $body->referenceId()) {
$reference = sha1($body->referenceId() . '|' . Configure::read('MISP.uuid'));
$reference = "<$reference@{$email->domain()}>";
$email->addHeaders([
'In-Reply-To' => $reference,
'References' => $reference,
]);
}
$signed = false;
if (Configure::read('GnuPG.sign')) {
@ -454,8 +546,11 @@ class SendEmail
}
try {
$email->send();
return $encrypted;
return [
'contents' => $email->send(),
'encrypted' => $encrypted,
'subject' => $subject,
];
} catch (Exception $e) {
throw new SendEmailException('The message could not be sent.', 0, $e);
}
@ -499,15 +594,23 @@ class SendEmail
/**
* @param array $user User model
* @param string $subject
* @param string $body
* @param CakeEmailBody $body
* @param array $attachments
* @param array $replyToUser User model
* @return CakeEmailExtended
*/
private function create(array $user, $subject, $body, array $attachments = array(), array $replyToUser = array())
private function create(array $user, $subject, CakeEmailBody $body, array $attachments = array(), array $replyToUser = array())
{
$email = new CakeEmailExtended();
$fromEmail = Configure::read('MISP.email');
// Set correct domain when sending email from CLI
$fromEmailParts = explode('@', $fromEmail, 2);
if (isset($fromEmailParts[1])) {
$email->domain($fromEmailParts[1]);
}
// We must generate message ID by own, because CakeEmail returns different message ID for every call of
// getHeaders() method.
$email->messageId($this->generateMessageId($email));
@ -530,11 +633,11 @@ class SendEmail
$email->replyTo(Configure::read('MISP.email_reply_to'));
}
$email->from(Configure::read('MISP.email'));
$email->returnPath(Configure::read('MISP.email')); // TODO?
$email->from($fromEmail, Configure::read('MISP.email_from_name'));
$email->returnPath($fromEmail); // TODO?
$email->to($user['User']['email']);
$email->subject($subject);
$email->emailFormat('text');
$email->emailFormat($body->format());
$email->body($body);
foreach ($attachments as $key => $value) {
@ -558,14 +661,15 @@ class SendEmail
$messagePart = new MessagePart();
$messagePart->addHeader('Content-Type', array(
'multipart/mixed',
$email->emailFormat() === 'both' ? 'multipart/alternative' : 'multipart/mixed',
'boundary="' . $email->boundary() . '"',
'protected-headers="v1"',
));
// Protect User-Facing Headers according to https://tools.ietf.org/id/draft-autocrypt-lamps-protected-headers-01.html
// Protect User-Facing Headers and Structural Headers according to
// https://tools.ietf.org/id/draft-autocrypt-lamps-protected-headers-02.html
$originalHeaders = $email->getHeaders(array('subject', 'from', 'to'));
$protectedHeaders = array('From', 'To', 'Date', 'Message-ID', 'Subject', 'Reply-To');
$protectedHeaders = ['From', 'To', 'Date', 'Message-ID', 'Subject', 'Reply-To', 'In-Reply-To', 'References'];
foreach ($protectedHeaders as $header) {
if (isset($originalHeaders[$header])) {
$messagePart->addHeader($header, $originalHeaders[$header]);
@ -654,7 +758,7 @@ class SendEmail
$messagePart = new MessagePart();
$messagePart->addHeader('Content-Type', array(
'multipart/mixed',
$email->emailFormat() === 'both' ? 'multipart/alternative' : 'multipart/mixed',
'boundary="' . $email->boundary() . '"',
));
$messagePart->setPayload($renderedEmail);

View File

@ -0,0 +1,88 @@
<?php
class SendEmailTemplate
{
/** @var array */
private $viewVars = [];
/** @var string */
private $viewName;
/** @var string|null */
private $referenceId;
/** @var string|null */
private $subject;
public function __construct($viewName)
{
$this->viewName = $viewName;
}
/**
* This value will be used for grouping emails in mail client.
* @param string|null $referenceId
* @return string
*/
public function referenceId($referenceId = null)
{
if ($referenceId === null) {
return $this->referenceId;
}
$this->referenceId = $referenceId;
}
/**
* Get subject from template. Must be called after render method.
* @param string|null $subject
* @return string
*/
public function subject($subject = null)
{
if ($subject === null) {
return $this->subject;
}
$this->subject = $subject;
}
/**
* Set template variable.
* @param string $key
* @param mixed $value
*/
public function set($key, $value)
{
$this->viewVars[$key] = $value;
}
/**
* @param bool $hideDetails True when GnuPG.bodyonlyencrypted is enabled and e-mail cannot be send in encrypted form
* @return CakeEmailBody
* @throws CakeException
*/
public function render($hideDetails = false)
{
$View = new View();
$View->autoLayout = false;
$View->helpers = ['TextColour'];
$View->loadHelpers();
$View->set($this->viewVars);
$View->set('hideDetails', $hideDetails);
$View->viewPath = $View->layoutPath = 'Emails' . DS . 'html';
try {
$html = $View->render($this->viewName);
} catch (MissingViewException $e) {
$html = null; // HTMl template is optional
}
$View->viewPath = $View->layoutPath = 'Emails' . DS . 'text';
$View->hasRendered = false;
$text = $View->render($this->viewName);
// Template can change default subject.
$this->subject = $View->get('subject');
return new CakeEmailBody($text, $html);
}
}

View File

@ -3036,6 +3036,34 @@ class AppModel extends Model
}
}
/**
* Optimised version of CakePHP _findList method when just one or two fields are set from same model
* @param string $state
* @param array $query
* @param array $results
* @return array
*/
protected function _findList($state, $query, $results = [])
{
if ($state === 'before') {
return parent::_findList($state, $query, $results);
}
if (empty($results)) {
return [];
}
if ($query['list']['groupPath'] === null) {
$keyPath = explode('.', $query['list']['keyPath']);
$valuePath = explode('.', $query['list']['valuePath']);
if ($keyPath[1] === $valuePath[1]) { // same model
return array_column(array_column($results, $keyPath[1]), $valuePath[2], $keyPath[2]);
}
}
return parent::_findList($state, $query, $results);
}
/**
* Find method that allows to fetch just one column from database.
* @param $state

View File

@ -4,6 +4,7 @@ App::uses('CakeEmail', 'Network/Email');
App::uses('RandomTool', 'Tools');
App::uses('AttachmentTool', 'Tools');
App::uses('TmpFileTool', 'Tools');
App::uses('SendEmailTemplate', 'Tools');
/**
* @property User $User
@ -510,24 +511,16 @@ class Event extends AppModel
public function afterSave($created, $options = array())
{
if (!Configure::read('MISP.completely_disable_correlation') && !$created) {
$db = $this->getDataSource();
$updateCorrelation = array();
if (isset($this->data['Event']['date'])) {
$updateCorrelation['Correlation.date'] = $db->value($this->data['Event']['date']);
}
if (isset($this->data['Event']['info'])) {
$updateCorrelation['Correlation.info'] = $db->value($this->data['Event']['info']);
}
$updateCorrelation = [];
if (isset($this->data['Event']['distribution'])) {
$updateCorrelation['Correlation.distribution'] = $db->value($this->data['Event']['distribution']);
$updateCorrelation['Correlation.distribution'] = (int)$this->data['Event']['distribution'];
}
if (isset($this->data['Event']['sharing_group_id'])) {
$updateCorrelation['Correlation.sharing_group_id'] = $db->value($this->data['Event']['sharing_group_id']);
$updateCorrelation['Correlation.sharing_group_id'] = (int)$this->data['Event']['sharing_group_id'];
}
if (!empty($updateCorrelation)) {
$this->Correlation = ClassRegistry::init('Correlation');
$this->Correlation->updateAll($updateCorrelation, array('Correlation.event_id' => intval($this->data['Event']['id'])));
$this->Correlation->updateAll($updateCorrelation, ['Correlation.event_id' => (int)$this->data['Event']['id']]);
}
}
if (empty($this->data['Event']['unpublishAction']) && empty($this->data['Event']['skip_zmq']) && Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) {
@ -3155,7 +3148,7 @@ class Event extends AppModel
{
$event = $this->find('first', [
'conditions' => ['Event.id' => $id],
'contain' => ['EventTag' => ['Tag'], 'ThreatLevel'],
'recursive' => -1,
]);
if (empty($event)) {
throw new NotFoundException('Invalid Event.');
@ -3184,27 +3177,6 @@ class Event extends AppModel
$event['Event']['sharing_group_id'],
$userConditions
);
if (Configure::read('MISP.extended_alert_subject')) {
$subject = preg_replace("/\r|\n/", "", $event['Event']['info']);
if (strlen($subject) > 58) {
$subject = substr($subject, 0, 55) . '... - ';
} else {
$subject .= " - ";
}
} else {
$subject = '';
}
$subjMarkingString = $this->getEmailSubjectMarkForEvent($event);
if (Configure::read('MISP.threatlevel_in_email_subject') === false) {
$threatLevel = '';
} else {
$threatLevel = $event['ThreatLevel']['name'] . " - ";
}
$subject = "[" . Configure::read('MISP.org') . " MISP] Event $id - $subject$threatLevel" . strtoupper($subjMarkingString);
$eventUrl = $this->__getAnnounceBaseurl() . "/events/view/" . $id;
$bodyNoEnc = __("A new or modified event was just published on %s", $eventUrl) . "\n\n";
$bodyNoEnc .= __("If you would like to unsubscribe from receiving such alert e-mails, simply\ndisable publish alerts via %s", $this->__getAnnounceBaseurl() . '/users/edit');
$userCount = count($usersWithAccess);
$this->UserSetting = ClassRegistry::init('UserSetting');
@ -3216,11 +3188,12 @@ class Event extends AppModel
'includeEventCorrelations' => true,
'noEventReports' => true,
'noSightings' => true,
'metadata' => Configure::read('MISP.event_alert_metadata_only') ?: false,
])[0];
if ($this->UserSetting->checkPublishFilter($user, $eventForUser)) {
$body = $this->__buildAlertEmailBody($eventForUser, $user, $oldpublish);
$this->User->sendEmail(['User' => $user], $body, $bodyNoEnc, $subject);
$body = $this->prepareAlertEmail($eventForUser, $user, $oldpublish);
$this->User->sendEmail(['User' => $user], $body, false, null);
}
if ($jobId) {
$this->Job->saveProgress($jobId, null, $k / $userCount * 100);
@ -3233,136 +3206,46 @@ class Event extends AppModel
return true;
}
/**
* @param string $body
* @param string $bodyTempOther
* @param array $objects
* @param int|null $oldpublish Timestamp of latest publish
*/
private function __buildAlertEmailObject(&$body, &$bodyTempOther, array $objects, $oldpublish)
{
foreach ($objects as $object) {
if (isset($oldpublish) && isset($object['timestamp']) && $object['timestamp'] > $oldpublish) {
$body .= '* ';
} else {
$body .= ' ';
}
$body .= $object['name'] . '/' . $object['meta-category'] . "\n";
if (!empty($object['Attribute'])) {
$body .= $this->__buildAlertEmailAttribute($body, $bodyTempOther, $object['Attribute'], $oldpublish, ' ');
}
}
}
/**
* @param string $body
* @param string $bodyTempOther
* @param array $attributes
* @param int|null $oldpublish Timestamp of latest publish
* @param string $indent
*/
private function __buildAlertEmailAttribute(&$body, &$bodyTempOther, array $attributes, $oldpublish, $indent = ' ')
{
$appendlen = 20;
foreach ($attributes as $attribute) {
$ids = $attribute['to_ids'] ? ' (IDS)' : '';
// Defanging URLs (Not "links") emails domains/ips in notification emails
$value = $attribute['value'];
if ('url' == $attribute['type'] || 'uri' == $attribute['type']) {
$value = str_ireplace("http", "hxxp", $value);
$value = str_ireplace(".", "[.]", $value);
} elseif (in_array($attribute['type'], array('email-src', 'email-dst', 'whois-registrant-email', 'dns-soa-email', 'email-reply-to'))) {
$value = str_replace("@", "[at]", $value);
} elseif (in_array($attribute['type'], array('hostname', 'domain', 'ip-src', 'ip-dst', 'domain|ip'))) {
$value = str_replace(".", "[.]", $value);
}
$strRepeatCount = $appendlen - 2 - strlen($attribute['type']);
$strRepeat = ($strRepeatCount > 0) ? str_repeat(' ', $strRepeatCount) : '';
if (isset($oldpublish) && isset($attribute['timestamp']) && $attribute['timestamp'] > $oldpublish) {
$line = '* ' . $indent . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $value . $ids . " *\n";
} else {
$line = $indent . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $value . $ids . "\n";
}
if (!empty($attribute['AttributeTag'])) {
$tags = [];
foreach ($attribute['AttributeTag'] as $aT) {
$tags[] = $aT['Tag']['name'];
}
$line .= ' - Tags: ' . implode(', ', $tags) . "\n";
}
if ('other' == $attribute['type']) { // append the 'other' attribute types to the bottom.
$bodyTempOther .= $line;
} else {
$body .= $line;
}
}
}
/**
* @param array $event
* @param array $user
* @param int|null $oldpublish Timestamp of latest publish
* @return string
* @param array $user E-mail receiver
* @param int|null $oldpublish Timestamp of previous publishing.
* @return SendEmailTemplate
* @throws CakeException
*/
private function __buildAlertEmailBody(array $event, array $user, $oldpublish)
public function prepareAlertEmail(array $event, array $user, $oldpublish = null)
{
// The mail body, h() is NOT needed as we are sending plain-text mails.
$body = "";
$body .= '==============================================' . "\n";
$body .= 'URL : ' . $this->__getAnnounceBaseurl() . '/events/view/' . $event['Event']['id'] . "\n";
$body .= 'Event ID : ' . $event['Event']['id'] . "\n";
$body .= 'Date : ' . $event['Event']['date'] . "\n";
if (Configure::read('MISP.showorg')) {
$body .= 'Reported by : ' . $event['Orgc']['name'] . "\n";
$body .= 'Local owner of the event : ' . $event['Org']['name'] . "\n";
}
$body .= 'Distribution: ' . $this->distributionLevels[$event['Event']['distribution']] . "\n";
if ($event['Event']['distribution'] == 4) {
$body .= 'Sharing Group: ' . $event['SharingGroup']['name'] . "\n";
}
$tags = [];
foreach ($event['EventTag'] as $tag) {
$tags[] = $tag['Tag']['name'];
}
$body .= 'Tags: ' . implode(', ', $tags) . "\n";
$body .= 'Threat Level: ' . $event['ThreatLevel']['name'] . "\n";
$body .= 'Analysis : ' . $this->analysisLevels[$event['Event']['analysis']] . "\n";
$body .= 'Description : ' . $event['Event']['info'] . "\n";
if (!empty($event['RelatedEvent'])) {
$body .= '==============================================' . "\n";
$body .= 'Related to: '. "\n";
foreach ($event['RelatedEvent'] as $relatedEvent) {
$body .= $this->__getAnnounceBaseurl() . '/events/view/' . $relatedEvent['Event']['id'] . ' (' . $relatedEvent['Event']['date'] . ') ' ."\n";
if (Configure::read('MISP.extended_alert_subject')) {
$subject = preg_replace("/\r|\n/", "", $event['Event']['info']);
if (strlen($subject) > 58) {
$subject = substr($subject, 0, 55) . '... - ';
} else {
$subject .= " - ";
}
$body .= '==============================================' . "\n";
} else {
$subject = '';
}
$bodyTempOther = "";
if (!empty($event['Attribute'])) {
$body .= 'Attributes (* indicates a new or modified attribute):' . "\n";
$this->__buildAlertEmailAttribute($body, $bodyTempOther, $event['Attribute'], $oldpublish);
if (Configure::read('MISP.threatlevel_in_email_subject') === false) {
$threatLevel = '';
} else {
$threatLevel = $event['ThreatLevel']['name'] . " - ";
}
if (!empty($event['Object'])) {
$body .= 'Objects (* indicates a new or modified object):' . "\n";
$this->__buildAlertEmailObject($body, $bodyTempOther, $event['Object'], $oldpublish);
}
if (!empty($bodyTempOther)) {
$body .= "\n";
}
$body .= $bodyTempOther; // append the 'other' attribute types to the bottom.
$body .= '==============================================' . "\n";
$body .= sprintf(
"You receive this e-mail because the e-mail address %s is set to receive publish alerts on the MISP instance at %s.%s%s",
$user['email'],
$this->__getAnnounceBaseurl(),
PHP_EOL,
PHP_EOL
);
$body .= "If you would like to unsubscribe from receiving such alert e-mails, simply\ndisable publish alerts via " . $this->__getAnnounceBaseurl() . '/users/edit' . PHP_EOL;
$body .= '==============================================' . "\n";
return $body;
$subjMarkingString = $this->getEmailSubjectMarkForEvent($event);
$subject = "[" . Configure::read('MISP.org') . " MISP] Event {$event['Event']['id']} - $subject$threatLevel" . strtoupper($subjMarkingString);
$template = new SendEmailTemplate('alert');
$template->set('event', $event);
$template->set('user', $user);
$template->set('oldPublishTimestamp', $oldpublish);
$template->set('baseurl', $this->__getAnnounceBaseurl());
$template->set('distributionLevels', $this->distributionLevels);
$template->set('analysisLevels', $this->analysisLevels);
$template->set('tlp', $subjMarkingString);
$template->subject($subject);
$template->referenceId("event-alert|{$event['Event']['id']}");
return $template;
}
/**
@ -3422,69 +3305,33 @@ class Event extends AppModel
$tplColorString = $this->getEmailSubjectMarkForEvent($event);
$subject = "[" . Configure::read('MISP.org') . " MISP] Need info about event $id - " . strtoupper($tplColorString);
$result = true;
foreach ($orgMembers as $reporter) {
list($bodyevent, $body) = $this->__buildContactEventEmailBody($user, $message, $event);
$result = $this->User->sendEmail($reporter, $bodyevent, $body, $subject, $user) && $result;
foreach ($orgMembers as $eventReporter) {
$body = $this->prepareContactAlertEmail($user, $eventReporter, $message, $event);
$result = $this->User->sendEmail($eventReporter, $body, false, $subject, $user) && $result;
}
return $result;
}
private function __buildContactEventEmailBody(array $user, $message, array $event)
/**
* @param array $user
* @param array $eventReporter
* @param string $message
* @param array $event
* @return SendEmailTemplate
*/
private function prepareContactAlertEmail(array $user, array $eventReporter, $message, array $event)
{
// The mail body, h() is NOT needed as we are sending plain-text mails.
$body = "";
$body .= "Hello, \n";
$body .= "\n";
$body .= "Someone wants to get in touch with you concerning a MISP event. \n";
$body .= "\n";
$body .= "You can reach them at " . $user['User']['email'] . "\n";
if (!empty($user['User']['gpgkey'])) {
$body .= "Their GnuPG key is added as attachment to this email. \n";
}
if (!empty($user['User']['certif_public'])) {
$body .= "Their Public certificate is added as attachment to this email. \n";
}
$body .= "\n";
$body .= "They wrote the following message: \n";
$body .= $message . "\n";
$body .= "\n";
$body .= "\n";
$body .= "The event is the following: \n";
// print the event in mail-format
// LATER place event-to-email-layout in a function
$body .= 'URL : ' . $this->__getAnnounceBaseurl() . '/events/view/' . $event['Event']['id'] . "\n";
$bodyevent = $body;
$bodyevent .= 'Event ID : ' . $event['Event']['id'] . "\n";
$bodyevent .= 'Date : ' . $event['Event']['date'] . "\n";
if (Configure::read('MISP.showorg')) {
$bodyevent .= 'Reported by : ' . $event['Orgc']['name'] . "\n";
}
$bodyevent .= 'Risk : ' . $event['ThreatLevel']['name'] . "\n";
$bodyevent .= 'Analysis : ' . $this->analysisLevels[$event['Event']['analysis']] . "\n";
foreach ($event['RelatedEvent'] as $relatedEvent) {
$bodyevent .= 'Related to : ' . $this->__getAnnounceBaseurl() . '/events/view/' . $relatedEvent['Event']['id'] . ' (' . $relatedEvent['Event']['date'] . ')' . "\n";
}
$bodyevent .= 'Info : ' . "\n";
$bodyevent .= $event['Event']['info'] . "\n";
$bodyTempOther = "";
if (!empty($event['Attribute'])) {
$bodyevent .= 'Attributes:' . "\n";
$this->__buildAlertEmailAttribute($bodyevent, $bodyTempOther, $event['Attribute'], null);
}
if (!empty($event['Object'])) {
$bodyevent .= 'Objects:' . "\n";
$this->__buildAlertEmailObject($bodyevent, $bodyTempOther, $event['Object'], null);
}
if (!empty($bodyTempOther)) {
$bodyevent .= "\n";
}
$bodyevent .= $bodyTempOther; // append the 'other' attribute types to the bottom.
return array($bodyevent, $body);
$template = new SendEmailTemplate('alert_contact');
$template->set('event', $event);
$template->set('requestor', $user);
$template->set('message', $message);
$template->set('user', $this->User->rearrangeToAuthForm($eventReporter));
$template->set('baseurl', $this->__getAnnounceBaseurl());
$template->set('distributionLevels', $this->distributionLevels);
$template->set('analysisLevels', $this->analysisLevels);
$template->set('contactAlert', true);
$template->set('tlp', $this->getEmailSubjectMarkForEvent($event));
return $template;
}
public function captureSGForElement($element, $user, $server=false)
@ -7340,7 +7187,7 @@ class Event extends AppModel
/**
* extractAllTagNames Returns all tag names attached to any elements in an event
*
* @param mixed $event
* @param array $event
* @return array All tag names in the event
*/
public function extractAllTagNames(array $event)
@ -7370,10 +7217,12 @@ class Event extends AppModel
}
if (!empty($event['Object'])) {
foreach ($event['Object'] as $object) {
foreach ($object['Attribute'] as $attribute) {
foreach ($attribute['AttributeTag'] as $attributeTag) {
$tagName = $attributeTag['Tag']['name'];
$tags[$tagName] = $tagName;
if (!empty($object['Attribute'])) {
foreach ($object['Attribute'] as $attribute) {
foreach ($attribute['AttributeTag'] as $attributeTag) {
$tagName = $attributeTag['Tag']['name'];
$tags[$tagName] = $tagName;
}
}
}
}

View File

@ -5,7 +5,8 @@ App::uses('AppModel', 'Model');
class EventLock extends AppModel
{
// In seconds
const DEFAULT_TTL = 900;
const DEFAULT_TTL = 900,
PREFIX = 'misp:event_lock';
/**
* @param array $user
@ -14,7 +15,7 @@ class EventLock extends AppModel
*/
public function insertLock(array $user, $eventId)
{
return $this->insertLockToRedis("misp:event_lock:$eventId:user:{$user['id']}", [
return $this->insertLockToRedis($eventId, "user:{$user['id']}", [
'type' => 'user',
'timestamp' => time(),
'User' => [
@ -32,7 +33,7 @@ class EventLock extends AppModel
*/
public function insertLockBackgroundJob($eventId, $jobId)
{
return $this->insertLockToRedis("misp:event_lock:$eventId:job:$jobId", [
return $this->insertLockToRedis($eventId, "job:$jobId", [
'type' => 'job',
'timestamp' => time(),
'job_id' => $jobId,
@ -45,21 +46,38 @@ class EventLock extends AppModel
*/
public function insertLockApi($eventId, array $user)
{
$rand = mt_rand();
if ($this->insertLockToRedis("misp:event_lock:$eventId:api:{$user['id']}:$rand", [
$apiLockId = mt_rand();
if ($this->insertLockToRedis($eventId, "api:{$user['id']}:$apiLockId", [
'type' => 'api',
'user_id' => $user['id'],
'timestamp' => time(),
])) {
return $rand;
return $apiLockId;
}
return null;
}
/**
* @param int $eventId
* @param int $apiLockId
* @return bool
*/
public function deleteApiLock($eventId, $apiLockId, array $user)
{
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
$deleted = $redis->hdel(self::PREFIX . $eventId, "api:{$user['id']}:$apiLockId");
return $deleted > 0;
}
/**
* @param int $eventId
* @param int $jobId
* @return null
* @return bool
*/
public function deleteBackgroundJobLock($eventId, $jobId)
{
@ -69,40 +87,7 @@ class EventLock extends AppModel
return false;
}
$deleted = $redis->del("misp:event_lock:$eventId:job:$jobId");
return $deleted > 0;
}
/**
* @param string $key
* @param array $data
* @return bool
*/
private function insertLockToRedis($key, array $data)
{
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
return $redis->setex($key, self::DEFAULT_TTL, json_encode($data));
}
/**
* @param int $eventId
* @param int $lockId
* @return bool
*/
public function deleteApiLock($eventId, $lockId, array $user)
{
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
$deleted = $redis->del("misp:event_lock:$eventId:api:{$user['id']}:$lockId");
$deleted = $redis->hDel(self::PREFIX . $eventId, "job:$jobId");
return $deleted > 0;
}
@ -120,13 +105,39 @@ class EventLock extends AppModel
return [];
}
$keys = $redis->keys("misp:event_lock:$eventId:*");
$keys = $redis->hGetAll(self::PREFIX . $eventId);
if (empty($keys)) {
return [];
}
return array_map(function ($value) {
return $this->jsonDecode($value);
}, $redis->mget($keys));
$output = [];
$now = time();
foreach ($keys as $value) {
$value = $this->jsonDecode($value);
if ($value['timestamp'] + self::DEFAULT_TTL > $now) {
$output[] = $value;
}
}
return $output;
}
/**
* @param int $eventId
* @param string $lockId
* @param array $data
* @return bool
*/
private function insertLockToRedis($eventId, $lockId, array $data)
{
try {
$redis = $this->setupRedisWithException();
} catch (Exception $e) {
return false;
}
$pipeline = $redis->pipeline();
$pipeline->hSet(self::PREFIX . $eventId, $lockId, json_encode($data));
$pipeline->expire(self::PREFIX . $eventId, self::DEFAULT_TTL); // prolong TTL
return $pipeline->exec()[0] !== false;
}
}

View File

@ -1125,7 +1125,7 @@ class Server extends AppModel
* @param array $server
* @param array $user
* @param string|int $technique Either the 'full' string or the event id
* @param bool $event
* @param array|bool $event
* @return array List of successfully pushed clusters
*/
public function syncGalaxyClusters($HttpSocket, array $server, array $user, $technique='full', $event=false)
@ -4739,6 +4739,14 @@ class Server extends AppModel
'test' => 'testBool',
'type' => 'boolean',
),
'email_from_name' => [
'level' => 2,
'description' => __('Notification e-mail sender name.'),
'value' => '',
'errorMessage' => '',
'test' => 'testForEmpty',
'type' => 'string',
],
'taxii_sync' => array(
'level' => 3,
'description' => __('This setting is deprecated and can be safely removed.'),
@ -4856,12 +4864,20 @@ class Server extends AppModel
),
'extended_alert_subject' => array(
'level' => 1,
'description' => __('enabling this flag will allow the event description to be transmitted in the alert e-mail\'s subject. Be aware that this is not encrypted by GnuPG, so only enable it if you accept that part of the event description will be sent out in clear-text.'),
'description' => __('Enabling this flag will allow the event description to be transmitted in the alert e-mail\'s subject. Be aware that this is not encrypted by GnuPG, so only enable it if you accept that part of the event description will be sent out in clear-text.'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean'
),
'event_alert_metadata_only' => [
'level' => self::SETTING_OPTIONAL,
'description' => __('Send just event metadata (attributes and objects will be omitted) for event alert.'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean'
],
'default_event_distribution' => array(
'level' => 0,
'description' => __('The default distribution setting for events (0-3).'),

View File

@ -713,12 +713,12 @@ class SharingGroup extends AppModel
$this->create();
$newSG = array();
$date = date('Y-m-d H:i:s');
if (empty($sg['name']) || empty($sg['releasability'])) {
if (empty($sg['name'])) {
return false;
}
$newSG = [
'name' => $sg['name'],
'releasability' => $sg['releasability'],
'releasability' => !isset($sg['releasability']) ? '' : $sg['releasability'],
'description' => !isset($sg['description']) ? '' : $sg['description'],
'uuid' => !isset($sg['uuid']) ? CakeText::uuid() : $sg['uuid'],
'organisation_uuid' => !isset($sg['organisation_uuid']) ? $user['Organisation']['uuid'] : $sg['organisation_uuid'],

View File

@ -536,7 +536,6 @@ class Sighting extends AppModel
*/
public function attachToEvent(array $event, array $user, $attribute = null, $extraConditions = false, $forSync = false)
{
$contain = [];
$conditions = array('Sighting.event_id' => $event['Event']['id']);
if (isset($attribute['Attribute']['id'])) {
$conditions['Sighting.attribute_id'] = $attribute['Attribute']['id'];
@ -547,8 +546,6 @@ class Sighting extends AppModel
'conditions' => ['Attribute.id' => $attribute],
'fields' => ['Attribute.uuid']
]);
} else {
$contain['Attribute'] = ['fields' => 'Attribute.uuid'];
}
$ownEvent = $user['Role']['perm_site_admin'] || $event['Event']['org_id'] == $user['org_id'];
@ -570,18 +567,29 @@ class Sighting extends AppModel
$sightings = $this->find('all', array(
'conditions' => $conditions,
'recursive' => -1,
'contain' => $contain,
));
if (empty($sightings)) {
return array();
return [];
}
foreach ($sightings as $k => $sighting) {
if (isset($sighting['Attribute']['uuid'])) {
$sighting['Sighting']['attribute_uuid'] = $sighting['Attribute']['uuid'];
} else {
$sighting['Sighting']['attribute_uuid'] = $attribute['Attribute']['uuid'];
if ($attribute === null) {
// Do not add attribute uuid in contain query, joining is slow and takes more memory
$attributeUuids = $this->Attribute->find('all', [
'conditions' => ['Attribute.event_id' => $event['Event']['id']],
'fields' => ['Attribute.id', 'Attribute.uuid'],
'recursive' => -1,
]);
// `array_column` is much faster than find('list')
$attributeUuids = array_column(array_column($attributeUuids, 'Attribute'), 'uuid', 'id');
foreach ($sightings as $k => $sighting) {
$sighting['Sighting']['attribute_uuid'] = $attributeUuids[$sighting['Sighting']['attribute_id']];
$sightings[$k] = $sighting;
}
unset($attributeUuids);
} else {
foreach ($sightings as $k => $sighting) {
$sighting['Sighting']['attribute_uuid'] = $attribute['Attribute']['uuid'];
$sightings[$k] = $sighting;
}
$sightings[$k] = $sighting;
}
return $this->attachOrgToSightings($sightings, $user, $forSync);
}

View File

@ -757,7 +757,7 @@ class User extends AppModel
'conditions' => $conditions,
'recursive' => -1,
'fields' => array('id', 'email', 'gpgkey', 'certif_public', 'org_id', 'disabled'),
'contain' => ['Role' => ['fields' => ['perm_site_admin', 'perm_audit']], 'Organisation' => ['fields' => ['id']]],
'contain' => ['Role' => ['fields' => ['perm_site_admin', 'perm_audit']], 'Organisation' => ['fields' => ['id', 'name']]],
));
foreach ($users as $k => $user) {
$user = $user['User'];
@ -786,7 +786,7 @@ class User extends AppModel
* the remaining two parameters are the e-mail subject and a secondary user object which will be used as the replyto address if set. If it is set and an encryption key for the replyTo user exists, then his/her public key will also be attached
*
* @param array $user
* @param string $body
* @param SendEmailTemplate|string $body
* @param string|false $bodyNoEnc
* @param string $subject
* @param array|false $replyToUser
@ -800,13 +800,14 @@ class User extends AppModel
return true;
}
$this->Log = ClassRegistry::init('Log');
$this->loadLog();
$replyToLog = $replyToUser ? ' from ' . $replyToUser['User']['email'] : '';
$gpg = $this->initializeGpg();
$sendEmail = new SendEmail($gpg);
try {
$encrypted = $sendEmail->sendToUser($user, $subject, $body, $bodyNoEnc ?: null, $replyToUser ?: array());
$result = $sendEmail->sendToUser($user, $subject, $body, $bodyNoEnc,$replyToUser ?: []);
} catch (SendEmailException $e) {
$this->logException("Exception during sending e-mail", $e);
@ -823,9 +824,9 @@ class User extends AppModel
return false;
}
$logTitle = $encrypted ? 'Encrypted email' : 'Email';
$logTitle = $result['encrypted'] ? 'Encrypted email' : 'Email';
// Intentional two spaces to pass test :)
$logTitle .= $replyToLog . ' to ' . $user['User']['email'] . ' sent, titled "' . $subject . '".';
$logTitle .= $replyToLog . ' to ' . $user['User']['email'] . ' sent, titled "' . $result['subject'] . '".';
$this->Log->create();
$this->Log->save(array(

View File

@ -1,8 +1,8 @@
<div class="footer <?php echo $debugMode;?>">
<div id="shortcutsListContainer" class="<?php echo $debugMode == 'debugOn' ? 'hidden': ''; ?>">
<div id="triangle"></div>
<div id="triangle" title="<?= __('Show keyboard shortcuts help') ?>"></div>
<div id="shortcutsList">
<span> <?php echo __('Keyboard shortcuts for this page'); ?>:</span><br>
<?= __('Keyboard shortcuts for this page') ?>:<br>
<div id="shortcuts"><?php echo __('none'); ?></div>
</div>
</div>

View File

@ -0,0 +1,116 @@
<?php
if (!isset($oldPublishTimestamp)) {
$oldPublishTimestamp = null;
}
if (!isset($contactAlert)) {
$contactAlert = false;
}
if ($hideDetails) { // Used when GnuPG.bodyonlyencrypted is enabled and e-mail cannot be send in encrypted form
$eventUrl = $baseurl . "/events/view/" . $event['Event']['id'];
echo __("A new or modified event was just published on %s", $eventUrl) . PHP_EOL . PHP_EOL;
echo __("If you would like to unsubscribe from receiving such alert e-mails, simply\ndisable publish alerts via %s", $baseurl . '/users/edit');
return;
}
$renderAttributes = function(array $attributes, $indent = ' ') use ($oldPublishTimestamp) {
$appendlen = 20;
foreach ($attributes as $attribute) {
$ids = $attribute['to_ids'] ? ' (IDS)' : '';
// Defanging URLs (Not "links") emails domains/ips in notification emails
$value = $attribute['value'];
if ('url' === $attribute['type'] || 'uri' === $attribute['type']) {
$value = str_ireplace("http", "hxxp", $value);
$value = str_ireplace(".", "[.]", $value);
} elseif (in_array($attribute['type'], ['email-src', 'email-dst', 'whois-registrant-email', 'dns-soa-email', 'email-reply-to'], true)) {
$value = str_replace("@", "[at]", $value);
} elseif (in_array($attribute['type'], ['hostname', 'domain', 'ip-src', 'ip-dst', 'domain|ip'], true)) {
$value = str_replace(".", "[.]", $value);
}
$strRepeatCount = $appendlen - 2 - strlen($attribute['type']);
$strRepeat = ($strRepeatCount > 0) ? str_repeat(' ', $strRepeatCount) : '';
if (isset($oldPublishTimestamp) && isset($attribute['timestamp']) && $attribute['timestamp'] > $oldPublishTimestamp) {
$line = '* ' . $indent . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $value . $ids . " *\n";
} else {
$line = $indent . $attribute['category'] . '/' . $attribute['type'] . $strRepeat . ': ' . $value . $ids . "\n";
}
if (!empty($attribute['AttributeTag'])) {
$tags = [];
foreach ($attribute['AttributeTag'] as $aT) {
$tags[] = $aT['Tag']['name'];
}
$line .= ' - Tags: ' . implode(', ', $tags) . "\n";
}
echo $line;
}
};
$renderObjects = function(array $objects) use ($renderAttributes, $oldPublishTimestamp) {
foreach ($objects as $object) {
$body = '';
if (isset($oldPublishTimestamp) && isset($object['timestamp']) && $object['timestamp'] > $oldPublishTimestamp) {
$body .= '* ';
} else {
$body .= ' ';
}
$body .= $object['name'] . '/' . $object['meta-category'] . "\n";
if (!empty($object['Attribute'])) {
$body .= $renderAttributes($object['Attribute'], ' ');
}
echo $body;
}
};
$tags = [];
foreach ($event['EventTag'] as $tag) {
$tags[] = $tag['Tag']['name'];
}
?>
==============================================
URL : <?= $baseurl ?>/events/view/<?= $event['Event']['id'] . PHP_EOL ?>
Event ID : <?= $event['Event']['id'] . PHP_EOL ?>
Date : <?= $event['Event']['date'] . PHP_EOL ?>
<?php if (Configure::read('MISP.showorg')): ?>
Reported by : <?= $event['Orgc']['name'] . PHP_EOL ?>
Local owner of the event : <?= $event['Org']['name'] . PHP_EOL ?>
<?php endif; ?>
Distribution: <?= $distributionLevels[$event['Event']['distribution']] . PHP_EOL ?>
<?php if ($event['Event']['distribution'] == 4): ?>
Sharing Group: <?= $event['SharingGroup']['name'] . PHP_EOL ?>
<?php endif; ?>
Tags: <?= implode(", ", $tags) . PHP_EOL ?>
Threat Level: <?= $event['ThreatLevel']['name'] . PHP_EOL ?>
Analysis : <?= $analysisLevels[$event['Event']['analysis']] . PHP_EOL ?>
Description : <?= $event['Event']['info'] . PHP_EOL ?>
<?php if (!empty($event['RelatedEvent'])): ?>
==============================================
Related to:
<?php
foreach ($event['RelatedEvent'] as $relatedEvent) {
echo $baseurl . '/events/view/' . $relatedEvent['Event']['id'] . ' (' . $relatedEvent['Event']['date'] . ') ' . "\n";
}
?>
==============================================
<?php endif; ?>
<?php if (!empty($event['Attribute'])): ?>
Attributes<?= isset($oldPublishTimestamp) ? " (* indicates a new or modified attribute since last update):\n" : ":\n" ?>
<?= $renderAttributes($event['Attribute']) ?>
<?php endif; ?>
<?php if (!empty($event['Object'])): ?>
Objects<?= isset($oldPublishTimestamp) ? " (* indicates a new or modified object since last update):\n" : ":\n" ?>
<?= $renderObjects($event['Object']) ?>
<?php endif; ?>
==============================================
You receive this e-mail because the e-mail address <?= $user['email'] ?> is set
to receive <?= $contactAlert ? 'contact' : 'publish' ?> alerts on the MISP instance at <?= $baseurl ?>.
If you would like to unsubscribe from receiving such alert e-mails, simply
disable <?= $contactAlert ? 'contact' : 'publish' ?> alerts via <?= $baseurl ?>/users/edit
==============================================

View File

@ -0,0 +1,23 @@
Hello,
Someone wants to get in touch with you concerning a MISP event.
You can reach them at <?= $requestor['User']['email'] ?>
<?php if (!empty($requestor['User']['gpgkey'])): ?>
Their PGP key is added as attachment to this email.
<?php endif; ?>
<?php if (!empty($requestor['User']['certif_public'])): ?>
Their Public certificate is added as attachment to this email.
<?php endif; ?>
They wrote the following message:
<?= $message ?>
The event is the following:
<?php
if ($hideDetails) {
echo $baseurl . ' /events/view/' . $event['Event']['id'];
} else {
require __DIR__ . '/alert.ctp'; // include event details
}

View File

@ -39,17 +39,24 @@ form button.btn[disabled] {
float:none;
}*/
h2{
h2 {
font-size: 25px;
clear:both;
}
h3{
h3 {
font-size: 18px;
line-height: 20px;
}
pre {
font-size: 11px;
code {
padding: 0;
border: 0;
background: transparent;
font-size: .875em;
}
pre {
font-size: 12px;
}
.ellipsis-overflow {
@ -68,9 +75,7 @@ pre {
.navbar-lab .navbar-inner{
background: #FF9900;
-webkit-border-radius: 0px !important;
-moz-border-radius: 0px !important;
border-radius: 0px !important;
border-radius: 0 !important;
}
.navbar-lab .nav > li > a{
@ -78,8 +83,6 @@ pre {
}
.navbar-inner{
-webkit-border-radius: 0px !important;
-moz-border-radius: 0px !important;
border-radius: 0px !important;
}
@ -597,8 +600,6 @@ dd {
height: 100%;
position: absolute;
padding: 0;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
background: rgba(0,0,0,0.25);
box-shadow: 0 2px 6px rgba(0,0,0,0.5), inset 0 1px rgba(255,255,255,0.3), inset 0 10px rgba(255,255,255,0.1), inset 0 10px 20px rgba(255,255,255,0.3), inset 0 -15px 30px rgba(0,0,0,0.3);
@ -679,10 +680,8 @@ dd {
.quote {
margin: 0 10px;
margin-bottom: 10px;
-moz-border-radius: 0px;
-webkit-border-radius: 0px;
background: #f2f6f8 none;
border-radius: 0px;
border-radius: 0;
border: 1px solid #417394;
position: relative;
top: 0;
@ -691,7 +690,6 @@ dd {
.loading {
display: none;
text-color: #0088cc;
position: fixed;
top: 50%;
left: 50%;
@ -706,27 +704,17 @@ dd {
left: 13px;
}
.toggle {
border-radius: 0px !important;
-webkit-border-radius: 0px !important;
-moz-border-radius: 0px !important;
border-radius: 0 !important;
}
.toggle-left {
border-bottom-right-radius: 0px !important;
-webkit-bottom-right-radius: 0px !important;
-moz-border-bottom-right-radius: 0px !important;
border-top-right-radius: 0px !important;
-webkit-top-right-radius: 0px !important;
-moz-border-top-right-radius: 0px !important;
border-bottom-right-radius: 0 !important;
border-top-right-radius: 0 !important;
}
.toggle-right {
border-bottom-left-radius: 0px !important;
-webkit-bottom-left-radius: 0px !important;
-moz-border-bottom-left-radius: 0px !important;
border-top-left-radius: 0px !important;
-webkit-top-left-radius: 0px !important;
-moz-border-top-left-radius: 0px !important;
border-bottom-left-radius: 0 !important;
border-top-left-radius: 0 !important;
}
.spinner {
@ -734,9 +722,6 @@ dd {
width:60px;
margin:0 auto;
position:relative;
-webkit-animation: rotation .6s infinite linear;
-moz-animation: rotation .6s infinite linear;
-o-animation: rotation .6s infinite linear;
animation: rotation .6s infinite linear;
border:6px solid rgba(0,174,239,.15);
border-radius:100%;
@ -800,20 +785,12 @@ a.proposal_link_red:hover {
}
.tag:first-child {
-webkit-border-top-left-radius: 3px;
-moz-border-top-left-radius: 3px;
border-top-left-radius: 3px;
-webkit-border-bottom-left-radius: 3px;
-moz-border-bottom-left-radius: 3px;
border-bottom-left-radius: 3px;
}
.tag:last-child {
-webkit-border-top-right-radius: 3px;
-moz-border-top-right-radius: 3px;
border-top-right-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
-moz-border-bottom-right-radius: 3px;
border-bottom-right-radius: 3px;
margin-right:2px;
}
@ -837,11 +814,7 @@ a.proposal_link_red:hover {
font-size: 12px;
font-weight: bold;
line-height: 14px;
-webkit-border-top-left-radius: 3px;
-moz-border-top-left-radius: 3px;
border-top-left-radius: 3px;
-webkit-border-bottom-left-radius: 3px;
-moz-border-bottom-left-radius: 3px;
border-bottom-left-radius: 3px;
box-shadow: 3px 3px 3px #888888;
}
@ -852,11 +825,7 @@ a.proposal_link_red:hover {
font-size: 12px;
font-weight: bold;
line-height: 14px;
-webkit-border-top-right-radius: 3px;
-moz-border-top-right-radius: 3px;
border-top-right-radius: 3px;
-webkit-border-bottom-right-radius: 3px;
-moz-border-bottom-right-radius: 3px;
border-bottom-right-radius: 3px;
box-shadow: 3px 3px 3px #888888;
background-color:black;
@ -869,8 +838,6 @@ a.proposal_link_red:hover {
font-size: 12px;
font-weight: bold;
line-height: 14px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
box-shadow: 3px 3px 3px #888888;
}
@ -1244,8 +1211,6 @@ li .generic-picker-item-element-check {
padding-left:10px;
padding-right:10px;
border-radius: 6px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
box-shadow: 2px 2px 2px #888888;
}
@ -1550,11 +1515,9 @@ span.success {
.attributehistogramBar {
overflow: hidden;
height:13px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
box-shadow: 2px 2px 2px #888888;
margin-left:0px;
margin-left:0;
}
.attributehistogram-left-table {
@ -1580,8 +1543,6 @@ span.success {
display:inline-block;
width:10px;
height:10px;
-webkit-border-radius: 2px;
-moz-border-radius: 2px;
border-radius: 2px;
box-shadow: 1px 1px 1px #888888;
}
@ -1627,10 +1588,7 @@ span.success {
margin: 0 0 15px 0;
background-color: #FFFFFF;
padding:0px !important;
padding-right:0px !important;
border: 1px solid black !important;
-webkit-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
}
@ -1719,9 +1677,7 @@ span.success {
color:white;
font-weight:bold;
height:25px;
-moz-border-radius: 0px;
-webkit-border-radius: 7px 7px 0px 0px;
border-radius: 7px 7px 0px 0px;
border-radius: 7px 7px 0 0;
}
.templateElementHeaderText {
@ -1735,9 +1691,7 @@ span.success {
height: 100%;
position: absolute;
padding: 0;
-webkit-border-radius: 7px 7px 0px 0px;
-moz-border-radius: 7px 7px 0px 0px;
border-radius: 7px 7px 0px 0px;
border-radius: 7px 7px 0 0;
background: rgba(0,0,0,0.1.25);
box-shadow: 0 2px 6px rgba(0,0,0,0.5),
inset 0 1px rgba(255,255,255,0.3),
@ -1754,10 +1708,8 @@ span.success {
height:40px;
line-height:40px;
background-color: #FFFFFF;
padding:0px !important;
padding:0 !important;
border: 1px dashed black !important;
-webkit-radius: 8px;
-moz-border-radius: 8px;
border-radius: 8px;
text-align:center;
vertical-align:middle;
@ -2095,21 +2047,12 @@ a.discrete {
.progress-queued .bar, .progress .bar-queued {
background-color: #A0A0A0;
background-image: -moz-linear-gradient(top, #dbdbdb, #bfbfbf);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#dbdbdb), to(#bfbfbf));
background-image: -webkit-linear-gradient(top, #dbdbdb, #bfbfbf);
background-image: -o-linear-gradient(top, #dbdbdb, #bfbfbf);
background-image: linear-gradient(to bottom, #dbdbdb, #bfbfbf);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffbfbfbf', GradientType=0);
}
.progress-queued.progress-striped .bar, .progress-striped .bar-warning {
background-color: #A0A0A0;
background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
}
@ -2414,18 +2357,6 @@ table tr:hover .down-expand-button {
padding-left:0px !important;
}
@-webkit-keyframes rotation {
from {-webkit-transform: rotate(0deg);}
to {-webkit-transform: rotate(359deg);}
}
@-moz-keyframes rotation {
from {-moz-transform: rotate(0deg);}
to {-moz-transform: rotate(359deg);}
}
@-o-keyframes rotation {
from {-o-transform: rotate(0deg);}
to {-o-transform: rotate(359deg);}
}
@keyframes rotation {
from {
transform: rotate(0deg);
@ -2754,3 +2685,4 @@ Query builder
.query-builder .rule-value-container label input {
margin-top: 0;
}

View File

@ -58,7 +58,7 @@
#viewer-container {
margin: 0 0;
justify-content: center;
padding: 7px 15px;
padding: 10px 15px;
overflow-y: auto;
box-shadow: inset 0 2px 6px #eee;
}

View File

@ -1,4 +1,4 @@
function getShortcutsDefinition() {
function getShortcutsDefinition(controller, action) {
var shortcuts = [
{
"key": "l",
@ -13,11 +13,17 @@ function getShortcutsDefinition() {
"action": function () {
document.location.href = baseurl + '/events/add';
}
},
{
"key": "?",
"description": "Show this help",
"action": function () {
$('#triangle').click();
}
}
];
var $body = $(document.body);
if ($body.data('controller') === 'events' && $body.data('action') === 'view') {
if (controller === 'events' && action === 'view') {
shortcuts.push({
"key": "t",
"description": "Open the tag selection modal",
@ -46,9 +52,7 @@ function getShortcutsDefinition() {
$('#quickFilterField').focus();
}
});
}
if ($body.data('controller') === 'events' && $body.data('action') === 'index') {
} else if (controller === 'events' && action === 'index') {
shortcuts.push({
"key": "s",
"description": "Focus the filter events bar",

View File

@ -21,7 +21,8 @@ let keyboardShortcutsManager = {
init() {
/* Codacy comment to notify that baseurl is a read-only global variable. */
/* global baseurl */
this.mapKeyboardShortcuts(getShortcutsDefinition());
var $body = $(document.body);
this.mapKeyboardShortcuts(getShortcutsDefinition($body.data('controller'), $body.data('action')));
this.setKeyboardListener();
},
@ -43,7 +44,7 @@ let keyboardShortcutsManager = {
*/
addShortcutListToHTML() {
let html = "<ul>";
for(let shortcut of this.shortcutKeys.values()) {
for (let shortcut of this.shortcutKeys.values()) {
html += `<li><strong>${shortcut.key.toUpperCase()}</strong>: ${shortcut.description}</li>`
}
html += "</ul>"
@ -55,7 +56,7 @@ let keyboardShortcutsManager = {
* @param {} config The shortcut JSON list: [{key: string, description: string, action: string(eval-able JS code)}]
*/
mapKeyboardShortcuts(config) {
for(let shortcut of config) {
for (let shortcut of config) {
this.shortcutKeys.set(shortcut.key, shortcut);
}
this.addShortcutListToHTML();
@ -68,12 +69,12 @@ let keyboardShortcutsManager = {
*/
setKeyboardListener() {
window.onkeyup = (keyboardEvent) => {
if(this.shortcutKeys.has(keyboardEvent.key)) {
if (this.shortcutKeys.has(keyboardEvent.key)) {
let activeElement = document.activeElement.tagName;
if(!this.ESCAPED_TAG_NAMES.includes(activeElement)) {
if (!this.ESCAPED_TAG_NAMES.includes(activeElement)) {
this.shortcutKeys.get(keyboardEvent.key).action();
}
} else if(this.NAVIGATION_KEYS.includes(keyboardEvent.key)) {
} else if (this.NAVIGATION_KEYS.includes(keyboardEvent.key)) {
window.dispatchEvent(new CustomEvent(this.EVENTS[keyboardEvent.key], {detail: keyboardEvent}));
}
}
@ -81,8 +82,8 @@ let keyboardShortcutsManager = {
}
// Inits the keyboard shortcut manager's main routine and the click event on the keyboard shortcut triangle at the bottom of the screen.
$(document).ready(function(){
keyboardShortcutsManager.init();
$('#triangle').click(keyboardShortcutsManager.onTriangleClick);
$(function(){
keyboardShortcutsManager.init();
$('#triangle').click(keyboardShortcutsManager.onTriangleClick);
});

View File

@ -6,9 +6,12 @@ set -x
AUTH="$1"
HOST="$2"
curl -i -H "Accept: application/json" -H "content-type: application/json" -H "Authorization: $AUTH" --data "@event.json" -X POST http://${HOST}/events
curl -i -H "Accept: application/json" -H "content-type: application/json" -H "Authorization: $AUTH" --data "@event.json" -X POST http://${HOST}/events > /dev/null
curl -H "Authorization: $AUTH" -X GET http://${HOST}/events/csv/download/1/ignore:1 | sed -e 's/^M//g' | cut -d, -f2 --complement | sort > 1.csv
cat 1.csv
cut -d, -f2 --complement event.csv | sort > compare.csv
diff compare.csv 1.csv
# Test alert email generating
sudo -E su $USER -c '../app/Console/cake Event testEventNotificationEmail 1 1' > /dev/null
# Delete created event
curl -i -H "Accept: application/json" -H "content-type: application/json" -H "Authorization: $AUTH" -X POST http://${HOST}/events/delete/1