Merge branch '2.4' into tools

pull/4558/head
Steve Clement 2019-05-01 22:59:30 +09:00
commit c4d3074c2c
19 changed files with 1035 additions and 53 deletions

View File

@ -308,10 +308,47 @@ class AdminShell extends AppShell
}
public function updateDatabase() {
$whoami = exec('whoami');
if ($whoami === 'httpd' || $whoami === 'www-data') {
echo 'Executing all updates to bring the database up to date with the current version.' . PHP_EOL;
$this->Server->runUpdates(true);
echo 'All updates completed.' . PHP_EOL;
} else {
die('This OS user is not allowed to run this command.'. PHP_EOL. 'Run it under `www-data` or `httpd`.' . PHP_EOL);
}
}
public function updateApp() {
$whoami = exec('whoami');
if ($whoami === 'httpd' || $whoami === 'www-data') {
$command = $this->args[0];
if (!empty($this->args[1])) {
$processId = $this->args[1];
$job = $this->Job->read(null, $processId);
} else { // create worker
$this->Job->create();
$job_data = array(
'worker' => 'prio',
'job_type' => 'update_app',
'job_input' => 'command: ' . $command,
'status' => 0,
'retries' => 0,
'org_id' => '',
'org' => '',
'message' => 'Updating.',
);
$this->Job->save($job_data);
$job = $this->Job->read(null, $this->Job->id);
}
$result = $this->Server->updateDatabase($command, false);
$job['Job']['progress'] = 100;
$job['Job']['message'] = 'Update done';
$this->Job->save($job);
} else {
die('This OS user is not allowed to run this command.'. PHP_EOL. 'Run it under `www-data` or `httpd`.' . PHP_EOL);
}
}
public function getAuthkey() {
if (empty($this->args[0])) {

View File

@ -446,4 +446,5 @@ class ServerShell extends AppShell
$this->Task->id = $task['Task']['id'];
$this->Task->saveField('message', count($servers) . ' job(s) completed at ' . date('d/m/Y - H:i:s') . '.');
}
}

View File

@ -372,7 +372,7 @@ class AppController extends Controller
$this->Auth->logout();
throw new MethodNotAllowedException($message);//todo this should pb be removed?
} else {
$this->Flash->error('Warning: MISP is currently disabled for all users. Enable it in Server Settings (Administration -> Server Settings -> MISP tab -> live)', array('clear' => 1));
$this->Flash->error('Warning: MISP is currently disabled for all users. Enable it in Server Settings (Administration -> Server Settings -> MISP tab -> live). An update might also be in progress, you can see the progress in ' , array('params' => array('url' => $baseurl . '/servers/advancedUpdate/', 'urlName' => 'Advanced Update'), 'clear' => 1));
}
}
@ -835,8 +835,12 @@ class AppController extends Controller
}
$this->Server->updateDatabase($command);
$this->Flash->success('Done.');
if ($liveOff) {
$this->redirect(array('controller' => 'servers', 'action' => 'updateProgress'));
} else {
$this->redirect(array('controller' => 'pages', 'action' => 'display', 'administration'));
}
}
public function upgrade2324()
{

View File

@ -2246,7 +2246,7 @@ class AttributesController extends AppController
$data = $this->request->data;
}
if (empty($data)) {
throw new BadRequestException(__('Either specify the search terms in the url, or POST a json array / xml (with the root element being "request" and specify the correct headers based on content type.'));
throw new BadRequestException(__('Either specify the search terms in the url, or POST a json array / xml (with the root element being "request" and specify the correct headers based on content type).'));
}
$paramArray = array('eventId', 'tags', 'from', 'to', 'policy', 'walled_garden', 'ns', 'email', 'serial', 'refresh', 'retry', 'expiry', 'minimum_ttl', 'ttl', 'enforceWarninglist', 'ns_alt');
foreach ($paramArray as $p) {

View File

@ -340,6 +340,7 @@ class ACLComponent extends Component
'getSubmoduleQuickUpdateForm' => array('perm_site_admin'),
'getVersion' => array('*'),
'index' => array('OR' => array('perm_sync', 'perm_admin')),
'ondemandAction' => array(),
'postTest' => array('perm_sync'),
'previewEvent' => array(),
'previewIndex' => array(),
@ -359,6 +360,7 @@ class ACLComponent extends Component
'testConnection' => array('perm_sync'),
'update' => array(),
'updateJSON' => array(),
'updateProgress' => array(),
'updateSubmodule' => array(),
'uploadFile' => array(),
'clearWorkerQueue' => array()

View File

@ -2621,7 +2621,7 @@ class EventsController extends AppController
{
$filesize_units = array('B', 'KB', 'MB', 'GB', 'TB');
if ($this->_isSiteAdmin()) {
$this->Flash->info('Warning, you are logged in as a site admin, any export that you generate will contain the FULL UNRESTRICTED data-set. If you would like to generate an export for your own organisation, please log in with a different user.');
$this->Flash->info(__('Warning, you are logged in as a site admin, any export that you generate will contain the FULL UNRESTRICTED data-set. If you would like to generate an export for your own organisation, please log in with a different user.'));
}
// Check if the background jobs are enabled - if not, fall back to old export page.
if (Configure::read('MISP.background_jobs') && !Configure::read('MISP.disable_cached_exports')) {
@ -3954,7 +3954,7 @@ class EventsController extends AppController
}
if ($this->request->is('post')) {
if (empty($this->request->data)) {
throw new BadRequestException(__('Either specify the search terms in the url, or POST an xml (with the root element being "request".'));
throw new BadRequestException(__('Either specify the search terms in the url, or POST an xml (with the root element being "request").'));
} else {
$data = $this->request->data;
}

View File

@ -433,7 +433,7 @@ class FeedsController extends AppController
$this->redirect(array('action' => 'index'));
}
}
$message = __('Fetching the feed has successfuly completed.');
$message = __('Fetching the feed has successfully completed.');
if ($this->Feed->data['Feed']['source_format'] == 'misp') {
if (isset($result['add'])) {
$message .= ' Downloaded ' . count($result['add']) . ' new event(s).';

View File

@ -1545,6 +1545,69 @@ class ServersController extends AppController
}
}
public function ondemandAction()
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException('You are not authorised to do that.');
}
$this->AdminSetting = ClassRegistry::init('AdminSetting');
$actions = $this->Server->actions_description;
$default_fields = array(
'title' => '',
'description' => '',
'liveOff' => false,
'recommendBackup' => false,
'exitOnError' => false,
'requirements' => '',
'url' => '/'
);
foreach($actions as $id => $action) {
foreach($default_fields as $field => $value) {
if (!isset($action[$field])) {
$actions[$id][$field] = $value;
}
}
$done = $this->AdminSetting->getSetting($id);
$actions[$id]['done'] = ($done == '1');
}
$this->set('actions', $actions);
$this->set('updateLocked', $this->Server->isUpdateLocked());
}
public function updateProgress()
{
if (!$this->_isSiteAdmin()) {
throw new MethodNotAllowedException('You are not authorised to do that.');
}
$update_progress = $this->Server->getUpdateProgress();
$current_index = $update_progress['current'];
$current_command = !isset($update_progress['commands'][$current_index]) ? '' : $update_progress['commands'][$current_index];
$lookup_string = preg_replace('/\s{2,}/', '', substr($current_command, 0, -1));
$sql_info = $this->Server->query("SELECT * FROM INFORMATION_SCHEMA.PROCESSLIST;");
if (empty($sql_info)) {
$update_progress['process_list'] = array();
} else {
// retreive current update process
foreach($sql_info as $row) {
if (preg_replace('/\s{2,}/', '', $row['PROCESSLIST']['INFO']) == $lookup_string) {
$sql_info = $row['PROCESSLIST'];
break;
}
}
$update_progress['process_list'] = array();
$update_progress['process_list']['STATE'] = isset($sql_info['STATE']) ? $sql_info['STATE'] : '';
$update_progress['process_list']['PROGRESS'] = isset($sql_info['PROGRESS']) ? $sql_info['PROGRESS'] : 0;
$update_progress['process_list']['STAGE'] = isset($sql_info['STAGE']) ? $sql_info['STAGE'] : 0;
$update_progress['process_list']['MAX_STAGE'] = isset($sql_info['MAX_STAGE']) ? $sql_info['MAX_STAGE'] : 0;
}
if ($this->request->is('ajax')) {
return $this->RestResponse->viewData(h($update_progress), $this->response->type());
} else {
$this->set('updateProgress', $update_progress);
}
}
public function getSubmoduleQuickUpdateForm($submodule_path=false) {
$this->set('submodule', base64_decode($submodule_path));
$this->render('ajax/submodule_quick_update_form');

View File

@ -78,6 +78,21 @@ class AppModel extends Model
33 => false, 34 => false
);
public $advanced_updates_description = array(
);
public $actions_description = array(
'verifyGnuPGkeys' => array(
'title' => 'Verify GnuPG keys',
'description' => "Run a full validation of all GnuPG keys within this instance's userbase. The script will try to identify possible issues with each key and report back on the results.",
'url' => '/users/verifyGPG/'
),
'databaseCleanupScripts' => array(
'title' => 'Database Cleanup Scripts',
'description' => 'If you run into an issue with an infinite upgrade loop (when upgrading from version ~2.4.50) that ends up filling your database with upgrade script log messages, run the following script.',
'url' => '/logs/pruneUpdateLogs/'
)
);
public function afterSave($created, $options = array())
{
if ($created) {
@ -209,8 +224,46 @@ class AppModel extends Model
}
// SQL scripts for updates
public function updateDatabase($command)
public function updateDatabase($command, $useWorker=true)
{
// Exit if updates are locked
if ($this->isUpdateLocked()) {
return false;
}
$this->__resetUpdateProgress();
// restart this function by a worker
if ($useWorker && Configure::read('MISP.background_jobs')) {
$job = ClassRegistry::init('Job');
$job->create();
$data = array(
'worker' => 'prio',
'job_type' => 'update_app',
'job_input' => 'command: ' . $command,
'status' => 0,
'retries' => 0,
'org_id' => '',
'org' => '',
'message' => 'Updating.',
);
$job->save($data);
$jobId = $job->id;
$process_id = CakeResque::enqueue(
'prio',
'AdminShell',
array('updateApp', $command, $jobId),
true
);
$job->saveField('process_id', $process_id);
return true;
}
$liveOff = false;
$exitOnError = false;
if (isset($advanced_updates_description[$command])) {
$liveOff = isset($advanced_updates_description[$command]['liveOff']) ? $advanced_updates_description[$command]['liveOff'] : $liveOff;
$exitOnError = isset($advanced_updates_description[$command]['exitOnError']) ? $advanced_updates_description[$command]['exitOnError'] : $exitOnError;
}
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
$dataSource = $dataSourceConfig['datasource'];
$sqlArray = array();
@ -1150,8 +1203,48 @@ class AppModel extends Model
return false;
break;
}
foreach ($sqlArray as $sql) {
$now = new DateTime();
$this->__changeLockState(time());
// switch MISP instance live to false
if ($liveOff) {
$this->Server = Classregistry::init('Server');
$liveSetting = 'MISP.live';
$this->Server->serverSettingsSaveValue($liveSetting, false);
}
$sql_update_count = count($sqlArray);
$index_update_count = count($indexArray);
$total_update_count = $sql_update_count + $index_update_count;
$this->__setUpdateProgress(0, $total_update_count);
$str_index_array = array();
foreach($indexArray as $toIndex) {
$str_index_array[] = __('Indexing ') . implode($toIndex, '->');
}
$this->__setUpdateCmdMessages(array_merge($sqlArray, $str_index_array));
$flag_stop = false;
$error_count = 0;
// execute test before update. Exit if it fails
if (isset($this->advanced_updates_description[$command]['preUpdate'])) {
$function_name = $this->advanced_updates_description[$command]['preUpdate'];
try {
$this->{$function_name}();
} catch (Exception $e) {
$this->__setPreUpdateTestState(false);
$this->__setUpdateProgress(0, false);
$this->__setUpdateResMessages(0, __('Issues executing the pre-update test `') . $function_name . __('`. The returned error is: ') . PHP_EOL . $e->getMessage());
$this->__setUpdateError(0);
$error_count++;
$exitOnError = true;
$flag_stop = true;
}
}
if (!$flag_stop) {
$this->__setPreUpdateTestState(true);
foreach ($sqlArray as $i => $sql) {
try {
$this->__setUpdateProgress($i, false);
$this->query($sql);
$this->Log->create();
$this->Log->save(array(
@ -1161,9 +1254,10 @@ class AppModel extends Model
'email' => 'SYSTEM',
'action' => 'update_database',
'user_id' => 0,
'title' => 'Successfuly executed the SQL query for ' . $command,
'change' => 'The executed SQL query was: ' . $sql
'title' => __('Successfuly executed the SQL query for ') . $command,
'change' => __('The executed SQL query was: ') . $sql
));
$this->__setUpdateResMessages($i, __('Successfuly executed the SQL query for ') . $command);
} catch (Exception $e) {
$this->Log->create();
$this->Log->save(array(
@ -1173,29 +1267,59 @@ class AppModel extends Model
'email' => 'SYSTEM',
'action' => 'update_database',
'user_id' => 0,
'title' => 'Issues executing the SQL query for ' . $command,
'change' => 'The executed SQL query was: ' . $sql . PHP_EOL . ' The returned error is: ' . $e->getMessage()
'title' => __('Issues executing the SQL query for ') . $command,
'change' => __('The executed SQL query was: ') . $sql . PHP_EOL . __(' The returned error is: ') . $e->getMessage()
));
$this->__setUpdateResMessages($i, __('Issues executing the SQL query for ') . $command . __('. The returned error is: ') . PHP_EOL . $e->getMessage());
$this->__setUpdateError($i);
$error_count++;
if ($exitOnError) {
$flag_stop = true;
break;
}
}
}
}
if (!$flag_stop) {
if (!empty($indexArray)) {
if ($clean) {
$this->cleanCacheFiles();
}
foreach ($indexArray as $iA) {
foreach ($indexArray as $i => $iA) {
$this->__setUpdateProgress(count($sqlArray)+$i, false);
if (isset($iA[2])) {
$this->__addIndex($iA[0], $iA[1], $iA[2]);
} else {
$this->__addIndex($iA[0], $iA[1]);
}
$this->__setUpdateResMessages(count($sqlArray)+$i, __('Successfuly indexed ') . implode($iA, '->'));
}
}
$this->__setUpdateProgress(count($sqlArray)+count($indexArray), false);
}
if ($clean) {
$this->cleanCacheFiles();
}
if ($liveOff) {
$liveSetting = 'MISP.live';
$this->Server->serverSettingsSaveValue($liveSetting, true);
}
if (!$flag_stop && $error_count == 0) {
$this->__postUpdate($command);
}
$this->__changeLockState(false);
return true;
}
// check whether the adminSetting should be updated after the update
private function __postUpdate($command) {
if (isset($this->advanced_updates_description[$command]['record'])) {
if($this->advanced_updates_description[$command]['record']) {
$this->AdminSetting->changeSetting($command, 1);
}
}
}
private function __dropIndex($table, $field)
{
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
@ -1390,6 +1514,126 @@ class AppModel extends Model
if ($requiresLogout) {
$this->updateDatabase('destroyAllSessions');
}
return true;
}
private function __setUpdateProgress($current, $total=false)
{
$updateProgress = $this->getUpdateProgress();
$updateProgress['current'] = $current;
if ($total !== false) {
$updateProgress['total'] = $total;
} else {
$now = new DateTime();
$updateProgress['time']['started'][$current] = $now->format('Y-m-d H:i:s');
}
$this->__saveUpdateProgress($updateProgress);
}
private function __setPreUpdateTestState($state)
{
$updateProgress = $this->getUpdateProgress();
$updateProgress['preTestSuccess'] = $state;
$this->__saveUpdateProgress($updateProgress);
}
private function __setUpdateError($index)
{
$updateProgress = $this->getUpdateProgress();
$updateProgress['failed_num'][] = $index;
$this->__saveUpdateProgress($updateProgress);
}
private function __resetUpdateProgress()
{
$updateProgress = array(
'commands' => array(),
'results' => array(),
'time' => array('started' => array(), 'elapsed' => array()),
'current' => '',
'total' => '',
'failed_num' => array()
);
$this->__saveUpdateProgress($updateProgress);
}
private function __setUpdateCmdMessages($messages)
{
$updateProgress = $this->getUpdateProgress();
$updateProgress['commands'] = $messages;
$this->__saveUpdateProgress($updateProgress);
}
private function __setUpdateResMessages($index, $message)
{
$updateProgress = $this->getUpdateProgress();
$updateProgress['results'][$index] = $message;
$temp = new DateTime();
$diff = $temp->diff(new DateTime($updateProgress['time']['started'][$index]));
$updateProgress['time']['elapsed'][$index] = $diff->format('%H:%I:%S');
$this->__saveUpdateProgress($updateProgress);
}
public function getUpdateProgress()
{
if (!isset($this->AdminSetting)) {
$this->AdminSetting = ClassRegistry::init('AdminSetting');
}
$updateProgress = $this->AdminSetting->getSetting('update_progress');
if ($updateProgress !== false) {
$updateProgress = json_decode($updateProgress, true);
} else {
$this->__resetUpdateProgress();
$updateProgress = $this->AdminSetting->getSetting('update_progress');
$updateProgress = json_decode($updateProgress, true);
}
foreach($updateProgress as $setting => $value) {
if (!is_array($value)) {
$value = $value !== false && $value !== '' ? intval($value) : 0;
}
$updateProgress[$setting] = $value;
}
return $updateProgress;
}
private function __saveUpdateProgress($updateProgress)
{
$data = json_encode($updateProgress);
$this->AdminSetting->changeSetting('update_progress', $data);
}
private function __changeLockState($locked)
{
$this->AdminSetting->changeSetting('update_locked', $locked);
}
private function getUpdateLockState()
{
if (!isset($this->AdminSetting)) {
$this->AdminSetting = ClassRegistry::init('AdminSetting');
}
$locked = $this->AdminSetting->getSetting('update_locked');
return is_null($locked) ? false : $locked;
}
public function isUpdateLocked()
{
$lockState = $this->getUpdateLockState();
if ($lockState !== false && $lockState !== '') {
// if lock is old, still allows the update
// This can be useful if the update process crashes
$diffSec = time() - intval($lockState);
if (Configure::read('MISP.updateTimeThreshold')) {
$updateWaitThreshold = intval(Configure::read('MISP.updateTimeThreshold'));
} else {
$this->Server = ClassRegistry::init('Server');
$updateWaitThreshold = intval($this->Server->serverSettings['MISP']['updateTimeThreshold']['value']);
}
if ($diffSec < $updateWaitThreshold) {
return true;
}
}
return false;
}
private function __queueCleanDB()

View File

@ -363,6 +363,118 @@ class Event extends AppModel
)
);
public function __construct($id = false, $table = null, $ds = null)
{
parent::__construct($id, $table, $ds);
$this->export_types = array(
'json' => array(
'extension' => '.json',
'type' => 'JSON',
'scope' => 'Event',
'requiresPublished' => 0,
'params' => array('includeAttachments' => 1, 'ignore' => 1, 'returnFormat' => 'json'),
'description' => __('Click this to download all events and attributes that you have access to in MISP JSON format.'),
),
'xml' => array(
'extension' => '.xml',
'type' => 'XML',
'scope' => 'Event',
'params' => array('includeAttachments' => 1, 'ignore' => 1, 'returnFormat' => 'xml'),
'requiresPublished' => 0,
'description' => __('Click this to download all events and attributes that you have access to in MISP XML format.'),
),
'csv_sig' => array(
'extension' => '.csv',
'type' => 'CSV_Sig',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('published' => 1, 'to_ids' => 1, 'returnFormat' => 'csv'),
'description' => __('Click this to download all attributes that are indicators and that you have access to (except file attachments) in CSV format.'),
),
'csv_all' => array(
'extension' => '.csv',
'type' => 'CSV_All',
'scope' => 'Event',
'requiresPublished' => 0,
'params' => array('ignore' => 1, 'returnFormat' => 'csv'),
'description' => __('Click this to download all attributes that you have access to (except file attachments) in CSV format.'),
),
'suricata' => array(
'extension' => '.rules',
'type' => 'Suricata',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'suricata'),
'description' => __('Click this to download all network related attributes that you have access to under the Suricata rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a whitelist containing host, domain name and IP numbers to exclude from the NIDS export.'),
),
'snort' => array(
'extension' => '.rules',
'type' => 'Snort',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'snort'),
'description' => __('Click this to download all network related attributes that you have access to under the Snort rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a whitelist containing host, domain name and IP numbers to exclude from the NIDS export.'),
),
'bro' => array(
'extension' => '.intel',
'type' => 'Bro',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'bro'),
'description' => __('Click this to download all network related attributes that you have access to under the Bro rule format. Only published events and attributes marked as IDS Signature are exported. Administration is able to maintain a whitelist containing host, domain name and IP numbers to exclude from the NIDS export.'),
),
'stix' => array(
'extension' => '.xml',
'type' => 'STIX',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'stix', 'includeAttachments' => 1),
'description' => __('Click this to download an a STIX document containing the STIX version of all events and attributes that you have access to.')
),
'stix2' => array(
'extension' => '.json',
'type' => 'STIX2',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'stix2', 'includeAttachments' => 1),
'description' => __('Click this to download an a STIX2 document containing the STIX2 version of all events and attributes that you have access to.')
),
'rpz' => array(
'extension' => '.txt',
'type' => 'RPZ',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'rpz'),
'description' => __('Click this to download an RPZ Zone file generated from all ip-src/ip-dst, hostname, domain attributes. This can be useful for DNS level firewalling. Only published events and attributes marked as IDS Signature are exported.')
),
'text' => array(
'extension' => '.txt',
'type' => 'TEXT',
'scope' => 'Attribute',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'text', 'includeAttachments' => 1),
'description' => __('Click on one of the buttons below to download all the attributes with the matching type. This list can be used to feed forensic software when searching for susipicious files. Only published events and attributes marked as IDS Signature are exported.')
),
'yara' => array(
'extension' => '.yara',
'type' => 'Yara',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'yara'),
'description' => __('Click this to download Yara rules generated from all relevant attributes.')
),
'yara-json' => array(
'extension' => '.json',
'type' => 'Yara',
'scope' => 'Event',
'requiresPublished' => 1,
'params' => array('returnFormat' => 'yara-json'),
'description' => __('Click this to download Yara rules generated from all relevant attributes. Rules are returned in a JSON format with information about origin (generated or parsed) and validity.')
),
);
}
public function beforeDelete($cascade = true)
{
// blacklist the event UUID if the feature is enabled

View File

@ -915,6 +915,14 @@ class Server extends AppModel
'test' => 'testBool',
'type' => 'boolean',
'null' => true
),
'updateTimeThreshold' => array(
'level' => 1,
'description' => __('Sets the minimum time before being able to re-trigger an update if the previous one failed. (safe guard to avoid starting the same update multiple time)'),
'value' => '7200',
'test' => 'testForNumeric',
'type' => 'numeric',
'null' => true
)
),
'GnuPG' => array(

View File

@ -6,6 +6,13 @@
$message = str_replace('$flashErrorMessage', '<span class="useCursorPointer underline bold" onClick="flashErrorPopover();">here</span>', $message);
}
echo $message;
if (isset($params['url'])) {
if (isset($params['urlName'])) {
echo '<a href="' . h($params['url']) . '">' . h($params['urlName']) . '</a>';
} else {
echo '<a href="' . h($params['url']) . '">' . h($params['url']) . '</a>';
}
}
if ($this->Session->read('flashErrorMessage')) {
echo sprintf('<div class="hidden" id="flashErrorMessage">%s</div>', $this->element('flashErrorMessage', array('message' => $this->Session->read('flashErrorMessage'))));
}

View File

@ -60,6 +60,7 @@
</span><br />
<pre class="hidden green bold" id="gitResult"></pre>
<button title="<?php echo __('Pull the latest MISP version from github');?>" class="btn btn-inverse" style="padding-top:1px;padding-bottom:1px;" onClick = "updateMISP();"><?php echo __('Update MISP');?></button>
<a title="<?php echo __('Click the following button to go to the update progress page. This page lists all updates that are currently queued and executed.'); ?>" class="btn btn-inverse" style="padding-top:1px;padding-bottom:1px;" href="<?php echo $baseurl; ?>/servers/updateProgress/"><?php echo __('Update Progress');?></a>
</div>
<h3><?php echo __('Submodules version');?>
<it id="refreshSubmoduleStatus" class="fas fa-sync useCursorPointer" style="font-size: small; margin-left: 5px;" title="<?php echo __('Refresh submodules version.'); ?>"></it>
@ -361,12 +362,9 @@
</div><br />
<span class="btn btn-inverse" role="button" tabindex="0" aria-label="<?php echo __('Check for orphaned attribute');?>" title="<?php echo __('Check for orphaned attributes');?>" style="padding-top:1px;padding-bottom:1px;" onClick="checkOrphanedAttributes();"><?php echo __('Check for orphaned attributes');?></span><br /><br />
<?php echo $this->Form->postButton(__('Remove orphaned attributes'), $baseurl . '/attributes/pruneOrphanedAttributes', $options = array('class' => 'btn btn-primary', 'style' => 'padding-top:1px;padding-bottom:1px;')); ?>
<h3><?php echo __('Verify GnuPG keys');?></h3>
<p><?php echo __('Run a full validation of all GnuPG keys within this instance\'s userbase. The script will try to identify possible issues with each key and report back on the results.');?></p>
<span class="btn btn-inverse" onClick="location.href='<?php echo $baseurl;?>/users/verifyGPG';"><?php echo __('Verify GnuPG keys');?></span> (<?php echo __('Check whether every user\'s GnuPG key is usable');?>)</li>
<h3><?php echo __('Database cleanup scripts');?></h3>
<p><?php echo __('If you run into an issue with an infinite upgrade loop (when upgrading from version ~2.4.50) that ends up filling your database with upgrade script log messages, run the following script.');?></p>
<?php echo $this->Form->postButton(__('Prune upgrade logs'), $baseurl . '/logs/pruneUpdateLogs', $options = array('class' => 'btn btn-primary', 'style' => 'padding-top:1px;padding-bottom:1px;')); ?>
<h3><?php echo __('Administrator On-demand Action');?></h3>
<p><?php echo __('Click the following button to go to the Administrator On-demand Action page.');?></p>
<span class="btn btn-inverse" style="padding-top:1px;padding-bottom:1px;" onClick="location.href = '<?php echo $baseurl; ?>/servers/ondemandAction/';"><?php echo __('Administrator On-demand Action');?></span>
<h3><?php echo __('Legacy Administrative Tools');?></h3>
<p><?php echo __('Click the following button to go to the legacy administrative tools page. There should in general be no need to do this unless you are upgrading a very old MISP instance (<2.4), all updates are done automatically with more current versions.');?></p>
<span class="btn btn-inverse" style="padding-top:1px;padding-bottom:1px;" onClick="location.href = '<?php echo $baseurl; ?>/pages/display/administration';"><?php echo __('Legacy Administrative Tools');?></span>

View File

@ -14,7 +14,7 @@
<tr>
<td style="vertical-align:top">
<?php
echo $this->Form->submit('Enrich', array('class' => 'btn btn-primary'));
echo $this->Form->submit(__('Enrich'), array('class' => 'btn btn-primary'));
?>
</td>
<td style="width:540px;"></td>

View File

@ -23,7 +23,7 @@
</fieldset>
<?php
echo $this->Form->button('Merge', array('class' => 'btn btn-primary'));
echo $this->Form->button(__('Merge'), array('class' => 'btn btn-primary'));
echo $this->Form->end();
?>
</div>

View File

@ -0,0 +1,87 @@
<?php
if (!$isSiteAdmin) exit();
$disabledBtnText = $updateLocked ? 'title="' . __('An action is already in progress...') . '" disabled' : 'title=' . __('Action');
?>
<div class="index">
<h2><?php echo __('Administrator On-demand Action'); ?></h2>
<?php if ($updateLocked): ?>
<div class='alert alert-danger'>
<?php echo __('An action is already in progress. Starting new actions is not possible until completion of the current action process.'); ?>
</div>
<?php endif; ?>
<div style="margin-bottom: 10px;">
<a id="btnShowProgress" class="btn btn-inverse" href="<?php echo $baseurl; ?>/servers/updateProgress/"><?php echo __('Show Update Progress Page'); ?></a>
</div>
<?php $i = 0; ?>
<?php foreach($actions as $id => $action): ?>
<div class="headerUpdateBlock">
<h4><?php echo ($i+1) . '. ' . h($action['title']); ?></h4>
</div>
<div class="bodyUpdateBlock">
<h5><?php echo h($action['description']); ?></h5>
<?php if (!$action['done']): ?>
<?php if ($action['requirements'] !== ''): ?>
<div class="alert alert-warning">
<i class="icon-warning-sign"></i> <?php echo h($action['requirements']); ?>
</div>
<?php endif; ?>
<?php if ($action['recommendBackup']): ?>
<div class="alert alert-block">
<i class="icon-warning-sign"></i> <?php echo __('Running this script may take a very long time depending of the size of your database. It is adviced that you <b>back your database up</b> before running it.'); ?>
</div>
<?php endif; ?>
<?php if ($action['liveOff']): ?>
<div class="alert alert-info">
<i class="icon-question-sign"></i> <?php echo __('Running this script will make this instance unusable for all users (not site-admin) during the time of upgrade.'); ?>
</div>
<?php endif; ?>
<?php
$url_param = $action['liveOff'] ? '1' : '';
$url_param .= $action['exitOnError'] ? '/1' : '';
echo $this->Form->create(false, array( 'url' => $baseurl . $action['url'] . $url_param ));
?>
<button class="btn btn-warning <?php echo isset($action['redirectToUpdateProgress']) && $action['redirectToUpdateProgress'] ? 'submitButton' : 'submitButtonToUpdateProgress'; ?>" <?php echo $disabledBtnText; ?> role="button" tabindex="0" aria-label="<?php echo __('Submit'); ?>"><?php echo __('Action: ') . h($action['title']); ?></button>
<?php
echo $this->Form->end();
?>
<?php else: ?>
<div class="alert alert-success">
<i class="fa fa-check-square"></i> <?php echo __('This action has been done and cannot be run again.'); ?>
</div>
<?php endif; ?>
</div>
<?php $i++; ?>
<?php endforeach; ?>
</div>
<?php
echo $this->element('side_menu', array('menuList' => 'admin', 'menuItem' => 'adminTools'));
?>
<script type="text/javascript">
$(document).ready(function(){
$('.submitButtonToUpdateProgress').click(function() {
var form = $(this).closest("form");
$.ajax({
data: form.serialize(),
cache: false,
timeout: 100,
complete: function (data, textStatus) {
window.location.href = $('#btnShowProgress').prop('href');
},
type:"post",
url: form.prop('action')
});
});
});
</script>

View File

@ -0,0 +1,161 @@
<?php
if (!$isSiteAdmin) exit();
if ($updateProgress['total'] !== 0 ) {
$percentageFail = floor(count($updateProgress['failed_num']) / $updateProgress['total']*100);
$percentage = floor(($updateProgress['current']) / $updateProgress['total']*100);
} else {
$percentage = 100;
$percentageFail = 0;
}
if (isset($updateProgress['preTestSuccess']) && $updateProgress['preTestSuccess'] === false) {
$percentage = 0;
$percentageFail = 100;
}
?>
<div style="width: 50%;margin: 0 auto;">
<?php if (count($updateProgress['commands']) > 0): ?>
<h2><?php echo(__('Database Update progress'));?></h2>
<div class="" style="max-width: 1000px;">
<div>
<h5 style='display: inline-block'>Pre update test status:</h5>
<?php
$icon = isset($updateProgress['preTestSuccess']) ? ($updateProgress['preTestSuccess'] ? 'fa-check' : 'fa-times') : 'fa-question-circle ';
?>
<i class='fa <?php echo($icon); ?>' style="font-size: x-large"></i>
</div>
<div class="progress progress-striped" style="max-width: 1000px;">
<div id="pb-progress" class="bar" style="font-weight: bold; width: <?php echo h($percentage);?>%;"><?php echo h($percentage);?>%</div>
<div id="pb-fail" class="bar" style="width: <?php echo h($percentageFail);?>%; background-color: #ee5f5b;"></div>
</div>
<table class="table table-bordered table-stripped updateProgressTable">
<thead>
<tr>
<th></th>
<th>Update command</th>
</tr>
</thead>
<tbody>
<?php foreach($updateProgress['commands'] as $i => $cmd):
if (isset($updateProgress['results'][$i])) {
$res = $updateProgress['results'][$i];
} else {
$res = false;
}
$rowDone = $i < $updateProgress['current'];
$rowCurrent = $i === $updateProgress['current'];
$rowFail = in_array($i, $updateProgress['failed_num']);
$rowClass = '';
$rowIcon = '<i id="icon-' . $i . '" class="fa"></i>';
if ($rowDone) {
$rowClass = 'class="alert alert-success"';
$rowIcon = '<i id="icon-' . $i . '" class="fa fa-check-circle-o"></i>';
}
if ($rowCurrent && !$rowFail) {
$rowClass = 'class="alert alert-info"';
$rowIcon = '<i id="icon-' . $i . '" class="fa fa-cogs"></i>';
} else if ($rowFail) {
$rowClass = 'class="alert alert-danger"';
$rowIcon = '<i id="icon-' . $i . '" class="fa fa-times-circle-o"></i>';
}
if (isset($updateProgress['time']['started'][$i])) {
$datetimeStart = $updateProgress['time']['started'][$i];
if (isset($updateProgress['time']['elapsed'][$i])) {
$updateDuration = $updateProgress['time']['elapsed'][$i];
} else { // compute elapsed based on started
$temp = new DateTime();
$diff = $temp->diff(new DateTime($datetimeStart));
$updateDuration = $diff->format('%H:%I:%S');
}
} else {
$datetimeStart = '';
$updateDuration = '';
}
?>
<tr id="row-<?php echo $i; ?>" <?php echo $rowClass; ?> >
<td><?php echo $rowIcon; ?></td>
<td>
<div>
<a style="cursor: pointer; maring-bottom: 2px;" onclick="toggleVisiblity(<?php echo $i;?>)">
<span class="foldable fa fa-terminal"></span>
<?php echo __('Update ') . ($i+1); ?>
<span class="inline-term"><?php echo h(substr($cmd, 0, 60)) . (strlen($cmd) > 60 ? '[...]' : '' );?></span>
<span class="label">
<?php echo __('Started @ '); ?>
<span id="startedTime-<?php echo $i; ?>"><?php echo h($datetimeStart); ?></span>
</span>
<span class="label">
<?php echo __('Elapsed Time @ '); ?>
<span id="elapsedTime-<?php echo $i; ?>"><?php echo h($updateDuration); ?></span>
</span>
</a>
<div data-terminalid="<?php echo $i;?>" style="display: none; margin-top: 5px;">
<div id="termcmd-<?php echo $i;?>" class="div-terminal">
<?php
$temp = preg_replace('/^\n*\s+/', '', $cmd);
$temp = preg_split('/\s{4,}/m', $temp);
foreach ($temp as $j => $line) {
$pad = $j > 0 ? '30' : '0';
if ($line !== '') {
echo '<span style="margin-left: ' . $pad . 'px;">' . h($line) . '</span>';
}
}
?>
</div>
<div>
<span class="fa fa-level-up terminal-res-icon"></span>
<div id="termres-<?php echo $i;?>" class="div-terminal terminal-res">
<?php
if ($res !== false) {
$temp = preg_replace('/^\n*\s+/', '', $res);
$temp = preg_split('/\s{2,}/m', $temp);
foreach ($temp as $j => $line) {
$pad = $j > 0 ? '30' : '0';
if ($line !== '') {
echo '<span style="margin-left: ' . $pad . 'px;">' . h($line) . '</span>';
}
}
}
?>
</div>
</div>
</div>
</div>
<div id="single-update-progress-<?php echo $i;?>" class="single-update-progress hidden">
<div class="small-pb-in-td">
<div id="single-update-pb-<?php echo $i;?>" style="height: 100%; background: #149bdf; transition: width 0.6s ease;"></div>
</div>
<div id="small-state-text-<?php echo $i;?>" class="small-state-text-in-td badge" class="badge">Filling schema table</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php else: ?>
<h2><?php echo __('No update in progress'); ?></h2>
<script>
$(document).ready(function() {
setInterval(function() { location.reload(); }, 1000);
});
</script>
<?php endif; ?>
<script>
var updateProgress = <?php echo json_encode($updateProgress); ?>;
var urlGetProgress = "<?php echo $baseurl; ?>/servers/updateProgress";
</script>
<?php
echo $this->element('genericElements/assetLoader', array(
'css' => array('update_progress'),
'js' => array('update_progress')
));
?>
</div>

View File

@ -0,0 +1,76 @@
.headerUpdateBlock {
border: #ccc solid 1px;
border-bottom: 1px solid transparent;
background: #e6e6e6;
padding: 0px 6px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.bodyUpdateBlock {
border: #ccc solid 1px;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
background: #fbfbfb;
padding: 6px;
margin-bottom: 10px;
}
.div-terminal {
background-color: #303030;
color: white;
font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
padding: 10px;
border-radius: 5px;
}
.div-terminal > span {
display: block;
}
.terminal-res {
margin-top: 5px;
margin-left: 25px;
max-height: 500px;
overflow-y: auto;
}
.terminal-res-icon {
position: absolute;
transform: rotate(90deg);
font-size: large;
margin-right: 5px;
margin-left: 5px;
}
table.updateProgressTable > tbody > tr > td:first-child {
width: 40px;
text-align: center;
}
table.updateProgressTable .small-pb-in-td {
width: calc(100% + 16px);
height: 4px;
position: relative;
left: -8px;
top: 26px;
background-color: #f3f3f3;
}
table.updateProgressTable .small-state-text-in-td {
position: relative;
display: inline-block;
float: right;
}
.back-and-forth-animation {
position: relative;
animation: backandforthAnim 2.0s infinite;
}
@keyframes backandforthAnim {
0% {left: 0;}
50% {left: calc(95%);}
100% {left: 0;}
}

View File

@ -0,0 +1,182 @@
function toggleVisiblity(termId, auto, show) {
var term = $('div[data-terminalid='+termId+']')
if (auto === true) {
if (term.data('manual') !== true) { // show if manual is not set
if (show === true) {
term.show();
} else if (show === false) {
term.hide();
} else {
term.toggle();
}
}
} else {
term.data('manual', true);
if (show === true) {
term.show();
} else if (show === false) {
term.hide();
} else {
term.toggle();
}
}
}
var pooler;
var poolerInterval = 3000;
$(document).ready(function() {
pooler = setInterval(function() { update_state(); }, poolerInterval);
});
function update_state() {
$.getJSON(urlGetProgress, function(data) {
var total = parseInt(data['total']);
var current = parseInt(data['current']);
var failArray = data['failed_num'];
for (var i=0; i<total; i++) {
var term = $('div[data-terminalid='+i+']')
toggleVisiblity(i, true, false);
if (i < current) {
if (failArray.indexOf(String(i)) != -1) {
update_row_state(i, 2);
} else {
update_row_state(i, 0);
}
} else if (i == current) {
if (failArray.indexOf(String(i)) != -1) {
update_row_state(i, 2);
toggleVisiblity(i, true, true);
} else {
update_row_state(i, 1);
toggleVisiblity(i-1, true, true);
}
update_single_update_progress(i, data);
} else {
update_row_state(i, 3);
}
}
update_messages(data);
if (total > 0) {
var percFail = Math.round(failArray.length/total*100);
var perc = Math.round(current/total*100);
update_pb(perc, percFail);
}
if ((current+1) >= total || failArray.indexOf(current) != -1) {
clearInterval(pooler);
$('.single-update-progress').hide();
}
});
}
function update_messages(messages) {
if (messages.commands === undefined) {
return;
}
messages.commands.forEach(function(msg, i) {
var div = $('#termcmd-'+i);
create_spans_from_message(div, msg);
});
messages.results.forEach(function(msg, i) {
var div = $('#termres-'+i);
div.css('display', '');
create_spans_from_message(div, msg);
});
messages.time.started.forEach(function(startedText, i) {
var elapsedText = messages.time.elapsed[i];
if (elapsedText === undefined) {
var diff = new Date((new Date()).getTime() - (new Date(startedText)).getTime());
elapsedText = pad(diff.getUTCHours(), 2)
+ ':' + pad(diff.getUTCMinutes(), 2)
+ ':' + pad(diff.getUTCSeconds(), 2);
}
update_times(i, startedText, elapsedText)
});
}
function create_spans_from_message(toAppendto, msg) {
toAppendto.empty();
// create span for each line of text
msg = msg.replace(/^\n*\s+/, '');
var lines = msg.split(/\s{2,}/m)
lines.forEach(function(line, j) {
var pad = j > 0 ? '30' : '0';
if (line !== '') {
var span = $('<span style="margin-left: ' + pad + 'px;">' + line + '</span>');
toAppendto.append(span);
}
});
}
function update_row_state(i, state) {
var icon = $('#icon-'+i);
var row = $('#row-'+i);
switch(state) {
case 0: // success
row.removeClass('alert-danger alert-info');
row.addClass('alert-success');
icon.removeClass('fa-times-circle-o fa-cogs');
icon.addClass('fa-check-circle-o');
break;
case 1: // current
row.removeClass('alert-success alert-danger');
row.addClass('alert-info');
icon.removeClass('fa-check-circle-o', 'fa-times-circle-o');
icon.addClass('fa-cogs');
break;
case 2: //fail
row.removeClass('alert-success alert-info');
row.addClass('alert-danger');
icon.removeClass('fa-check-circle-o fa-cogs');
icon.addClass('fa-times-circle-o');
break;
case 3: //no state
default:
row.removeClass('alert-success alert-info alert-danger');
icon.removeClass('fa-check-circle-o fa-times-circle-o fa-cogs');
break;
}
}
function update_pb(perc, percFail) {
var pb = $('#pb-progress');
pb.css('width', perc+'%');
pb.text(perc+'%');
var pbF = $('#pb-fail');
pbF.css('width', percFail+'%');
}
function update_times(i, startedText, elapsedText) {
var started = $('#startedTime-'+i);
var elapsed = $('#elapsedTime-'+i);
started.text(startedText);
elapsed.text(elapsedText);
}
function update_single_update_progress(i, data) {
$('.single-update-progress').hide();
var div = $('#single-update-progress-'+i);
var pb = div.find('#single-update-pb-'+i);
var state = div.find('#small-state-text-'+i);
div.show();
var perc = parseInt(data['process_list']['PROGRESS']);
if (data['process_list']['MAX_STAGE'] == 0) { // if MAX_STAGE == 0, progress could not be determined
perc = 5;
if (data['failed_num'].indexOf(data['current']) >= 0) { // do not animate if failed
state.text('Failed');
perc = 0;
} else {
state.text('Unkown or No state');
pb.addClass('back-and-forth-animation');
}
} else {
perc = perc == 0 ? 1 : perc; // for UI, always set min progress to 1
pb.removeClass('back-and-forth-animation');
state.text(data['process_list']['STATE']);
}
pb.css('width', perc+'%');
}
function pad(num, size){ return ('000000000' + num).substr(-size); }