Merge branch 'feature/proposalFix' into 2.4

pull/854/head
Iglocska 2016-01-14 02:53:06 +01:00
commit 9ed006bb51
8 changed files with 255 additions and 163 deletions

View File

@ -2899,7 +2899,7 @@ class EventsController extends AppController {
} else {
$counter++;
}
//if (!$sa['deleted']) $this->Event->ShadowAttribute->__sendProposalAlertEmail($event['Event']['id']);
if (!$sa['deleted']) $this->Event->ShadowAttribute->__sendProposalAlertEmail($event['Event']['id']);
}
}
if ($success) {

View File

@ -246,7 +246,7 @@ class ShadowAttributesController extends AppController {
}
if ($this->ShadowAttribute->setDeleted($id)) {
if ($this->Auth->user('org_id') == $this->Event->data['Event']['orgc_id']) {
$this->_setProposalLock($eventId, false);
$this->ShadowAttribute->setProposalLock($eventId, false);
}
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
@ -389,7 +389,7 @@ class ShadowAttributesController extends AppController {
if ($successes) {
// list the ones that succeeded
$emailResult = "";
if (!$this->__sendProposalAlertEmail($eventId) == false) $emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
if (!$this->ShadowAttribute->sendProposalAlertEmail($eventId) == false) $emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
$this->Session->setFlash(__('The lines' . $successes . ' have been saved' . $emailResult, true));
}
}
@ -410,7 +410,7 @@ class ShadowAttributesController extends AppController {
if ($this->ShadowAttribute->save($this->request->data)) {
// list the ones that succeeded
$emailResult = "";
if (!$this->__sendProposalAlertEmail($this->request->data['ShadowAttribute']['event_id'])) {
if (!$this->ShadowAttribute->sendProposalAlertEmail($this->request->data['ShadowAttribute']['event_id'])) {
$emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
}
// inform the user and redirect
@ -586,7 +586,7 @@ class ShadowAttributesController extends AppController {
}
}
if (!$completeFail) {
if (!$this->__sendProposalAlertEmail($eventId)) $emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
if (!$this->ShadowAttribute->sendProposalAlertEmail($eventId)) $emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
if (empty($fails)) $this->Session->setFlash(__('The attachment has been uploaded'));
else $this->Session->setFlash(__('The attachment has been uploaded, but some of the proposals could not be created. The failed proposals are: ' . implode(', ', $fails)));
} else {
@ -691,7 +691,7 @@ class ShadowAttributesController extends AppController {
$this->request->data['ShadowAttribute']['email'] = $this->Auth->user('email');
if ($this->ShadowAttribute->save($this->request->data)) {
$emailResult = "";
if (!$this->__sendProposalAlertEmail($this->request->data['ShadowAttribute']['event_id'])) $emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
if (!$this->ShadowAttribute->sendProposalAlertEmail($this->request->data['ShadowAttribute']['event_id'])) $emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
if ($this->_isRest()) {
$sa = $this->ShadowAttribute->find(
'first',
@ -777,7 +777,7 @@ class ShadowAttributesController extends AppController {
);
if ($this->ShadowAttribute->save($sa)) {
$emailResult = "";
if (!$this->__sendProposalAlertEmail($existingAttribute['Event']['id'])) $emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
if (!$this->ShadowAttribute->sendProposalAlertEmail($existingAttribute['Event']['id'])) $emailResult = " but sending out the alert e-mails has failed for at least one recipient.";
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'The proposal to delete the attribute has been saved' . $emailResult)),'status'=>200));
} else {
return new CakeResponse(array('body'=> json_encode(array('false' => true, 'errors' => 'Could not create proposal.')),'status'=>200));
@ -825,131 +825,6 @@ class ShadowAttributesController extends AppController {
$this->set('_serialize', array('ShadowAttribute'));
}
private function _setProposalLock($id, $lock = true) {
$this->loadModel('Event');
$this->Event->recursive = -1;
$event = $this->Event->read(null, $id);
if ($lock) {
$event['Event']['proposal_email_lock'] = 1;
} else {
$event['Event']['proposal_email_lock'] = 0;
}
$fieldList = array('proposal_email_lock', 'id', 'info');
$this->Event->save($event, array('fieldList' => $fieldList));
}
private function __sendProposalAlertEmail($id) {
if (Configure::read('MISP.disable_emailing')) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => $this->Auth->user('id'),
'email' => $this->Auth->user('email'),
'action' => 'email',
'title' => 'The sending of new proposal alert e-mails for event ' . $id . ' failed. Reason: Emailing is currently disabled on this instance.',
'change' => null,
));
return true;
}
$this->loadModel('Event');
$this->Event->recursive = -1;
$event = $this->Event->read(null, $id);
// If the event has an e-mail lock, return
if ($event['Event']['proposal_email_lock'] == 1) {
return;
} else {
$this->_setProposalLock($id);
}
try {
$this->loadModel('User');
$this->User->recursive = -1;
$orgMembers = array();
$temp = $this->User->findAllByOrg($event['Event']['orgc_id'], array('email', 'gpgkey', 'contactalert', 'id'));
foreach ($temp as $tempElement) {
if ($tempElement['User']['contactalert'] || $tempElement['User']['id'] == $event['Event']['user_id']) {
array_push($orgMembers, $tempElement);
}
}
$body = "";
$body .= "Hello, \n";
$body .= "\n";
$body .= "A user of another organisation has proposed a change to an event created by you or your organisation. \n";
$body .= "\n";
$body .= "To view the event in question, follow this link:";
$body .= ' ' . Configure::read('MISP.baseurl') . '/events/view/' . $id . "\n";
$body .= "\n";
$body .= "You can reach the user at " . $this->Auth->user('email');
$body .= "\n";
// sign the body
require_once 'Crypt/GPG.php';
$gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'), 'binary' => (Configure::read('GnuPG.binary') ? Configure::read('GnuPG.binary') : '/usr/bin/gpg')));
$gpg->addSignKey(Configure::read('GnuPG.email'), Configure::read('GnuPG.password'));
$bodySigned = $gpg->sign($body, Crypt_GPG::SIGN_MODE_CLEAR);
// Add the GPG key of the user as attachment
// LATER sign the attached GPG key
if (null != (!$this->User->getPGP($this->Auth->user('id')))) {
// save the gpg key to a temporary file
$tmpfname = tempnam(TMP, "GPGkey");
$handle = fopen($tmpfname, "w");
fwrite($handle, $this->User->getPGP($this->Auth->user('id')));
fclose($handle);
// attach it
$this->Email->attachments = array(
'gpgkey.asc' => $tmpfname
);
}
foreach ($orgMembers as &$reporter) {
if (!empty($reporter['User']['gpgkey'])) {
// import the key of the user into the keyring
// this isn't really necessary, but it gives it the fingerprint necessary for the next step
$keyImportOutput = $gpg->importKey($reporter['User']['gpgkey']);
// say what key should be used to encrypt
try {
$gpg = new Crypt_GPG(array('homedir' => Configure::read('GnuPG.homedir'), 'binary' => (Configure::read('GnuPG.binary') ? Configure::read('GnuPG.binary') : '/usr/bin/gpg')));
$gpg->addEncryptKey($keyImportOutput['fingerprint']); // use the key that was given in the import
$bodyEncSig = $gpg->encrypt($bodySigned, true);
} catch (Exception $e){
// catch errors like expired PGP keys
$this->log($e->getMessage());
// no need to return here, as we want to send out mails to the other users if GPG encryption fails for a single user
}
} else {
$bodyEncSig = $bodySigned;
// FIXME should I allow sending unencrypted "contact" mails to people if they didn't import they GPG key?
}
// prepare the email
$this->Email->from = Configure::read('MISP.email');
$this->Email->to = $reporter['User']['email'];
$this->Email->subject = "[" . Configure::read('MISP.org') . " MISP] Proposal to event #" . $id;
$this->Email->template = 'body';
$this->Email->sendAs = 'text'; // both text or html
$this->set('body', $bodyEncSig);
// Add the GPG key of the user as attachment
// LATER sign the attached GPG key
if (null != ($this->User->getPGP($this->Auth->user('id')))) {
// attach the gpg key
$this->Email->attachments = array(
'gpgkey.asc' => $tmpfname
);
}
// send it
$result = $this->Email->send();
// If you wish to send multiple emails using a loop, you'll need
// to reset the email fields using the reset method of the Email component.
$this->Email->reset();
}
} catch (Exception $e) {
return false;
}
return true;
}
public function index($eventId = false) {
$conditions = array();
if (!$this->_isSiteAdmin()) {
@ -1006,14 +881,26 @@ class ShadowAttributesController extends AppController {
if (strlen($uuid) != 36) {
throw new NotFoundException(__('Invalid UUID'));
}
$this->ShadowAttribute->recursive = -1;
$temp = $this->ShadowAttribute->findAllByEventUuid($uuid);
$temp = $this->ShadowAttribute->find('all', array(
'conditions' => array('event_uuid' => $uuid),
'recursive' => -1,
'contain' => array(
'Org' => array('fields' => array('uuid', 'name')),
'EventOrg' => array('fields' => array('uuid', 'name')),
)
));
foreach ($temp as &$t) {
if ($this->ShadowAttribute->typeIsAttachment($t['ShadowAttribute']['type'])) {
$encodedFile = $this->ShadowAttribute->base64EncodeAttachment($t['ShadowAttribute']);
$t['ShadowAttribute']['data'] = $encodedFile;
}
}
if ($temp == null) {
$this->response->statusCode(404);
$this->set('name', 'Invalid Event.');
$this->set('message', 'Invalid Event');
$this->set('errors', 'Invalid Event');
$this->set('url', '/shadow_attributes/getProposalsByUuid/edit/' . $uuid);
$this->set('name', 'No proposals found.');
$this->set('message', 'No proposals found');
$this->set('errors', 'No proposals found');
$this->set('url', '/shadow_attributes/getProposalsByUuid/' . $uuid);
$this->set('_serialize', array('name', 'message', 'url', 'errors'));
$this->response->send();
return false;
@ -1023,6 +910,45 @@ class ShadowAttributesController extends AppController {
}
}
public function getProposalsByUuidList() {
if (!$this->_isRest() || !$this->userRole['perm_sync']) {
throw new MethodNotAllowedException(__('This feature is only available using the API to Sync users'));
}
if (!$this->request->is('Post')) throw new MethodNotAllowedException('This feature is only available using POST requests');
$result = array();
foreach ($this->request->data as $eventUuid) {
$temp = $this->ShadowAttribute->find('all', array(
'conditions' => array('event_uuid' => $eventUuid),
'recursive' => -1,
'contain' => array(
'Org' => array('fields' => array('uuid', 'name')),
'EventOrg' => array('fields' => array('uuid', 'name')),
),
));
if (empty($temp)) continue;
foreach ($temp as &$t) {
if ($this->ShadowAttribute->typeIsAttachment($t['ShadowAttribute']['type'])) {
$encodedFile = $this->ShadowAttribute->base64EncodeAttachment($t['ShadowAttribute']);
$t['ShadowAttribute']['data'] = $encodedFile;
}
}
$result = array_merge($result, $temp);
}
if (empty($result)) {
$this->response->statusCode(404);
$this->set('name', 'No proposals found.');
$this->set('message', 'No proposals found');
$this->set('errors', 'No proposals found');
$this->set('url', '/shadow_attributes/getProposalsByUuidList');
$this->set('_serialize', array('name', 'message', 'url', 'errors'));
$this->response->send();
return false;
} else {
$this->set('result', $result);
$this->render('get_proposals_by_uuid_list');
}
}
public function fetchEditForm($id, $field = null) {
$validFields = array('value', 'comment', 'type', 'category', 'to_ids');
if (!isset($field) || !in_array($field, $validFields)) throw new MethodNotAllowedException('Invalid field requested.');

View File

@ -965,8 +965,8 @@ class Event extends AppModel {
$request = array(
'header' => array(
'Authorization' => $authkey,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'Accept' => 'application/xml',
'Content-Type' => 'application/xml',
//'Connection' => 'keep-alive' // LATER followup cakephp ticket 2854 about this problem http://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/2854
)
);
@ -976,6 +976,34 @@ class Event extends AppModel {
$uri = $url . '/shadow_attributes/getProposalsByUuid/' . $eventId;
}
$response = $HttpSocket->get($uri, $data = '', $request);
if ($response->isOk()) {
$xmlArray = Xml::toArray(Xml::build($response->body));
$xmlArray = $this->updateXMLArray($xmlArray);
return $xmlArray['response'];
} else {
// TODO parse the XML response and keep the reason why it failed
return null;
}
}
public function downloadProposalsFromServer($uuidList, $server, $HttpSocket = false) {
$url = $server['Server']['url'];
$authkey = $server['Server']['authkey'];
if (null == $HttpSocket) {
App::uses('SyncTool', 'Tools');
$syncTool = new SyncTool();
$HttpSocket = $syncTool->setupHttpSocket($server);
}
$request = array(
'header' => array(
'Authorization' => $authkey,
'Accept' => 'application/json',
'Content-Type' => 'application/json',
//'Connection' => 'keep-alive' // LATER followup cakephp ticket 2854 about this problem http://cakephp.lighthouseapp.com/projects/42648-cakephp/tickets/2854
)
);
$uri = $url . '/shadow_attributes/getProposalsByUuidList';
$response = $HttpSocket->post($uri, json_encode($uuidList), $request);
if ($response->isOk()) {
return(json_decode($response->body, true));
} else {

View File

@ -964,51 +964,97 @@ class Server extends AppModel {
if ($jobId) {
$job->saveField('message', 'Pulling proposals.');
}
$events = $eventModel->find('all', array(
'fields' => array('id', 'uuid'),
$events = $eventModel->find('list', array(
'fields' => array('uuid'),
'recursive' => -1,
'conditions' => $conditions
));
$shadowAttribute = ClassRegistry::init('ShadowAttribute');
$shadowAttribute->recursive = -1;
foreach ($events as $k => &$event) {
$proposals = $eventModel->downloadEventFromServer($event['Event']['uuid'], $server, null, true);
if (null != $proposals) {
if (isset($proposals['ShadowAttribute']['id'])) {
$temp = $proposals['ShadowAttribute'];
$proposals['ShadowAttribute'] = array(0 => $temp);
}
foreach($proposals['ShadowAttribute'] as &$proposal) {
unset($proposal['id']);
if (!empty($events)) {
$proposals = $eventModel->downloadProposalsFromServer($events, $server, false);
if ($proposals !== null) {
$uuidEvents = array_flip($events);
foreach ($proposals as $k => &$proposal) {
$proposal = $proposal['ShadowAttribute'];
$oldsa = $shadowAttribute->findOldProposal($proposal);
$proposal['event_id'] = $event['Event']['id'];
$proposal['event_id'] = $uuidEvents[$proposal['event_uuid']];
if (!$oldsa || $oldsa['timestamp'] < $proposal['timestamp']) {
if ($oldsa) $shadowAttribute->delete($oldsa['id']);
if (!isset($pulledProposals[$event['Event']['id']])) $pulledProposals[$event['Event']['id']] = 0;
$pulledProposals[$event['Event']['id']]++;
if (!isset($pulledProposals[$proposal['event_id']])) $pulledProposals[$proposal['event_id']] = 0;
$pulledProposals[$proposal['event_id']]++;
if (isset($proposal['old_id'])) {
$oldAttribute = $eventModel->Attribute->find('first', array('recursive' => -1, 'conditions' => array('uuid' => $proposal['uuid'])));
if ($oldAttribute) $proposal['old_id'] = $oldAttribute['Attribute']['id'];
else $proposal['old_id'] = 0;
}
// check if this is a proposal from an old MISP instance
if (!isset($proposal['org_id']) && isset($proposal['org'])) {
if (!isset($proposal['Org']) && isset($proposal['org']) && !empty($proposal['org'])) {
$proposal['Org'] = $proposal['org'];
$proposal['EventOrg'] = $proposal['event_org'];
} else if (!isset($proposal['Org']) && !isset($proposal['EventOrg'])) {
continue;
}
$proposal['org_id'] = $this->Organisation->captureOrg($proposal['Org'], $user);
$proposal['event_org_id'] = $this->Organisation->captureOrg($proposal['EventOrg'], $user);
unset($proposal['Org']);
unset($proposal['EventOrg']);
$shadowAttribute->create();
$shadowAttribute->save($proposal);
if ($shadowAttribute->save($proposal)) $shadowAttribute->sendProposalAlertEmail($proposal['event_id']);
}
if ($jobId) {
if ($k % 50 == 0) {
$job->id = $jobId;
$job->saveField('progress', 50 * (($k + 1) / count($proposals)));
}
}
}
}
if ($jobId) {
if ($k % 10 == 0) {
$job->id = $jobId;
$job->saveField('progress', 50 * (($k + 1) / count($events)));
} else {
// Fallback for < 2.4.7 instances
$k = 0;
foreach ($events as $eid => &$event) {
$proposals = $eventModel->downloadEventFromServer($event, $server, null, true);
if (null != $proposals) {
if (isset($proposals['ShadowAttribute']['id'])) {
$temp = $proposals['ShadowAttribute'];
$proposals['ShadowAttribute'] = array(0 => $temp);
}
foreach($proposals['ShadowAttribute'] as &$proposal) {
$oldsa = $shadowAttribute->findOldProposal($proposal);
$proposal['event_id'] = $eid;
if (!$oldsa || $oldsa['timestamp'] < $proposal['timestamp']) {
if ($oldsa) $shadowAttribute->delete($oldsa['id']);
if (!isset($pulledProposals[$eid])) $pulledProposals[$eid] = 0;
$pulledProposals[$eid]++;
if (isset($proposal['old_id'])) {
$oldAttribute = $eventModel->Attribute->find('first', array('recursive' => -1, 'conditions' => array('uuid' => $proposal['uuid'])));
if ($oldAttribute) $proposal['old_id'] = $oldAttribute['Attribute']['id'];
else $proposal['old_id'] = 0;
}
// check if this is a proposal from an old MISP instance
if (!isset($proposal['Org']) && isset($proposal['org']) && !empty($proposal['org'])) {
$proposal['Org'] = $proposal['org'];
$proposal['EventOrg'] = $proposal['event_org'];
} else if (!isset($proposal['Org']) && !isset($proposal['EventOrg'])) {
continue;
}
$proposal['org_id'] = $this->Organisation->captureOrg($proposal['Org'], $user);
$proposal['event_org_id'] = $this->Organisation->captureOrg($proposal['EventOrg'], $user);
unset($proposal['Org']);
unset($proposal['EventOrg']);
$shadowAttribute->create();
if ($shadowAttribute->save($proposal)) $shadowAttribute->sendProposalAlertEmail($eid);
}
}
}
if ($jobId) {
if ($k % 10 == 0) {
$job->id = $jobId;
$job->saveField('progress', 50 * (($k + 1) / count($events)));
}
}
$k++;
}
}
}

View File

@ -432,6 +432,7 @@ class ShadowAttribute extends AppModel {
'type' => $sa['type'],
'category' => $sa['category'],
'to_ids' => $sa['to_ids'],
'comment' => $sa['comment']
),
));
if (empty($oldsa)) return false;
@ -446,5 +447,50 @@ class ShadowAttribute extends AppModel {
}
return $org_ids;
}
public function sendProposalAlertEmail($id) {
$this->Event->recursive = -1;
$event = $this->Event->read(null, $id);
// If the event has an e-mail lock, return
if ($event['Event']['proposal_email_lock'] == 1) {
return;
} else {
$this->setProposalLock($id);
}
$this->User = ClassRegistry::init('User');
$this->User->recursive = -1;
$orgMembers = $this->User->find('all',array(
'conditions' => array(
'org_id' => $event['Event']['orgc_id'],
'contactalert' => 1
),
'fields' => array('email', 'gpgkey', 'contactalert', 'id')
));
$body = "Hello, \n\n";
$body .= "A user of another organisation has proposed a change to an event created by you or your organisation. \n\n";
$body .= 'To view the event in question, follow this link: ' . Configure::read('MISP.baseurl') . '/events/view/' . $id . "\n";
$subject = "[" . Configure::read('MISP.org') . " MISP] Proposal to event #" . $id;
$result = true;
foreach ($orgMembers as &$user) {
$result = $result && $this->User->sendEmail($user, $body, $body, $subject);
}
return $result;
}
public function setProposalLock($id, $lock = true) {
$this->Event->recursive = -1;
$event = $this->Event->read(null, $id);
if ($lock) {
$event['Event']['proposal_email_lock'] = 1;
} else {
$event['Event']['proposal_email_lock'] = 0;
}
$fieldList = array('proposal_email_lock', 'id', 'info');
$this->Event->save($event, array('fieldList' => $fieldList));
}
}

View File

@ -0,0 +1,24 @@
<?php
$xmlArray = array();
//
// cleanup the array from things we do not want to expose
//
$jsonArray['ShadowAttribute'] = array();
foreach ($proposal as &$temp) {
unset($temp['ShadowAttribute']['email']);
unset($temp['ShadowAttribute']['value1']);
unset($temp['ShadowAttribute']['value2']);
$temp['ShadowAttribute']['Org'] = $temp['Org'];
$temp['ShadowAttribute']['EventOrg'] = $temp['EventOrg'];
// hide the org field is we are not in showorg mode
unset($temp['ShadowAttribute']['org_id']);
unset($temp['ShadowAttribute']['org']);
unset($temp['ShadowAttribute']['event_org_id']);
if (!Configure::read('MISP.showorg') && !$isAdmin) {
unset($temp['ShadowAttribute']['Org']);
unset($temp['ShadowAttribute']['EventOrg']);
}
$jsonArray['ShadowAttribute'][] = $temp['ShadowAttribute'];
}
echo json_encode($jsonArray);

View File

@ -0,0 +1,17 @@
<?php
foreach ($result as &$temp) {
unset($temp['ShadowAttribute']['id']);
unset($temp['ShadowAttribute']['email']);
unset($temp['ShadowAttribute']['value1']);
unset($temp['ShadowAttribute']['value2']);
$temp['ShadowAttribute']['Org'] = $temp['Org'];
$temp['ShadowAttribute']['EventOrg'] = $temp['EventOrg'];
// hide the org field is we are not in showorg mode
if (!Configure::read('MISP.showorg') && !$isAdmin) {
unset($temp['ShadowAttribute']['Org']);
unset($temp['ShadowAttribute']['EventOrg']);
}
$temp = array('ShadowAttribute' => $temp['ShadowAttribute']);
}
echo json_encode($result);

View File

@ -14,10 +14,15 @@ foreach ($proposal as &$temp) {
unset($temp['ShadowAttribute']['email']);
unset($temp['ShadowAttribute']['value1']);
unset($temp['ShadowAttribute']['value2']);
$temp['ShadowAttribute']['Org'] = $temp['Org'];
$temp['ShadowAttribute']['EventOrg'] = $temp['EventOrg'];
// hide the org field is we are not in showorg mode
unset($temp['ShadowAttribute']['org_id']);
unset($temp['ShadowAttribute']['org']);
unset($temp['ShadowAttribute']['event_org_id']);
if (!Configure::read('MISP.showorg') && !$isAdmin) {
unset($temp['ShadowAttribute']['org']);
unset($temp['ShadowAttribute']['event_org_id']);
unset($temp['ShadowAttribute']['Org']);
unset($temp['ShadowAttribute']['EventOrg']);
}
$xmlArray['response']['ShadowAttribute'][] = $temp['ShadowAttribute'];
}