mirror of https://github.com/MISP/MISP
new: [UI] Event locks for background jobs and automatic tools
parent
2faaa30578
commit
c1399b36f9
|
@ -262,7 +262,9 @@ class ACLComponent extends Component
|
|||
'viewEventAttributes' => array('*'),
|
||||
'viewGraph' => array('*'),
|
||||
'viewGalaxyMatrix' => array('*'),
|
||||
'xml' => array('*')
|
||||
'xml' => array('*'),
|
||||
'addEventLock' => ['perm_auth'],
|
||||
'removeEventLock' => ['perm_auth'],
|
||||
),
|
||||
'favouriteTags' => array(
|
||||
'toggle' => array('*'),
|
||||
|
|
|
@ -5280,9 +5280,43 @@ class EventsController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
public function addEventLock($id)
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException('This endpoint requires a POST request.');
|
||||
}
|
||||
|
||||
$event = $this->Event->fetchSimpleEvent($this->Auth->User(), $id);
|
||||
if (empty($event)) {
|
||||
throw new MethodNotAllowedException(__('Invalid Event'));
|
||||
}
|
||||
if (!$this->__canModifyEvent($event)) {
|
||||
throw new UnauthorizedException(__('You do not have permission to do that.'));
|
||||
}
|
||||
|
||||
$this->loadModel('EventLock');
|
||||
$lockId = $this->EventLock->insertLockApi($event['Event']['id'], $this->Auth->user());
|
||||
return $this->RestResponse->viewData(['lock_id' => $lockId], $this->response->type());
|
||||
}
|
||||
|
||||
public function removeEventLock($id, $lockId)
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException('This endpoint requires a POST request.');
|
||||
}
|
||||
|
||||
$event = $this->Event->fetchSimpleEvent($this->Auth->User(), $id);
|
||||
if (empty($event)) {
|
||||
throw new MethodNotAllowedException(__('Invalid Event'));
|
||||
}
|
||||
|
||||
$this->loadModel('EventLock');
|
||||
$deleted = $this->EventLock->deleteApiLock($event['Event']['id'], $lockId, $this->Auth->user());
|
||||
return $this->RestResponse->viewData(['deleted' => $deleted], $this->response->type());
|
||||
}
|
||||
|
||||
public function checkLocks($id)
|
||||
{
|
||||
$this->loadModel('EventLock');
|
||||
$event = $this->Event->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('Event.id' => $id),
|
||||
|
@ -5290,29 +5324,34 @@ class EventsController extends AppController
|
|||
));
|
||||
$locks = array();
|
||||
if (!empty($event) && ($event['Event']['orgc_id'] == $this->Auth->user('org_id') || $this->_isSiteAdmin())) {
|
||||
$this->loadModel('EventLock');
|
||||
$locks = $this->EventLock->checkLock($this->Auth->user(), $id);
|
||||
}
|
||||
if (!empty($locks)) {
|
||||
$temp = $locks;
|
||||
$locks = array();
|
||||
foreach ($temp as $t) {
|
||||
if ($t['User']['id'] !== $this->Auth->user('id')) {
|
||||
if ($t['type'] === 'user' && $t['User']['id'] !== $this->Auth->user('id')) {
|
||||
if (!$this->_isSiteAdmin() && $t['User']['org_id'] != $this->Auth->user('org_id')) {
|
||||
continue;
|
||||
$locks[] = __('another user');
|
||||
} else {
|
||||
$locks[] = $t['User']['email'];
|
||||
}
|
||||
$locks[] = $t['User']['email'];
|
||||
} else if ($t['type'] === 'job') {
|
||||
$locks[] = __('background job');
|
||||
} else if ($t['type'] === 'api') {
|
||||
$locks[] = __('external tool');
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: i18n
|
||||
if (!empty($locks)) {
|
||||
$message = sprintf('Warning: Your view on this event might not be up to date as it is currently being edited by: %s', implode(', ', $locks));
|
||||
$this->set('message', $message);
|
||||
$this->layout = false;
|
||||
$this->render('/Events/ajax/event_lock');
|
||||
} else {
|
||||
if (empty($locks)) {
|
||||
return $this->RestResponse->viewData('', $this->response->type(), false, true);
|
||||
}
|
||||
|
||||
$message = __('Warning: Your view on this event might not be up to date as it is currently being edited by: %s', implode(', ', $locks));
|
||||
$this->set('message', $message);
|
||||
$this->layout = false;
|
||||
$this->render('/Events/ajax/event_lock');
|
||||
}
|
||||
|
||||
public function getEditStrategy($id)
|
||||
|
|
|
@ -3619,9 +3619,6 @@ class Event extends AppModel
|
|||
// Low level function to add an Event based on an Event $data array
|
||||
public function _add(array &$data, $fromXml, array $user, $org_id = 0, $passAlong = null, $fromPull = false, $jobId = null, &$created_id = 0, &$validationErrors = array())
|
||||
{
|
||||
if ($jobId) {
|
||||
App::uses('AuthComponent', 'Controller/Component');
|
||||
}
|
||||
if (Configure::read('MISP.enableEventBlocklisting') !== false && isset($data['Event']['uuid'])) {
|
||||
$this->EventBlocklist = ClassRegistry::init('EventBlocklist');
|
||||
if ($this->EventBlocklist->isBlocked($data['Event']['uuid'])) {
|
||||
|
@ -3799,6 +3796,12 @@ class Event extends AppModel
|
|||
);
|
||||
$saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList['Event']));
|
||||
if ($saveResult) {
|
||||
if ($jobId) {
|
||||
/** @var EventLock $eventLock */
|
||||
$eventLock = ClassRegistry::init('EventLock');
|
||||
$eventLock->insertLockBackgroundJob($this->id, $jobId);
|
||||
}
|
||||
|
||||
if ($passAlong) {
|
||||
if ($server['Server']['publish_without_email'] == 0) {
|
||||
$st = "enabled";
|
||||
|
@ -3927,6 +3930,10 @@ class Event extends AppModel
|
|||
}
|
||||
}
|
||||
}
|
||||
if ($jobId) {
|
||||
$eventLock->deleteBackgroundJobLock($this->id, $jobId);
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
$validationErrors['Event'] = $this->validationErrors;
|
||||
|
@ -4051,6 +4058,11 @@ class Event extends AppModel
|
|||
$saveResult = $this->save(array('Event' => $data['Event']), array('fieldList' => $fieldList));
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
if ($saveResult) {
|
||||
if ($jobId) {
|
||||
/** @var EventLock $eventLock */
|
||||
$eventLock = ClassRegistry::init('EventLock');
|
||||
$eventLock->insertLockBackgroundJob($data['Event']['id'], $jobId);
|
||||
}
|
||||
$validationErrors = array();
|
||||
if (isset($data['Event']['Attribute'])) {
|
||||
$data['Event']['Attribute'] = array_values($data['Event']['Attribute']);
|
||||
|
@ -4177,6 +4189,9 @@ class Event extends AppModel
|
|||
}
|
||||
$this->publish($existingEvent['Event']['id']);
|
||||
}
|
||||
if ($jobId) {
|
||||
$eventLock->deleteBackgroundJobLock($data['Event']['id'], $jobId);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return $this->validationErrors;
|
||||
|
@ -6087,6 +6102,10 @@ class Event extends AppModel
|
|||
$ontheflyattributes = array();
|
||||
$i = 0;
|
||||
if ($jobId) {
|
||||
/** @var EventLock $eventLock */
|
||||
$eventLock = ClassRegistry::init('EventLock');
|
||||
$eventLock->insertLockBackgroundJob($event['Event']['id'], $jobId);
|
||||
|
||||
$this->Job = ClassRegistry::init('Job');
|
||||
$total = count($attributeSources);
|
||||
}
|
||||
|
@ -6194,6 +6213,7 @@ class Event extends AppModel
|
|||
$message = $saved . ' ' . $messageScopeSaved . ' created' . $emailResult . '.';
|
||||
}
|
||||
if ($jobId) {
|
||||
$eventLock->deleteBackgroundJobLock($event['Event']['id'], $jobId);
|
||||
$this->Job->saveStatus($jobId, true, __('Processing complete. %s', $message));
|
||||
}
|
||||
if (!empty($returnRawResults)) {
|
||||
|
|
|
@ -1,65 +1,132 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
|
||||
// Table `event_locks` is not used anymore
|
||||
class EventLock extends AppModel
|
||||
{
|
||||
public $useTable = 'event_locks';
|
||||
// In seconds
|
||||
const DEFAULT_TTL = 900;
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = array(
|
||||
'Containable',
|
||||
);
|
||||
|
||||
public $belongsTo = array(
|
||||
'User' => array(
|
||||
'className' => 'User',
|
||||
'foreignKey' => 'user_id',
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
public $validate = array(
|
||||
);
|
||||
|
||||
public function beforeValidate($options = array())
|
||||
/**
|
||||
* @param array $user
|
||||
* @param int $eventId
|
||||
* @return bool True if insert was successful.
|
||||
*/
|
||||
public function insertLock(array $user, $eventId)
|
||||
{
|
||||
parent::beforeValidate();
|
||||
return true;
|
||||
return $this->insertLockToRedis("misp:event_lock:$eventId:user:{$user['id']}", [
|
||||
'type' => 'user',
|
||||
'timestamp' => time(),
|
||||
'User' => [
|
||||
'id' => $user['id'],
|
||||
'org_id' => $user['org_id'],
|
||||
'email' => $user['email'],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function insertLock($user, $eventId)
|
||||
/**
|
||||
* @param int $eventId
|
||||
* @param int $jobId
|
||||
* @return bool True if insert was successful.
|
||||
*/
|
||||
public function insertLockBackgroundJob($eventId, $jobId)
|
||||
{
|
||||
$date = new DateTime();
|
||||
$lock = array(
|
||||
'timestamp' => $date->getTimestamp(),
|
||||
return $this->insertLockToRedis("misp:event_lock:$eventId:job:$jobId", [
|
||||
'type' => 'job',
|
||||
'timestamp' => time(),
|
||||
'job_id' => $jobId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $eventId
|
||||
* @return int|null Lock ID
|
||||
*/
|
||||
public function insertLockApi($eventId, array $user)
|
||||
{
|
||||
$rand = mt_rand();
|
||||
if ($this->insertLockToRedis("misp:event_lock:$eventId:api:{$user['id']}:$rand", [
|
||||
'type' => 'api',
|
||||
'user_id' => $user['id'],
|
||||
'event_id' => $eventId
|
||||
);
|
||||
$this->deleteAll(array('user_id' => $user['id']));
|
||||
$this->create();
|
||||
return $this->save($lock);
|
||||
'timestamp' => time(),
|
||||
])) {
|
||||
return $rand;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function checkLock($user, $eventId)
|
||||
/**
|
||||
* @param int $eventId
|
||||
* @param int $jobId
|
||||
* @return null
|
||||
*/
|
||||
public function deleteBackgroundJobLock($eventId, $jobId)
|
||||
{
|
||||
$this->cleanupLock($user, $eventId);
|
||||
$locks = $this->find('all', array(
|
||||
'recursive' => -1,
|
||||
'contain' => array('User.email', 'User.org_id', 'User.id'),
|
||||
'conditions' => array(
|
||||
'event_id' => $eventId
|
||||
)
|
||||
));
|
||||
return $locks;
|
||||
try {
|
||||
$redis = $this->setupRedisWithException();
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$deleted = $redis->del("misp:event_lock:$eventId:job:$jobId");
|
||||
return $deleted > 0;
|
||||
}
|
||||
|
||||
// If a lock has been active for 15 minutes, delete it
|
||||
public function cleanupLock()
|
||||
/**
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
private function insertLockToRedis($key, array $data)
|
||||
{
|
||||
$date = new DateTime();
|
||||
$timestamp = $date->getTimestamp();
|
||||
$timestamp -= 900;
|
||||
$this->deleteAll(array('timestamp <' => $timestamp));
|
||||
return true;
|
||||
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");
|
||||
return $deleted > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @param int $eventId
|
||||
* @return array[]
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function checkLock(array $user, $eventId)
|
||||
{
|
||||
try {
|
||||
$redis = $this->setupRedisWithException();
|
||||
} catch (Exception $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keys = $redis->keys("misp:event_lock:$eventId:*");
|
||||
if (empty($keys)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(function ($value) {
|
||||
return $this->jsonDecode($value);
|
||||
}, $redis->mget($keys));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -540,7 +540,7 @@
|
|||
<script type="text/javascript">
|
||||
var showContext = false;
|
||||
$(function () {
|
||||
queryEventLock('<?php echo h($event['Event']['id']); ?>', '<?php echo h($me['org_id']); ?>');
|
||||
queryEventLock('<?php echo h($event['Event']['id']); ?>');
|
||||
popoverStartup();
|
||||
|
||||
$("th, td, dt, div, span, li").tooltip({
|
||||
|
|
|
@ -4998,16 +4998,14 @@ $(document.body).on('click', 'a[data-paginator]', function (e) {
|
|||
});
|
||||
});
|
||||
|
||||
function queryEventLock(event_id, user_org_id) {
|
||||
function queryEventLock(event_id) {
|
||||
if (tabIsActive) {
|
||||
$.ajax({
|
||||
url: baseurl + "/events/checkLocks/" + event_id,
|
||||
type: "get",
|
||||
success: function(data, statusText, xhr) {
|
||||
if (xhr.status == 200) {
|
||||
if ($('#event_lock_warning').length != 0) {
|
||||
$('#event_lock_warning').remove();
|
||||
}
|
||||
$('#event_lock_warning').remove();
|
||||
if (data != '') {
|
||||
$('#main-view-container').append(data);
|
||||
}
|
||||
|
@ -5015,7 +5013,7 @@ function queryEventLock(event_id, user_org_id) {
|
|||
}
|
||||
});
|
||||
}
|
||||
setTimeout(function() { queryEventLock(event_id, user_org_id); }, 5000);
|
||||
setTimeout(function() { queryEventLock(event_id); }, 5000);
|
||||
}
|
||||
|
||||
function checkIfLoggedIn() {
|
||||
|
|
Loading…
Reference in New Issue