diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 18211593b..e5ca1fa09 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -30,10 +30,6 @@ jobs: with: submodules: 'recursive' - # Stop mysql - - name: Shutdown Ubuntu MySQL (SUDO) - run: sudo service mysql stop - # Run mariadb - uses: getong/mariadb-action@v1.1 with: @@ -61,21 +57,22 @@ jobs: echo "USER=`id -u -n`" >> $GITHUB_ENV echo "HOST=localhost" >> $GITHUB_ENV - # Runs a set of commands using the runners shell - - name: Install deps + - name: Install system deps env: php_version: ${{ matrix.php }} run: | - sudo apt-get -y update - # Repo is missing for unknown reason - LC_ALL=C.UTF-8 sudo apt-add-repository ppa:ondrej/php -y - if [[ $php_version == "7.2" ]]; then - # hotfix due to: https://bugs.php.net/bug.php?id=81640 TODO: remove after libpcre2-8-0:10.36 gets to stable channel - sudo apt --fix-broken install - fi - sudo apt-get -y install curl python3 python3-zmq python3-requests python3-pip python3-nose python3-redis python3-lxml apache2 libapache2-mod-php$php_version - sudo pip3 install virtualenv # virtualenv must be instaled from pip and not from ubuntu packages - curl -sSL https://install.python-poetry.org | python - + sudo apt-get -y update + # Repo is missing for unknown reason + LC_ALL=C.UTF-8 sudo apt-add-repository ppa:ondrej/php -y + if [[ $php_version == "7.2" ]]; then + # hotfix due to: https://bugs.php.net/bug.php?id=81640 TODO: remove after libpcre2-8-0:10.36 gets to stable channel + sudo apt-get --fix-broken install + fi + sudo apt-get -y install curl python3 python3-pip python3-virtualenv apache2 libapache2-mod-php$php_version + + # Runs a set of commands using the runners shell + - name: Install deps + run: | sudo chown $USER:www-data $HOME/.composer pushd app sudo -H -u $USER php composer.phar install --no-progress @@ -149,32 +146,33 @@ jobs: - name: Configure MISP run: | - sudo -E su $USER -c 'app/Console/cake userInit -q | sudo tee ./key.txt' + sudo -u $USER app/Console/cake userInit -q | sudo tee ./key.txt echo "AUTH=`cat key.txt`" >> $GITHUB_ENV - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Session.autoRegenerate" 0' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Session.timeout" 600' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Session.cookieTimeout" 3600' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.host_org_id" 1' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.email" "info@admin.test"' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.disable_emailing" false' - sudo -E su $USER -c 'app/Console/cake Admin setSetting --force "debug" true' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.CustomAuth_disable_logout" false' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.redis_host" "127.0.0.1"' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.redis_port" 6379' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.redis_database" 13' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.redis_password" ""' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "GnuPG.email" "info@admin.test"' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "GnuPG.homedir" "`pwd`/.gnupg"' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "GnuPG.password" "travistest"' + sudo -u $USER app/Console/cake Admin setSetting "Session.autoRegenerate" 0 + sudo -u $USER app/Console/cake Admin setSetting "Session.timeout" 600 + sudo -u $USER app/Console/cake Admin setSetting "Session.cookieTimeout" 3600 + sudo -u $USER app/Console/cake Admin setSetting "MISP.host_org_id" 1 + sudo -u $USER app/Console/cake Admin setSetting "MISP.email" "info@admin.test" + sudo -u $USER app/Console/cake Admin setSetting "MISP.disable_emailing" false + sudo -u $USER app/Console/cake Admin setSetting --force "debug" true + sudo -u $USER app/Console/cake Admin setSetting "Plugin.CustomAuth_disable_logout" false + sudo -u $USER app/Console/cake Admin setSetting "MISP.redis_host" "127.0.0.1" + sudo -u $USER app/Console/cake Admin setSetting "MISP.redis_port" 6379 + sudo -u $USER app/Console/cake Admin setSetting "MISP.redis_database" 13 + sudo -u $USER app/Console/cake Admin setSetting "MISP.redis_password" "" + sudo -u $USER app/Console/cake Admin setSetting "GnuPG.email" "info@admin.test" + sudo -u $USER app/Console/cake Admin setSetting "GnuPG.homedir" "`pwd`/.gnupg" + sudo -u $USER app/Console/cake Admin setSetting "GnuPG.password" "travistest" + sudo -u $USER app/Console/cake Admin setSetting "MISP.download_gpg_from_homedir" 1 - name: Configure ZMQ run: | - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_host" "127.0.0.1"' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_port" 6379' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_database" 1' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_password" ""' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_enable" 1' - sudo -E su $USER -c 'app/Console/cake Admin setSetting "Plugin.ZeroMQ_audit_notifications_enable" 1' + sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_host" "127.0.0.1" + sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_port" 6379 + sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_database" 1 + sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_redis_password" "" + sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_enable" 1 + sudo -u $USER app/Console/cake Admin setSetting "Plugin.ZeroMQ_audit_notifications_enable" 1 - name: Update Galaxies run: sudo -E su $USER -c 'app/Console/cake Admin updateGalaxies' @@ -209,16 +207,7 @@ jobs: sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.python_bin" "$GITHUB_WORKSPACE/venv/bin/python"' . ./venv/bin/activate export PYTHONPATH=$PYTHONPATH:./app/files/scripts - pushd ./app/files/scripts/cti-python-stix2 - pip install . - popd - pushd ./app/files/scripts/python-stix - pip install . - popd - pushd PyMISP - pip install .[fileobjects,email] - popd - pip install zmq redis plyara + pip install ./PyMISP[fileobjects,email] ./app/files/scripts/python-stix ./app/files/scripts/cti-python-stix2 pyzmq redis plyara deactivate - name: Test if apache is working @@ -242,31 +231,32 @@ jobs: - name: Run PHP tests run: | ./app/Vendor/bin/parallel-lint --exclude app/Lib/cakephp/ --exclude app/Vendor/ --exclude app/Lib/random_compat/ -e php,ctp app/ - ./app/Vendor/bin/phpunit app/Test/ + sudo -u www-data ./app/Vendor/bin/phpunit app/Test/ - name: Run tests run: | - export PATH=$HOME/.local/env:$PATH # enable poetry binary - pushd tests ./curl_tests_GH.sh $AUTH $HOST popd sudo chmod -R g+ws `pwd`/app/tmp/logs + + . ./venv/bin/activate pushd PyMISP - poetry install -E fileobjects -E openioc -E virustotal -E docs -E pdfexport -E email - poetry run python tests/testlive_comprehensive.py - poetry add lxml - poetry run python ../tests/testlive_security.py -v - poetry run python ../tests/testlive_sync.py - poetry run python ../tests/testlive_comprehensive_local.py -v - poetry run python tests/test_mispevent.py + python tests/testlive_comprehensive.py + popd + python tests/testlive_security.py -v + python tests/testlive_sync.py + python tests/testlive_comprehensive_local.py -v + pushd PyMISP + python tests/test_mispevent.py popd cp PyMISP/tests/keys.py PyMISP/examples/events/ pushd PyMISP/examples/events/ - poetry run python ./create_massive_dummy_events.py -l 5 -a 30 + python ./create_massive_dummy_events.py -l 5 -a 30 popd - python3 tools/misp-feed/validate.py + python tools/misp-feed/validate.py + deactivate - name: Logs if: ${{ always() }} diff --git a/.gitignore b/.gitignore index 29f582b27..5abfb5e0c 100755 --- a/.gitignore +++ b/.gitignore @@ -107,3 +107,8 @@ vagrant/.vagrant/ vagrant/*.log /app/Lib/Dashboard/Custom/* !/app/Lib/Dashboard/Custom/empty +/app/View/Emails/html/Custom/* +!/app/View/Emails/html/Custom/empty +/app/View/Emails/text/Custom/* +!/app/View/Emails/text/Custom/empty + diff --git a/app/Console/Command/EventShell.php b/app/Console/Command/EventShell.php index bb402d032..816f3378e 100644 --- a/app/Console/Command/EventShell.php +++ b/app/Console/Command/EventShell.php @@ -187,8 +187,9 @@ class EventShell extends AppShell $this->Job->id = $id; $export_type = $this->args[2]; file_put_contents('/tmp/test', $export_type); - $typeData = $this->Event->export_types[$export_type]; - if (!in_array($export_type, array_keys($this->Event->export_types))) { + $exportTypes = $this->Event->exportTypes(); + $typeData = $exportTypes[$export_type]; + if (!in_array($export_type, array_keys($exportTypes))) { $this->Job->saveField('progress', 100); $timeDelta = (time()-$timeStart); $this->Job->saveField('message', 'Job Failed due to invalid export format. (in '.$timeDelta.'s)'); @@ -385,7 +386,7 @@ class EventShell extends AppShell // the special cache files containing all events $i = 0; foreach ($users as $user) { - foreach ($this->Event->export_types as $k => $type) { + foreach ($this->Event->exportTypes() as $k => $type) { if ($k == 'stix') continue; $this->Job->cache($k, $user['User']); $i++; diff --git a/app/Console/Command/Ls22Shell.php b/app/Console/Command/Ls22Shell.php new file mode 100644 index 000000000..63a945bbe --- /dev/null +++ b/app/Console/Command/Ls22Shell.php @@ -0,0 +1,361 @@ + $line) { + if ($k === 0) { + continue; + } + $fields = explode(',', $line); + if (count($fields) === 4 && $fields[1] === 'admin@admin.test') { + $this->__servers[] = [ + 'Server' => [ + 'url' => trim($fields[0]), + 'authkey' => trim($fields[2]) + ] + ]; + } + } + } + + public function getOptionParser() + { + $parser = parent::getOptionParser(); + $parser->addSubcommand('enableTaxonomy', [ + 'help' => __('Enable a taxonomy with all its tags.'), + 'parser' => array( + 'options' => array( + 'instances' => [ + 'help' => 'Path to the instance file, by default "instances.csv" from the local directory', + 'short' => 'i', + 'required' => true + ], + 'taxonomy' => [ + 'help' => 'The name of the taxonomy to enable, such as "tlp"', + 'short' => 't', + 'required' => true + ], + 'misp_url_filter' => [ + 'help' => 'The url of the instance to enable it for - otherwise all are selected', + 'short' => 'm', + 'required' => false + ] + ), + ), + ]); + $parser->addSubcommand('addWarninglist', [ + 'help' => __('Inject warninglist'), + 'parser' => array( + 'options' => array( + 'instances' => [ + 'help' => 'Path to the instance file, by default "instances.csv" from the local directory', + 'short' => 'i', + 'required' => true + ], + 'warninglist' => [ + 'help' => 'Path to the warninglist file', + 'short' => 'w', + 'required' => true + ] + ), + ), + ]); + $parser->addSubcommand('status', [ + 'help' => __('Check if the instances are available / the API key works.'), + 'parser' => array( + 'options' => array( + 'instances' => [ + 'help' => 'Path to the instance file, by default "instances.csv" from the local directory', + 'short' => 'i', + 'required' => true + ] + ), + ), + ]); + $parser->addSubcommand('scores', [ + 'help' => __('Generate the scores for all BTs.'), + 'parser' => array( + 'options' => array( + 'instances' => [ + 'help' => 'Path to the instance file, by default "instances.csv" from the local directory', + 'short' => 'i', + 'required' => true + ], + 'server_url' => [ + 'help' => 'URL of the server to query for the scores. If nothing is specified, the first valid entry from instances.csv is taken.', + 'short' => 's', + 'required' => false + ], + 'from' => [ + 'help' => 'Lower bound of the date. Accepts timestamp or date distance (such as 1d or 5h). Defaults to unbounded.', + 'short' => 'f', + 'required' => false + ], + 'to' => [ + 'help' => 'Upper bound of the date. Accepts timestamp or date distance (such as 1d or 5h). Defaults to unbounded.', + 'short' => 't', + 'required' => false + ] + ), + ), + ]); + return $parser; + } + + public function enableTaxonomy() + { + $taxonomyToEnable = $this->param('taxonomy'); + $instanceFilter = $this->param('misp_url_filter'); + if (empty($taxonomyToEnable)) { + $this->error('No taxonomy provided', 'Provide a taxonomy by specifying the -t or --taxonomy options.'); + } + $this->__getInstances($this->param('instances')); + $results = []; + foreach ($this->__servers as $server) { + if (!empty($instanceFilter) && strtolower(trim($server['Server']['url'])) !== strtolower(trim($instanceFilter))) { + continue; + } + $HttpSocket = $this->Server->setupHttpSocket($server, null); + $request = $this->Server->setupSyncRequest($server, 'Server'); + $response = $HttpSocket->get($server['Server']['url'] . '/taxonomies/index', false, $request); + if (!$response->isOk()) { + $this->out($server['Server']['url'] . ': ' . 'Connection or auth failed', 1, Shell::NORMAL); + } + $taxonomies = json_decode($response->body, true); + $success = false; + foreach ($taxonomies as $taxonomy) { + if ($taxonomy['Taxonomy']['namespace'] === $taxonomyToEnable) { + $response = $HttpSocket->post($server['Server']['url'] . '/taxonomies/enable/' . $taxonomy['Taxonomy']['id'], '{}', $request); + if ($response->isOk()) { + $response = $HttpSocket->post($server['Server']['url'] . '/taxonomies/addTag/' . $taxonomy['Taxonomy']['id'], '{}', $request); + if ($response->isOk()) { + $success = true; + } + } + } + } + $results[$server['Server']['url']] = $success ? 'Success' : 'Failed'; + $statusWrapped = sprintf( + '<%s>%s', + $success ? 'info' : 'error', + $results[$server['Server']['url']], + $success ? 'info' : 'error' + ); + $this->out($server['Server']['url'] . ': ' . $statusWrapped, 1, Shell::NORMAL); + } + } + + public function status() + { + $this->__getInstances($this->param('instances')); + $results = []; + foreach ($this->__servers as $server) { + $HttpSocket = $this->Server->setupHttpSocket($server, null); + $request = $this->Server->setupSyncRequest($server, 'Server'); + $start_time = microtime(true); + $response = $HttpSocket->get($server['Server']['url'] . '/users/view/me', false, $request); + $execution_time = round((microtime(true) - $start_time) * 1000); + $statusWrapped = sprintf( + '<%s>%s', + $response->isOk() ? 'info' : 'error', + $response->isOk() ? 'OK (' . $execution_time . 'ms)' : 'Failed. (' . $response->code . ')', + $response->isOk() ? 'info' : 'error' + ); + $this->out($server['Server']['url'] . ': ' . $statusWrapped, 1, Shell::NORMAL); + $results[$server['Server']['url']] = $response->isOk() ? $execution_time : false; + } + } + + public function addWarninglist() + { + $path = $this->param('warninglist'); + if (empty($path)) { + $this->error('No warninglist provided', 'Provide a path to a file containing a warninglist JSON by specifying the -w or --warninglist options.'); + } + $file = file_get_contents($path); + $this->__getInstances($this->param('instances')); + $results = []; + foreach ($this->__servers as $server) { + $HttpSocket = $this->Server->setupHttpSocket($server, null); + $request = $this->Server->setupSyncRequest($server, 'Server'); + $start_time = microtime(true); + $response = $HttpSocket->post($server['Server']['url'] . '/warninglists/add', $file, $request); + $statusWrapped = sprintf( + '<%s>%s', + $response->isOk() ? 'info' : 'error', + $response->isOk() ? 'OK' : 'Could not create warninglist', + $response->isOk() ? 'info' : 'error' + ); + $this->out($server['Server']['url'] . ': ' . $statusWrapped, 1, Shell::NORMAL); + } + } + + public function scores() + { + $results = []; + $this->__getInstances($this->param('instances')); + $server = null; + if (!empty($this->param['server_url'])) { + foreach ($this->__servers as $temp_server) { + if ($temp_server['Server']['url'] === $this->param['server_url']) { + $server = $temp_server; + } + } + } else { + $server = $this->__servers[0]; + } + $HttpSocket = $this->Server->setupHttpSocket($server, null); + $request = $this->Server->setupSyncRequest($server); + $response = $HttpSocket->get($server['Server']['url'] . '/organisations/index', false, $request); + $orgs = json_decode($response->body(), true); + $this->out(__('Organisations fetched. %d found.', count($orgs)), 1, Shell::VERBOSE); + $org_mapping = []; + foreach ($orgs as $org) { + $name = explode(' ', $org['Organisation']['name']); + if ($name[0] !== 'BT') { + continue; + } + $org_mapping[$org['Organisation']['name']] = $org['Organisation']['id']; + } + foreach ($org_mapping as $org_name => $org_id) { + $time_range = []; + if (!empty($this->param['from'])) { + $time_range[] = $this->param['from']; + } + if (!empty($this->param['to'])) { + if (empty($time_range)) { + $time_range[] = '365d'; + } + $time_range[] = $this->param['to']; + } + $params = [ + 'org' => $org_id + ]; + if (!empty($time_range)) { + $params['publish_timestamp'] = $time_range; + } + $response = $HttpSocket->post($server['Server']['url'] . '/events/restSearch', json_encode($params), $request); + $events = json_decode($response->body(), true); + $this->out(__('Events fetched from %s. %d found.', $org_name, count($events['response'])), 1, Shell::VERBOSE); + $results[$org_name] = [ + 'attribute_count' => 0, + 'object_count' => 0, + 'connected_elements' => 0, + 'event_tags' => 0, + 'attribute_tags' => 0, + 'attack' => 0, + 'other' => 0, + 'attribute_attack' => 0, + 'attribute_other' => 0, + 'score' => 0 + ]; + foreach ($events['response'] as $event) { + if (!empty($event['Event']['Tag'])) { + foreach ($event['Event']['Tag'] as $tag) { + if (substr($tag['name'], 0, 32) === 'misp-galaxy:mitre-attack-pattern') { + $results[$org_name]['attack'] += 1; + } else { + $results[$org_name]['other'] += 1; + } + } + } + if (!empty($event['Event']['Galaxy'])) { + foreach ($event['Event']['Galaxy'] as $galaxy) { + if ($galaxy['type'] === 'mitre-attack-pattern') { + $results[$org_name]['attack'] += 1; + } else { + $results[$org_name]['other'] += 1; + } + } + } + foreach ($event['Event']['Attribute'] as $attribute) { + if (!empty($attribute['referenced_by'])) { + $results[$org_name]['connected_elements'] +=1; + } + if (!empty($attribute['Tag'])) { + foreach ($attribute['Tag'] as $tag) { + if (substr($tag['name'], 0, 32) === 'misp-galaxy:mitre-attack-pattern') { + $results[$org_name]['attribute_attack'] += 1; + } else { + $results[$org_name]['attribute_other'] += 1; + } + } + } + } + $results[$org_name]['attribute_count'] += count($event['Event']['Attribute']); + if (!empty($event['Event']['Object'])) { + foreach ($event['Event']['Object'] as $object) { + $results[$org_name]['attribute_count'] += count($object['Attribute']); + $results[$org_name]['object_count'] += 1; + if (!empty($object['ObjectReference'])) { + $results[$org_name]['connected_elements'] += 1; + } + foreach ($object['Attribute'] as $attribute) { + if (!empty($attribute['Tag'])) { + foreach ($attribute['Tag'] as $tag) { + if (substr($tag['name'], 0, 32) === 'misp-galaxy:mitre-attack-pattern') { + $results[$org_name]['attribute_attack'] += 1; + } else { + $results[$org_name]['attribute_other'] += 1; + } + } + } + } + } + } + + } + } + $scores = []; + foreach ($results as $k => $result) { + $totalCount = $result['attribute_count'] + $result['object_count']; + if ($totalCount) { + $results[$k]['metrics']['connectedness'] = 100 * ($result['connected_elements'] / ($result['attribute_count'] + $result['object_count'])); + $results[$k]['metrics']['attack_weight'] = 100 * (2*($result['attack']) + $result['attribute_attack']) / ($result['attribute_count'] + $result['object_count']); + $results[$k]['metrics']['other_weight'] = 100 * (2*($result['other']) + $result['attribute_other']) / ($result['attribute_count'] + $result['object_count']); + } + foreach (['connectedness', 'attack_weight', 'other_weight'] as $metric) { + if (empty($results[$k]['metrics'][$metric])) { + $results[$k]['metrics'][$metric] = 0; + } + if ($results[$k]['metrics'][$metric] > 100) { + $results[$k]['metrics'][$metric] = 100; + } + } + $results[$k]['score'] = round(40 * $results[$k]['metrics']['connectedness'] + 40 * $results[$k]['metrics']['attack_weight'] + 20 * $results[$k]['metrics']['other_weight']) / 100; + $scores[$k] = $results[$k]['score']; + } + arsort($scores, SORT_DESC); + $this->out(str_repeat('=', 128), 1, Shell::NORMAL); + $this->out(sprintf( + '| %s | %s | %s |', + str_pad('Org', 10, ' ', STR_PAD_RIGHT), + str_pad('Graph', 100, ' ', STR_PAD_RIGHT), + str_pad('Score', 8, ' ', STR_PAD_RIGHT) + ), 1, Shell::NORMAL); + $this->out(str_repeat('=', 128), 1, Shell::NORMAL); + foreach ($scores as $org => $score) { + $score_string = str_repeat('█', round($score)); + $this->out(sprintf( + '| %s | %s | %s |', + str_pad($org, 10, ' ', STR_PAD_RIGHT), + sprintf( + '%s%s', + $score_string, + str_repeat(' ', 100 - mb_strlen($score_string)) + ), + str_pad($score . '%', 8, ' ', STR_PAD_RIGHT) + ), 1, Shell::NORMAL); + } + $this->out(str_repeat('=', 128), 1, Shell::NORMAL); + } +} diff --git a/app/Console/Command/SupportShell.php b/app/Console/Command/SupportShell.php index 46693db05..30eee1738 100644 --- a/app/Console/Command/SupportShell.php +++ b/app/Console/Command/SupportShell.php @@ -1,5 +1,3 @@ -http://download.geonames.org/export/dump/countryInfo.txt - Security->unlockedActions[] = 'getApiInfo'; + } + + public function openapi() + { + $this->set('title_for_layout', __('OpenAPI')); + } + + public function viewDeprecatedFunctionUse() + { + $server = ClassRegistry::init('Server'); + $data = $this->Deprecation->getDeprecatedAccessList($server); + if ($this->_isRest()) { + return $this->RestResponse->viewData($data, $this->response->type()); + } else { + $this->layout = false; + $this->set('data', $data); + } + } + + public function getAllApis() + { + $allValidApis = $this->RestResponse->getAllApis($this->Auth->user()); + $allValidApisFieldsConstraint = $this->RestResponse->getAllApisFieldsConstraint($this->Auth->user()); + $output = [ + 'allValidApis' => $allValidApis, + 'fieldsConstraint' => $allValidApisFieldsConstraint, + ]; + return $this->RestResponse->viewData($output, 'json'); + } + + public function getApiInfo() + { + $relative_path = $this->request->data['url']; + $result = $this->RestResponse->getApiInfo($relative_path); + if ($this->_isRest()) { + if (!empty($result)) { + $result['api_info'] = $result; + } + return $this->RestResponse->viewData($result, $this->response->type()); + } else { + if (empty($result)) { + return $this->RestResponse->viewData(' ', $this->response->type()); + } + $this->layout = false; + $this->autoRender = false; + $this->set('api_info', $result); + $this->render('ajax/get_api_info'); + } + } + + public function rest() + { + if ($this->request->is('post')) { + $request = $this->request->data; + if (!empty($request['Server'])) { + $request = $this->request->data['Server']; + } + $curl = ''; + $python = ''; + try { + $result = $this->__doRestQuery($request, $curl, $python); + $this->set('curl', $curl); + $this->set('python', $python); + if (!$result) { + $this->Flash->error('Something went wrong. Make sure you set the http method, body (when sending POST requests) and URL correctly.'); + } else { + $this->set('data', $result); + } + } catch (Exception $e) { + $this->Flash->error(__('Something went wrong. %s', $e->getMessage())); + } + } + $header = sprintf( + "Authorization: %s \nAccept: application/json\nContent-type: application/json", + __('YOUR_API_KEY') + ); + $this->set('header', $header); + + $allAccessibleApis = $this->RestResponse->getAccessibleApis($this->Auth->user()); + $this->set('allAccessibleApis', $allAccessibleApis); + $this->set('title_for_layout', __('REST client')); + } + + /** + * @param array $request + * @param string $curl + * @param string $python + * @return array|false + */ + private function __doRestQuery(array $request, &$curl = false, &$python = false) + { + $params = array(); + + $logHeaders = $request['header']; + if (!empty(Configure::read('Security.advanced_authkeys'))) { + $logHeaders = explode("\n", $request['header']); + foreach ($logHeaders as $k => $header) { + if (strpos($header, 'Authorization') !== false) { + $logHeaders[$k] = 'Authorization: ' . __('YOUR_API_KEY'); + } + } + $logHeaders = implode("\n", $logHeaders); + } + + if (empty($request['body'])) { + $historyBody = ''; + } else if (strlen($request['body']) > 65535) { + $historyBody = ''; // body is too long to save into history table + } else { + $historyBody = $request['body']; + } + + $rest_history_item = array( + 'headers' => $logHeaders, + 'body' => $historyBody, + 'url' => $request['url'], + 'http_method' => $request['method'], + 'use_full_path' => empty($request['use_full_path']) ? false : $request['use_full_path'], + 'show_result' => $request['show_result'], + 'skip_ssl' => $request['skip_ssl_validation'], + 'bookmark' => $request['bookmark'], + 'bookmark_name' => $request['name'], + 'timestamp' => time(), + ); + if (!empty($request['url'])) { + if (empty($request['use_full_path']) || empty(Configure::read('Security.rest_client_enable_arbitrary_urls'))) { + $path = preg_replace('#^(://|[^/?])+#', '', $request['url']); + $url = empty(Configure::read('Security.rest_client_baseurl')) ? (Configure::read('MISP.baseurl') . $path) : (Configure::read('Security.rest_client_baseurl') . $path); + unset($request['url']); + } else { + $url = $request['url']; + } + } else { + throw new InvalidArgumentException('URL not set.'); + } + if (!empty($request['skip_ssl_validation'])) { + $params['ssl_verify_peer'] = false; + $params['ssl_verify_host'] = false; + $params['ssl_verify_peer_name'] = false; + $params['ssl_allow_self_signed'] = true; + } + $params['timeout'] = 300; + App::uses('HttpSocketExtended', 'Tools'); + $HttpSocket = new HttpSocketExtended($params); + + $temp_headers = empty($request['header']) ? [] : explode("\n", $request['header']); + $request['header'] = array( + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'User-Agent' => 'MISP REST Client', + ); + foreach ($temp_headers as $header) { + $header = explode(':', $header); + $header[0] = trim($header[0]); + $header[1] = trim($header[1]); + $request['header'][$header[0]] = $header[1]; + } + $start = microtime(true); + if ( + !empty($request['method']) && + $request['method'] === 'GET' + ) { + if ($curl !== false) { + $curl = $this->__generateCurlQuery('get', $request, $url); + } + if ($python !== false) { + $python = $this->__generatePythonScript($request, $url); + } + $response = $HttpSocket->get($url, false, array('header' => $request['header'])); + } elseif ( + !empty($request['method']) && + $request['method'] === 'POST' && + !empty($request['body']) + ) { + if ($curl !== false) { + $curl = $this->__generateCurlQuery('post', $request, $url); + } + if ($python !== false) { + $python = $this->__generatePythonScript($request, $url); + } + $response = $HttpSocket->post($url, $request['body'], array('header' => $request['header'])); + } elseif ( + !empty($request['method']) && + $request['method'] === 'DELETE' + ) { + if ($curl !== false) { + $curl = $this->__generateCurlQuery('delete', $request, $url); + } + if ($python !== false) { + $python = $this->__generatePythonScript($request, $url); + } + $response = $HttpSocket->delete($url, false, array('header' => $request['header'])); + } else { + return false; + } + $viewData = [ + 'duration' => round((microtime(true) - $start) * 1000, 2) . ' ms', + 'url' => $url, + 'code' => $response->code, + 'headers' => $response->headers, + ]; + + if (!empty($request['show_result'])) { + $viewData['data'] = $response->body; + } else { + if ($response->isOk()) { + $viewData['data'] = 'Success.'; + } else { + $viewData['data'] = 'Something went wrong.'; + } + } + $rest_history_item['outcome'] = $response->code; + + $this->loadModel('RestClientHistory'); + $this->RestClientHistory->insert($this->Auth->user(), $rest_history_item); + + return $viewData; + } + + private function __generatePythonScript(array $request, $url) + { + $slashCounter = 0; + $baseurl = ''; + $relative = ''; + $verifyCert = ($url[4] === 's') ? 'True' : 'False'; + for ($i = 0; $i < strlen($url); $i++) { + //foreach ($url as $url[$i]) { + if ($url[$i] === '/') { + $slashCounter += 1; + if ($slashCounter == 3) { + continue; + } + } + if ($slashCounter < 3) { + $baseurl .= $url[$i]; + } else { + $relative .= $url[$i]; + } + } + $python_script = + sprintf( + 'misp_url = \'%s\' +misp_key = \'%s\' +misp_verifycert = %s +relative_path = \'%s\' +body = %s + +from pymisp import ExpandedPyMISP + +misp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert) +misp.direct_call(relative_path, body) +', + $baseurl, + $request['header']['Authorization'], + $verifyCert, + $relative, + (empty($request['body']) ? 'None' : $request['body']) + ); + return $python_script; + } + + private function __generateCurlQuery($type, array $request, $url) + { + if ($type === 'get') { + $curl = sprintf( + 'curl \%s -H "Authorization: %s" \%s -H "Accept: %s" \%s -H "Content-type: %s" \%s %s', + PHP_EOL, + $request['header']['Authorization'], + PHP_EOL, + $request['header']['Accept'], + PHP_EOL, + $request['header']['Content-Type'], + PHP_EOL, + $url + ); + } else { + $curl = sprintf( + 'curl \%s -d \'%s\' \%s -H "Authorization: %s" \%s -H "Accept: %s" \%s -H "Content-type: %s" \%s -X POST %s', + PHP_EOL, + json_encode(json_decode($request['body'])), + PHP_EOL, + $request['header']['Authorization'], + PHP_EOL, + $request['header']['Accept'], + PHP_EOL, + $request['header']['Content-Type'], + PHP_EOL, + $url + ); + } + return $curl; + } +} diff --git a/app/Controller/AppController.php b/app/Controller/AppController.php index bdc05ac47..590c8c369 100755 --- a/app/Controller/AppController.php +++ b/app/Controller/AppController.php @@ -34,7 +34,7 @@ class AppController extends Controller public $helpers = array('OrgImg', 'FontAwesome', 'UserName'); - private $__queryVersion = '138'; + private $__queryVersion = '139'; public $pyMispVersion = '2.4.157'; public $phpmin = '7.2'; public $phprec = '7.4'; @@ -202,20 +202,7 @@ class AppController extends Controller if (empty($dataToDecode)) { return null; } - try { - if (defined('JSON_THROW_ON_ERROR')) { - // JSON_THROW_ON_ERROR is supported since PHP 7.3 - return json_decode($dataToDecode, true, 512, JSON_THROW_ON_ERROR); - } else { - $decoded = json_decode($dataToDecode, true); - if ($decoded === null) { - throw new UnexpectedValueException('Could not parse JSON: ' . json_last_error_msg(), json_last_error()); - } - return $decoded; - } - } catch (Exception $e) { - throw new HttpException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.', 405, $e); - } + return $this->_jsonDecode($dataToDecode); }; // Throw exception if JSON in request is invalid. Default CakePHP behaviour would just ignore that error. $this->RequestHandler->addInputType('json', [$jsonDecode]); @@ -376,7 +363,7 @@ class AppController extends Controller { // Notifications and homepage is not necessary for AJAX or REST requests $user = $this->Auth->user(); - if ($user && !$this->_isRest() && !$this->request->is('ajax')) { + if ($user && !$this->_isRest() && isset($this->User) && !$this->request->is('ajax')) { $hasNotifications = $this->User->hasNotifications($user); $this->set('hasNotifications', $hasNotifications); @@ -1450,4 +1437,27 @@ class AppController extends Controller } return parent::_getViewObject(); } + + /** + * Decode JSON with proper error handling. + * @param string $dataToDecode + * @return mixed + */ + protected function _jsonDecode($dataToDecode) + { + try { + if (defined('JSON_THROW_ON_ERROR')) { + // JSON_THROW_ON_ERROR is supported since PHP 7.3 + return json_decode($dataToDecode, true, 512, JSON_THROW_ON_ERROR); + } else { + $decoded = json_decode($dataToDecode, true); + if ($decoded === null) { + throw new UnexpectedValueException('Could not parse JSON: ' . json_last_error_msg(), json_last_error()); + } + return $decoded; + } + } catch (Exception $e) { + throw new HttpException('Invalid JSON input. Make sure that the JSON input is a correctly formatted JSON string. This request has been blocked to avoid an unfiltered request.', 405, $e); + } + } } diff --git a/app/Controller/AttributesController.php b/app/Controller/AttributesController.php index 485164f1f..1dd70cf9f 100644 --- a/app/Controller/AttributesController.php +++ b/app/Controller/AttributesController.php @@ -940,22 +940,14 @@ class AttributesController extends AppController if (!$changed) { return new CakeResponse(array('body'=> json_encode(array('errors'=> array('value' => 'nochange'))), 'status'=>200, 'type' => 'json')); } - $date = new DateTime(); - $attribute['Attribute']['timestamp'] = $date->getTimestamp(); + $time = time(); + $attribute['Attribute']['timestamp'] = $time; - $fieldsToSave = ['timestamp']; - if ($changedKey === 'value') { - $fieldsToSave[] = 'value1'; - $fieldsToSave[] = 'value2'; - } else { - $fieldsToSave[] = $changedKey; - } - - if ($this->Attribute->save($attribute, true, $fieldsToSave)) { - $this->Attribute->Event->unpublishEvent($attribute['Attribute']['event_id'], false, $date->getTimestamp()); + if ($this->Attribute->save($attribute)) { + $this->Attribute->Event->unpublishEvent($attribute['Attribute']['event_id'], false, $time); if ($attribute['Attribute']['object_id'] != 0) { - $this->Attribute->Object->updateTimestamp($attribute['Attribute']['object_id'], $date->getTimestamp()); + $this->Attribute->Object->updateTimestamp($attribute['Attribute']['object_id'], $time); } return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'success' => 'Field updated.', 'check_publish' => true)), 'status'=>200, 'type' => 'json')); } else { @@ -1982,12 +1974,6 @@ class AttributesController extends AppController $result = $this->Attribute->shortDist[$result]; } elseif ($field === 'to_ids') { $result = ($result == 0 ? 'No' : 'Yes'); - } elseif ($field === 'timestamp') { - if (isset($result)) { - $result = date('Y-m-d', $result); - } else { - echo ' '; - } } elseif ($field === 'value') { $this->loadModel('Warninglist'); $attribute['Attribute'] = $this->Warninglist->checkForWarning($attribute['Attribute']); diff --git a/app/Controller/AuthKeysController.php b/app/Controller/AuthKeysController.php index 461c5b9c5..dbcb0ed91 100644 --- a/app/Controller/AuthKeysController.php +++ b/app/Controller/AuthKeysController.php @@ -141,10 +141,10 @@ class AuthKeysController extends AppController } $params = [ 'displayOnSuccess' => 'authkey_display', - 'saveModelVariable' => ['authkey_raw'], 'override' => ['authkey' => null], // do not allow to use own key, always generate random one - 'afterFind' => function ($authKey) { // remove hashed key from response + 'afterFind' => function (array $authKey, array $savedData) { // remove hashed key from response unset($authKey['AuthKey']['authkey']); + $authKey['AuthKey']['authkey_raw'] = $savedData['AuthKey']['authkey_raw']; return $authKey; } ]; @@ -195,11 +195,10 @@ class AuthKeysController extends AppController if ($this->IndexFilter->isRest()) { return $this->restResponsePayload; } - $this->loadModel('User'); $dropdownData = [ - 'user' => $this->User->find('list', [ + 'user' => $this->AuthKey->User->find('list', [ 'sort' => ['username' => 'asc'], - 'conditions' => $selectConditions + 'conditions' => $selectConditions, ]) ]; $this->set(compact('dropdownData')); diff --git a/app/Controller/Component/ACLComponent.php b/app/Controller/Component/ACLComponent.php index 378edbbda..9d215cf53 100644 --- a/app/Controller/Component/ACLComponent.php +++ b/app/Controller/Component/ACLComponent.php @@ -19,6 +19,13 @@ class ACLComponent extends Component 'queryACL' => array(), 'restSearch' => array('*'), ), + 'api' => [ + 'rest' => ['perm_auth'], + 'viewDeprecatedFunctionUse' => [], + 'openapi' => ['*'], + 'getApiInfo' => ['*'], + 'getAllApis' => ['*'], + ], 'attributes' => array( 'add' => array('perm_add'), 'add_attachment' => array('perm_add'), @@ -505,7 +512,6 @@ class ACLComponent extends Component 'eventBlockRule' => array(), 'fetchServersForSG' => array('perm_sharing_group'), 'filterEventIndex' => array(), - 'getApiInfo' => array('*'), 'getAvailableSyncFilteringRules' => array('*'), 'getInstanceUUID' => array('perm_sync'), 'getPyMISPVersion' => array('*'), @@ -531,8 +537,6 @@ class ACLComponent extends Component 'releaseUpdateLock' => array(), 'resetRemoteAuthKey' => array(), 'removeOrphanedCorrelations' => array(), - 'rest' => array('perm_auth'), - 'openapi' => array('*'), 'restartDeadWorkers' => array(), 'restartWorkers' => array(), 'serverSettings' => array(), @@ -549,7 +553,6 @@ class ACLComponent extends Component 'updateProgress' => array(), 'updateSubmodule' => array(), 'uploadFile' => array(), - 'viewDeprecatedFunctionUse' => array(), 'killAllWorkers' => [], 'cspReport' => ['*'], 'pruneDuplicateUUIDs' => array(), @@ -557,6 +560,7 @@ class ACLComponent extends Component 'upgrade2324' => array(), 'cleanModelCaches' => array(), 'updateDatabase' => array(), + 'rest' => ['perm_auth'], ), 'shadowAttributes' => array( 'accept' => array('perm_add'), diff --git a/app/Controller/Component/CRUDComponent.php b/app/Controller/Component/CRUDComponent.php index 7b9697b7c..fb276e34d 100644 --- a/app/Controller/Component/CRUDComponent.php +++ b/app/Controller/Component/CRUDComponent.php @@ -90,7 +90,8 @@ class CRUDComponent extends Component } /** @var Model $model */ $model = $this->Controller->{$modelName}; - if ($model->save($data)) { + $savedData = $model->save($data); + if ($savedData) { if (isset($params['afterSave'])) { $params['afterSave']($data); } @@ -100,15 +101,11 @@ class CRUDComponent extends Component 'id' => $model->id ] ]); - if (!empty($params['saveModelVariable'])) { - foreach ($params['saveModelVariable'] as $var) { - if (isset($model->$var)) { - $data[$modelName][$var] = $model->$var; - } - } + if (empty($data)) { + throw new Exception("Something went wrong, saved data not found in database."); } if (isset($params['afterFind'])) { - $data = $params['afterFind']($data); + $data = $params['afterFind']($data, $savedData); } $message = __('%s added.', $modelName); if ($this->Controller->IndexFilter->isRest()) { diff --git a/app/Controller/Component/RestResponseComponent.php b/app/Controller/Component/RestResponseComponent.php index 33bf6e28d..1dfa62cb8 100644 --- a/app/Controller/Component/RestResponseComponent.php +++ b/app/Controller/Component/RestResponseComponent.php @@ -48,7 +48,7 @@ class RestResponseComponent extends Component 'restSearch' => array( 'description' => "Search MISP using a list of filter parameters and return the data in the selected format. The search is available on an event and an attribute level, just select the scope via the URL (/events/restSearch vs /attributes/restSearch). Besides the parameters listed, other, format specific ones can be passed along (for example: requested_attributes and includeContext for the CSV export). This API allows pagination via the page and limit parameters.", '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'), + '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', 'sharinggroup', 'includeProposals', 'includeDecayScore', 'includeFullModel', 'decayingModel', 'excludeDecayed', 'score', 'first_seen', 'last_seen'), 'params' => array() ), 'addTag' => array( @@ -88,7 +88,7 @@ class RestResponseComponent extends Component 'restSearch' => array( 'description' => "Search MISP using a list of filter parameters and return the data in the selected format. The search is available on an event and an attribute level, just select the scope via the URL (/events/restSearch vs /attributes/restSearch). Besides the parameters listed, other, format specific ones can be passed along (for example: requested_attributes and includeContext for the CSV export). This API allows pagination via the page and limit parameters.", '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'), + 'optional' => array('page', 'limit', 'value', 'type', 'category', 'org', 'tag', 'tags', 'searchall', 'date', 'last', 'eventid', 'withAttachments', 'metadata', 'uuid', 'published', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'sgReferenceOnly', 'eventinfo', 'sharinggroup', 'excludeLocalTags', 'threat_level_id'), 'params' => array() ), 'addTag' => array( @@ -367,12 +367,7 @@ class RestResponseComponent extends Component $controller = $controller === 'EventGraph' ? 'event_graph' : Inflector::tableize($controller); foreach ($actions as $action => $data) { if ($this->ACL->canUserAccess($user, $controller, $action)) { - $admin_routing = ''; - if (substr($action, 0, 6) === 'admin_') { - $action = substr($action, 6); - $admin_routing = 'admin/'; - } - $url = $this->baseurl . '/' . $admin_routing . $controller . '/' . $action; + $url = $this->generateUrl($controller, $action); $result[$url] = $data; } } @@ -380,6 +375,25 @@ class RestResponseComponent extends Component return $result; } + /** + * @param array $user + * @return array + */ + public function getAccessibleApis(array $user) + { + $output = []; + foreach ($this->__descriptions as $controller => $actions) { + $controller = $controller === 'EventGraph' ? 'event_graph' : Inflector::tableize($controller); + foreach ($actions as $action => $data) { + if ($this->ACL->canUserAccess($user, $controller, $action)) { + $url = $this->generateUrl($controller, $action); + $output[$controller][$action] = $url; + } + } + } + return $output; + } + public function getAllApis($user) { $this->__setup(); @@ -389,15 +403,10 @@ class RestResponseComponent extends Component $controller = $controller === 'EventGraph' ? 'event_graph' : Inflector::tableize($controller); foreach ($actions as $action => $data) { if ($this->ACL->canUserAccess($user, $controller, $action)) { - $admin_routing = ''; - if (substr($action, 0, 6) === 'admin_') { - $action = substr($action, 6); - $admin_routing = 'admin/'; - } $data['api_name'] = '[' . $controller . '] ' . $action; $data['controller'] = $controller; $data['action'] = $action; - $data['body'] = array(); + $body = []; $filter_types = array('mandatory', 'optional'); foreach ($filter_types as $filter_type) { if (!empty($data[$filter_type])) { @@ -407,16 +416,16 @@ class RestResponseComponent extends Component } foreach ($filter_items as $filter) { if ($filter === lcfirst($filter)) { - $data['body'][$filter] = $filter_type; + $body[$filter] = $filter_type; } else { - $data['body'][$filter] = array($filter_type); + $body[$filter] = array($filter_type); } } } } } - $data['body'] = json_encode($data['body'], JSON_PRETTY_PRINT); - $url = $this->baseurl . '/' . $admin_routing . $controller . '/' . $action; + $data['body'] = $body; + $url = $this->generateUrl($controller, $action);; $data['url'] = $url; if (!empty($data['params'])) { foreach ($data['params'] as $param) { @@ -592,14 +601,14 @@ class RestResponseComponent extends Component } if ($response instanceof TmpFileTool) { - App::uses('CakeResponseFile', 'Tools'); - $cakeResponse = new CakeResponseFile(['status' => $code, 'type' => $type]); if ($this->signContents) { $this->CryptographicKey = ClassRegistry::init('CryptographicKey'); $data = $response->intoString(); $headers['x-pgp-signature'] = base64_encode($this->CryptographicKey->signWithInstanceKey($data)); - $cakeResponse = new CakeResponse(array('body' => $data, 'status' => $code, 'type' => $type)); + $cakeResponse = new CakeResponse(['body' => $data, 'status' => $code, 'type' => $type]); } else { + App::uses('CakeResponseFile', 'Tools'); + $cakeResponse = new CakeResponseFile(['status' => $code, 'type' => $type]); $cakeResponse->file($response); } } else { @@ -731,13 +740,8 @@ class RestResponseComponent extends Component $scopes = array('Event', 'Attribute', 'Sighting'); foreach ($scopes as $scope) { $this->{$scope} = ClassRegistry::init($scope); - $this->__descriptions[$scope]['restSearch'] = array( - 'description' => $this->__descriptions[$scope]['restSearch']['description'], - 'returnFormat' => array_keys($this->{$scope}->validFormats), - 'mandatory' => $this->__descriptions[$scope]['restSearch']['mandatory'], - 'optional' => $this->__descriptions[$scope]['restSearch']['optional'], - 'params' => $this->__descriptions[$scope]['restSearch']['params'] - ); + $returnFormat = array_keys($this->{$scope}->validFormats); + $this->__descriptions[$scope]['restSearch']['returnFormat'] = $returnFormat; } $this->__configureFieldConstraints(); $this->__setupFieldsConstraint(); @@ -1938,7 +1942,8 @@ class RestResponseComponent extends Component if ($values === null) { $tagModel = ClassRegistry::init("Tag"); $tags = $tagModel->find('column', array( - 'fields' => array('Tag.name') + 'fields' => array('Tag.name'), + 'callbacks' => false, )); $values = []; foreach ($tags as $tag) { @@ -1963,19 +1968,40 @@ class RestResponseComponent extends Component private function __overwriteAction($scope, $action, &$field) { $field['values'] = array_keys(ClassRegistry::init("Log")->actionDefinitions); } - private function __overwriteRoleId($scope, $action, &$field) { - $this->{$scope} = ClassRegistry::init("Role"); - $roles = $this->{$scope}->find('list', array( - 'fields' => array('id', 'name') - )); - $field['values'] = []; - foreach ($roles as $id => $name) { - $field['values'][] = ['label' => $name, 'value' => $id]; + + private function __overwriteRoleId($scope, $action, &$field) + { + static $values; + if ($values === null) { + $roleModel = ClassRegistry::init("Role"); + $roles = $roleModel->find('list', array( + 'fields' => array('id', 'name') + )); + $values = []; + foreach ($roles as $id => $name) { + $values[] = ['label' => $name, 'value' => $id]; + } } + $field['values'] = $values; } private function __overwriteSeen($scope, $action, &$field) { if ($action == 'restSearch') { $field['help'] = __('Seen within the last x amount of time, where x can be defined in days, hours, minutes (for example 5d or 12h or 30m)'); } } + + /** + * @param string $controller + * @param string $action + * @return string + */ + private function generateUrl($controller, $action) + { + $admin_routing = ''; + if (substr($action, 0, 6) === 'admin_') { + $action = substr($action, 6); + $admin_routing = 'admin/'; + } + return '/' . $admin_routing . $controller . '/' . $action; + } } diff --git a/app/Controller/EventsController.php b/app/Controller/EventsController.php index 773f8a10c..8f12bfe03 100644 --- a/app/Controller/EventsController.php +++ b/app/Controller/EventsController.php @@ -1109,6 +1109,7 @@ class EventsController extends AppController 'eventid' => array('OR' => array(), 'NOT' => array()), 'date' => array('from' => "", 'until' => ""), 'eventinfo' => array('OR' => array(), 'NOT' => array()), + 'all' => array('OR' => array(), 'NOT' => array()), 'threatlevel' => array('OR' => array(), 'NOT' => array()), 'distribution' => array('OR' => array(), 'NOT' => array()), 'sharinggroup' => array('OR' => array(), 'NOT' => array()), @@ -1187,6 +1188,7 @@ class EventsController extends AppController 'hasproposal' => __('Has proposal'), 'timestamp' => __('Last change at'), 'publishtimestamp' => __('Published at'), + 'all' => __('Search in all fields'), ]; if ($this->_isSiteAdmin()) { @@ -1402,6 +1404,8 @@ class EventsController extends AppController $advancedFiltering = $this->__checkIfAdvancedFiltering($filters); $this->set('advancedFilteringActive', $advancedFiltering['active'] ? 1 : 0); $this->set('advancedFilteringActiveRules', $advancedFiltering['activeRules']); + $this->set('mayModify', $this->__canModifyEvent($event)); + $this->set('mayPublish', $this->__canPublishEvent($event)); $this->response->disableCache(); // Remove `focus` attribute from URI @@ -3211,7 +3215,8 @@ class EventsController extends AppController 'order' => 'Event.timestamp DESC', )); $this->loadModel('Job'); - foreach ($this->Event->export_types as $k => $type) { + $exportTypes = $this->Event->exportTypes(); + foreach ($exportTypes as $k => $type) { if ($type['requiresPublished']) { $tempNewestEvent = $newestEventPublished; } else { @@ -3235,10 +3240,10 @@ class EventsController extends AppController if (!$file->readable()) { if (empty($tempNewestEvent)) { $lastModified = 'No valid events'; - $this->Event->export_types[$k]['recommendation'] = 0; + $exportTypes[$k]['recommendation'] = 0; } else { $lastModified = 'N/A'; - $this->Event->export_types[$k]['recommendation'] = 1; + $exportTypes[$k]['recommendation'] = 1; } } else { $filesize = $file->size(); @@ -3247,32 +3252,31 @@ class EventsController extends AppController $filesize_unit_index++; $filesize = $filesize / 1024; } - $this->Event->export_types[$k]['filesize'] = round($filesize, 1) . $filesize_units[$filesize_unit_index]; + $exportTypes[$k]['filesize'] = round($filesize, 1) . $filesize_units[$filesize_unit_index]; $fileChange = $file->lastChange(); $lastModified = $this->__timeDifference($now, $fileChange); if (empty($tempNewestEvent) || $fileChange > $tempNewestEvent['Event']['timestamp']) { if (empty($tempNewestEvent)) { $lastModified = 'No valid events'; } - $this->Event->export_types[$k]['recommendation'] = 0; + $exportTypes[$k]['recommendation'] = 0; } else { - $this->Event->export_types[$k]['recommendation'] = 1; + $exportTypes[$k]['recommendation'] = 1; } } - $this->Event->export_types[$k]['lastModified'] = $lastModified; + $exportTypes[$k]['lastModified'] = $lastModified; if (!empty($job)) { - $this->Event->export_types[$k]['job_id'] = $job['Job']['id']; - $this->Event->export_types[$k]['progress'] = $job['Job']['progress']; + $exportTypes[$k]['job_id'] = $job['Job']['id']; + $exportTypes[$k]['progress'] = $job['Job']['progress']; } else { - $this->Event->export_types[$k]['job_id'] = -1; - $this->Event->export_types[$k]['progress'] = 0; + $exportTypes[$k]['job_id'] = -1; + $exportTypes[$k]['progress'] = 0; } } } - $this->loadModel('Attribute'); - $this->set('sigTypes', array_keys($this->Attribute->typeDefinitions)); - $this->set('export_types', $this->Event->export_types); + $this->set('sigTypes', array_keys($this->Event->Attribute->typeDefinitions)); + $this->set('export_types', $exportTypes); } public function downloadExport($type, $extra = null) @@ -3289,8 +3293,9 @@ class EventsController extends AppController if ($extra != null) { $extra = '_' . $extra; } - $this->response->type($this->Event->export_types[$type]['extension']); - $path = 'tmp/cached_exports/' . $type . DS . 'misp.' . strtolower($this->Event->export_types[$type]['type']) . $extra . '.' . $org . $this->Event->export_types[$type]['extension']; + $exportType = $this->Event->exportTypes()[$type]; + $this->response->type($exportType['extension']); + $path = 'tmp/cached_exports/' . $type . DS . 'misp.' . strtolower($exportType['type']) . $extra . '.' . $org . $exportType['extension']; $this->response->file($path, array('download' => true)); } @@ -3920,17 +3925,18 @@ class EventsController extends AppController */ public function freeTextImport($id, $adhereToWarninglists = false, $returnMetaAttributes = false) { + $this->request->allowMethod(['post', 'get']); + $event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id); if (empty($event)) { - throw new MethodNotAllowedException(__('Invalid event.')); + throw new NotFoundException(__('Invalid event.')); } $this->set('event_id', $event['Event']['id']); if ($this->request->is('get')) { $this->layout = 'ajax'; $this->request->data['Attribute']['event_id'] = $event['Event']['id']; - } - if ($this->request->is('post')) { + } else if ($this->request->is('post')) { App::uses('ComplexTypeTool', 'Tools'); $complexTypeTool = new ComplexTypeTool(); $this->loadModel('Warninglist'); @@ -3944,16 +3950,16 @@ class EventsController extends AppController if (isset($this->request->data['Attribute']['adhereToWarninglists'])) { $adhereToWarninglists = $this->request->data['Attribute']['adhereToWarninglists']; } - $resultArray = $complexTypeTool->checkComplexRouter($this->request->data['Attribute']['value'], 'freetext'); - foreach ($resultArray as $key => $r) { - $temp = array(); - foreach ($r['types'] as $type) { - $temp[$type] = $type; - } - $resultArray[$key]['types'] = $temp; - } - + $resultArray = $complexTypeTool->checkFreeText($this->request->data['Attribute']['value']); if ($this->_isRest()) { + // Keep this 'types' format for rest response, but it is not necessary for UI + foreach ($resultArray as $key => $r) { + $temp = array(); + foreach ($r['types'] as $type) { + $temp[$type] = $type; + } + $resultArray[$key]['types'] = $temp; + } if ($returnMetaAttributes || !empty($this->request->data['Attribute']['returnMetaAttributes'])) { return $this->RestResponse->viewData($resultArray, $this->response->type()); } else { @@ -3967,7 +3973,6 @@ class EventsController extends AppController } } $this->Event->Attribute->fetchRelated($this->Auth->user(), $resultArray); - $resultArray = array_values($resultArray); $typeCategoryMapping = array(); foreach ($this->Event->Attribute->categoryDefinitions as $k => $cat) { foreach ($cat['types'] as $type) { @@ -3987,15 +3992,10 @@ class EventsController extends AppController $this->set('mayModify', $this->__canModifyEvent($event)); $this->set('typeDefinitions', $this->Event->Attribute->typeDefinitions); $this->set('typeCategoryMapping', $typeCategoryMapping); - foreach ($typeCategoryMapping as $k => $v) { - $typeCategoryMapping[$k] = array_values($v); - } - $this->set('mapping', $typeCategoryMapping); $this->set('resultArray', $resultArray); $this->set('importComment', ''); $this->set('title_for_layout', __('Freetext Import Results')); $this->set('title', __('Freetext Import Results')); - $this->loadModel('Warninglist'); $this->set('missingTldLists', $this->Warninglist->missingTldLists()); $this->render('resolved_attributes'); } @@ -4038,19 +4038,17 @@ class EventsController extends AppController public function saveFreeText($id) { - if (!$this->request->is('post')) { - throw new MethodNotAllowedException('This endpoint requires a POST request.'); - } + $this->request->allowMethod(['post']); $event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id); if (!$event) { throw new NotFoundException(__('Invalid event.')); } $this->Event->insertLock($this->Auth->user(), $id); - $attributes = json_decode($this->request->data['Attribute']['JsonObject'], true); - $default_comment = $this->request->data['Attribute']['default_comment']; + $attributes = $this->_jsonDecode($this->request->data['Attribute']['JsonObject']); + $defaultComment = $this->request->data['Attribute']['default_comment']; $proposals = !$this->__canModifyEvent($event) || (isset($this->request->data['Attribute']['force']) && $this->request->data['Attribute']['force']); - $flashMessage = $this->Event->processFreeTextDataRouter($this->Auth->user(), $attributes, $id, $default_comment, $proposals); + $flashMessage = $this->Event->processFreeTextDataRouter($this->Auth->user(), $attributes, $id, $defaultComment, $proposals); $this->Flash->info($flashMessage); if ($this->request->is('ajax')) { @@ -6139,7 +6137,7 @@ class EventsController extends AppController /** * @param array $event - * @return CakeResponseTmp + * @return CakeResponseFile * @throws Exception */ private function __restResponse(array $event) @@ -6171,23 +6169,24 @@ class EventsController extends AppController public function protect($id) { - $this->__toggleProtect($id, true); + return $this->__toggleProtect($id, true); } public function unprotect($id) { - $this->__toggleProtect($id, false); + return $this->__toggleProtect($id, false); } + /** + * @param string|int $id Event ID or UUID + * @param bool $protect + * @return CakeResponse|void + * @throws Exception + */ private function __toggleProtect($id, $protect) { - $id = $this->Toolbox->findIdByUuid($this->Event, $id); - $event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id, ['contain' => ['Orgc']]); - if ( - (!$this->_isSiteAdmin && $event['Event']['orgc_id'] !== $this->Auth->user('org_id')) || - !$event || - !$this->__canModifyEvent($event) - ) { + $event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id); + if (empty($event) || !$this->__canModifyEvent($event)) { throw new NotFoundException(__('Invalid event')); } if ($this->request->is('post')) { @@ -6197,7 +6196,7 @@ class EventsController extends AppController if ($this->Event->save($event)) { $message = __('Event switched to %s mode.', $protect ? __('protected') : __('unprotected')); if ($this->_isRest()) { - return $this->RestResponse->saveSuccessResponse('events', $protect ? 'protect' : 'unprotect', $id, false, $message); + return $this->RestResponse->saveSuccessResponse('events', $protect ? 'protect' : 'unprotect', $event['Event']['id'], false, $message); } else { $this->Flash->success($message); $this->redirect(['controller' => 'events', 'action' => 'view', $id]); @@ -6205,14 +6204,14 @@ class EventsController extends AppController } else { $message = __('Something went wrong - could not switch event to %s mode.', $protect ? __('protected') : __('unprotected')); if ($this->_isRest()) { - return $this->RestResponse->saveFailResponse('Events', $protect ? 'protect' : 'unprotect', false, $message, $this->response->type()); + return $this->RestResponse->saveFailResponse('Events', $protect ? 'protect' : 'unprotect', $event['Event']['id'], $message); } else { $this->Flash->error($message); - $this->redirect(['controller' => 'events', 'action' => 'view', $id]); + $this->redirect(['controller' => 'events', 'action' => 'view', $event['Event']['id']]); } } } else { - $this->set('id', $id); + $this->set('id', $event['Event']['id']); $this->set('title', $protect ? __('Protect event') : __('Remove event protection')); $this->set( 'question', diff --git a/app/Controller/FeedsController.php b/app/Controller/FeedsController.php index 7d0fb45bd..ff30f1280 100644 --- a/app/Controller/FeedsController.php +++ b/app/Controller/FeedsController.php @@ -298,6 +298,9 @@ class FeedsController extends AppController $tags = $this->Event->EventTag->Tag->find('list', array('fields' => array('Tag.name'), 'order' => array('lower(Tag.name) asc'))); $tags[0] = 'None'; + $this->loadModel('Server'); + $allTypes = $this->Server->getAllTypes(); + $dropdownData = [ 'orgs' => $this->Event->Orgc->find('list', array( 'fields' => array('id', 'name'), @@ -309,9 +312,12 @@ class FeedsController extends AppController 'distributionLevels' => $distributionLevels, 'inputSources' => $inputSources ]; + $this->set('allAttributeTypes', $allTypes['attribute']); + $this->set('allObjectTypes', $allTypes['object']); $this->set(compact('dropdownData')); $this->set('defaultPullRules', json_encode(Feed::DEFAULT_FEED_PULL_RULES)); $this->set('menuData', array('menuList' => 'feeds', 'menuItem' => 'add')); + $this->set('pull_scope', 'feed'); } private function __checkRegex($pattern) @@ -346,10 +352,14 @@ class FeedsController extends AppController 'delete_local_file', 'lookup_visible', 'headers', - 'orgc_id' + 'orgc_id', + 'fixed_event' ], 'afterFind' => function (array $feed) { $feed['Feed']['settings'] = json_decode($feed['Feed']['settings'], true); + if ($feed['Feed']['source_format'] == 'misp' && empty($feed['Feed']['rules'])) { + $feed['Feed']['rules'] = json_encode(Feed::DEFAULT_FEED_PULL_RULES); + } return $feed; }, @@ -437,6 +447,12 @@ class FeedsController extends AppController $tags = $this->Event->EventTag->Tag->find('list', array('fields' => array('Tag.name'), 'order' => array('lower(Tag.name) asc'))); $tags[0] = 'None'; + $this->loadModel('Server'); + $allTypes = $this->Server->getAllTypes(); + $this->set('allAttributeTypes', $allTypes['attribute']); + $this->set('allObjectTypes', $allTypes['object']); + $this->set('supportedUrlparams', Feed::SUPPORTED_URL_PARAM_FILTERS); + $dropdownData = [ 'orgs' => $this->Event->Orgc->find('list', array( 'fields' => array('id', 'name'), @@ -458,6 +474,7 @@ class FeedsController extends AppController if(!empty($this->request->data['Feed']['rules'])){ $this->request->data['Feed']['pull_rules'] = $this->request->data['Feed']['rules']; } + $this->set('pull_scope', 'feed'); $this->render('add'); } diff --git a/app/Controller/ObjectsController.php b/app/Controller/ObjectsController.php index dd8e789ec..7a3409b9c 100644 --- a/app/Controller/ObjectsController.php +++ b/app/Controller/ObjectsController.php @@ -573,21 +573,21 @@ class ObjectsController extends AppController public function fetchViewValue($id, $field = null) { $validFields = array('timestamp', 'comment', 'distribution', 'first_seen', 'last_seen'); - if (!isset($field) || !in_array($field, $validFields)) { + if (!isset($field) || !in_array($field, $validFields, true)) { throw new MethodNotAllowedException('Invalid field requested.'); } if (!$this->request->is('ajax')) { throw new MethodNotAllowedException('This function can only be accessed via AJAX.'); } $params = array( - 'conditions' => array('Object.id' => $id), - 'fields' => array('id', 'distribution', 'event_id', $field), - 'contain' => array( - 'Event' => array( - 'fields' => array('distribution', 'id', 'org_id'), - ) - ), - 'flatten' => 1 + 'conditions' => array('Object.id' => $id), + 'fields' => array('id', 'distribution', 'event_id', $field), + 'contain' => array( + 'Event' => array( + 'fields' => array('distribution', 'id', 'org_id'), + ) + ), + 'flatten' => 1 ); $object = $this->MispObject->fetchObjectSimple($this->Auth->user(), $params); if (empty($object)) { @@ -595,10 +595,11 @@ class ObjectsController extends AppController } $object = $object[0]; $result = $object['Object'][$field]; - if ($field == 'distribution') { - $result=$this->MispObject->shortDist[$result]; + if ($field === 'distribution') { + $result = $this->MispObject->shortDist[$result]; } $this->set('value', $result); + $this->set('field', $field); $this->layout = 'ajax'; $this->render('ajax/objectViewFieldForm'); } diff --git a/app/Controller/OrganisationsController.php b/app/Controller/OrganisationsController.php index 7d9f6345d..06c9350e4 100644 --- a/app/Controller/OrganisationsController.php +++ b/app/Controller/OrganisationsController.php @@ -392,7 +392,7 @@ class OrganisationsController extends AppController { $this->layout = false; $this->autoRender = false; - $this->set('id', $id); + $this->set('id', (int)$id); $this->set('removable', $removable); $this->set('extend', $extend); $this->render('ajax/sg_org_row_empty'); diff --git a/app/Controller/PagesController.php b/app/Controller/PagesController.php index 61d63f008..0f75e9bf6 100644 --- a/app/Controller/PagesController.php +++ b/app/Controller/PagesController.php @@ -30,7 +30,12 @@ class PagesController extends AppController public function display() { $path = func_get_args(); - + foreach ($path as $k => $part) { + if (strpos($part, '..') !== false || strpos($part, '/') !== false) { + unset($path[$k]); + } + } + $path = array_values($path); $count = count($path); if (!$count) { $this->redirect('/'); diff --git a/app/Controller/RestClientHistoryController.php b/app/Controller/RestClientHistoryController.php index fa84a4bba..91380f6c3 100644 --- a/app/Controller/RestClientHistoryController.php +++ b/app/Controller/RestClientHistoryController.php @@ -1,7 +1,9 @@ _isRest()) { $list = $this->RestClientHistory->find('all', $params); - } else { - $this->paginate = array_merge($this->paginate, $params); - $list = $this->paginate(); - } - if ($this->_isRest()) { return $this->RestResponse->viewData($list, $this->response->type()); - } else { - $this->set('bookmarked', $bookmarked); - $this->set('list', $list); - $this->layout = false; - $this->autoRender = false; - $this->render('index'); } + + $this->paginate = array_merge($this->paginate, $params); + $list = $this->paginate(); + $this->set('bookmarked', $bookmarked); + $this->set('list', array_column($list, 'RestClientHistory')); + $this->layout = false; + $this->autoRender = false; + $this->render('index'); } public function delete($id) diff --git a/app/Controller/ServersController.php b/app/Controller/ServersController.php index 81c5bc2e7..bd38bd082 100644 --- a/app/Controller/ServersController.php +++ b/app/Controller/ServersController.php @@ -39,7 +39,6 @@ class ServersController extends AppController $this->Auth->allow(['cspReport']); // cspReport must work without authentication parent::beforeFilter(); - $this->Security->unlockedActions[] = 'getApiInfo'; $this->Security->unlockedActions[] = 'cspReport'; // permit reuse of CSRF tokens on some pages. switch ($this->request->params['action']) { @@ -443,20 +442,7 @@ class ServersController extends AppController $allOrgs[] = array('id' => $o['Organisation']['id'], 'name' => $o['Organisation']['name']); } - $allTypes = []; - $this->loadModel('Attribute'); - $this->loadModel('ObjectTemplate'); - $objects = $this->ObjectTemplate->find('all', [ - 'recursive' => -1, - 'fields' => ['uuid', 'name'], - 'group' => ['uuid', 'name'], - ]); - $allTypes = [ - 'attribute' => array_unique(Hash::extract(Hash::extract($this->Attribute->categoryDefinitions, '{s}.types'), '{n}.{n}')), - 'object' => Hash::map($objects, '{n}.ObjectTemplate', function ($item) { - return ['id' => $item['uuid'], 'name' => sprintf('%s (%s)', $item['name'], $item['uuid'])]; - }) - ]; + $allTypes = $this->Server->getAllTypes(); $this->set('host_org_id', Configure::read('MISP.host_org_id')); $this->set('organisationOptions', $organisationOptions); @@ -468,6 +454,7 @@ class ServersController extends AppController $this->set('allTags', $this->__getTags()); $this->set('host_org_id', Configure::read('MISP.host_org_id')); + $this->set('pull_scope', 'server'); $this->render('edit'); } } @@ -641,20 +628,7 @@ class ServersController extends AppController $allOrgs[] = array('id' => $o['Organisation']['id'], 'name' => $o['Organisation']['name']); } - $allTypes = []; - $this->loadModel('Attribute'); - $this->loadModel('ObjectTemplate'); - $objects = $this->ObjectTemplate->find('all', [ - 'recursive' => -1, - 'fields' => ['uuid', 'name'], - 'group' => ['uuid', 'name'], - ]); - $allTypes = [ - 'attribute' => array_unique(Hash::extract(Hash::extract($this->Attribute->categoryDefinitions, '{s}.types'), '{n}.{n}')), - 'object' => Hash::map($objects, '{n}.ObjectTemplate', function ($item) { - return ['id' => $item['uuid'], 'name' => sprintf('%s (%s)', $item['name'], $item['uuid'])]; - }) - ]; + $allTypes = $this->Server->getAllTypes(); $oldRemoteSetting = 0; if (!$this->Server->data['RemoteOrg']['local']) { @@ -675,6 +649,7 @@ class ServersController extends AppController $this->set('server', $s); $this->set('id', $id); $this->set('host_org_id', Configure::read('MISP.host_org_id')); + $this->set('pull_scope', 'server'); } } @@ -1996,280 +1971,6 @@ class ServersController extends AppController return $this->RestResponse->viewData(array('uuid' => Configure::read('MISP.uuid')), $this->response->type()); } - public function rest() - { - $allValidApis = $this->RestResponse->getAllApis($this->Auth->user()); - $allValidApisFieldsContraint = $this->RestResponse->getAllApisFieldsConstraint($this->Auth->user()); - if ($this->request->is('post')) { - $request = $this->request->data; - if (!empty($request['Server'])) { - $request = $this->request->data['Server']; - } - $curl = ''; - $python = ''; - try { - $result = $this->__doRestQuery($request, $curl, $python); - $this->set('curl', $curl); - $this->set('python', $python); - if (!$result) { - $this->Flash->error('Something went wrong. Make sure you set the http method, body (when sending POST requests) and URL correctly.'); - } else { - $this->set('data', $result); - } - } catch (Exception $e) { - $this->Flash->error(__('Something went wrong. %s', $e->getMessage())); - } - } - $header = sprintf( - "Authorization: %s \nAccept: application/json\nContent-type: application/json", - __('YOUR_API_KEY') - ); - $this->set('header', $header); - $this->set('allValidApis', $allValidApis); - // formating for optgroup - $allValidApisFormated = array(); - foreach ($allValidApis as $endpoint_url => $endpoint_data) { - $allValidApisFormated[$endpoint_data['controller']][] = array('url' => $endpoint_url, 'action' => $endpoint_data['action']); - } - $this->set('allValidApisFormated', $allValidApisFormated); - $this->set('allValidApisFieldsContraint', $allValidApisFieldsContraint); - } - - /** - * @param array $request - * @param string $curl - * @param string $python - * @return array|false - */ - private function __doRestQuery(array $request, &$curl = false, &$python = false) - { - App::uses('SyncTool', 'Tools'); - $params = array(); - - $logHeaders = $request['header']; - if (!empty(Configure::read('Security.advanced_authkeys'))) { - $logHeaders = explode("\n", $request['header']); - foreach ($logHeaders as $k => $header) { - if (strpos($header, 'Authorization') !== false) { - $logHeaders[$k] = 'Authorization: ' . __('YOUR_API_KEY'); - } - } - $logHeaders = implode("\n", $logHeaders); - } - - if (empty($request['body'])) { - $historyBody = ''; - } else if (strlen($request['body']) > 65535) { - $historyBody = ''; // body is too long to save into history table - } else { - $historyBody = $request['body']; - } - - $rest_history_item = array( - 'org_id' => $this->Auth->user('org_id'), - 'user_id' => $this->Auth->user('id'), - 'headers' => $logHeaders, - 'body' => $historyBody, - 'url' => $request['url'], - 'http_method' => $request['method'], - 'use_full_path' => empty($request['use_full_path']) ? false : $request['use_full_path'], - 'show_result' => $request['show_result'], - 'skip_ssl' => $request['skip_ssl_validation'], - 'bookmark' => $request['bookmark'], - 'bookmark_name' => $request['name'], - 'timestamp' => time(), - ); - if (!empty($request['url'])) { - if (empty($request['use_full_path']) || empty(Configure::read('Security.rest_client_enable_arbitrary_urls'))) { - $path = preg_replace('#^(://|[^/?])+#', '', $request['url']); - $url = empty(Configure::read('Security.rest_client_baseurl')) ? (Configure::read('MISP.baseurl') . $path) : (Configure::read('Security.rest_client_baseurl') . $path); - unset($request['url']); - } else { - $url = $request['url']; - } - } else { - throw new InvalidArgumentException('URL not set.'); - } - if (!empty($request['skip_ssl_validation'])) { - $params['ssl_verify_peer'] = false; - $params['ssl_verify_host'] = false; - $params['ssl_verify_peer_name'] = false; - $params['ssl_allow_self_signed'] = true; - } - $params['timeout'] = 300; - App::uses('HttpSocket', 'Network/Http'); - $HttpSocket = new HttpSocket($params); - - $temp_headers = empty($request['header']) ? [] : explode("\n", $request['header']); - $request['header'] = array( - 'Accept' => 'application/json', - 'Content-Type' => 'application/json', - 'User-Agent' => 'MISP REST Client', - ); - foreach ($temp_headers as $header) { - $header = explode(':', $header); - $header[0] = trim($header[0]); - $header[1] = trim($header[1]); - $request['header'][$header[0]] = $header[1]; - } - $start = microtime(true); - if ( - !empty($request['method']) && - $request['method'] === 'GET' - ) { - if ($curl !== false) { - $curl = $this->__generateCurlQuery('get', $request, $url); - } - if ($python !== false) { - $python = $this->__generatePythonScript($request, $url); - } - $response = $HttpSocket->get($url, false, array('header' => $request['header'])); - } elseif ( - !empty($request['method']) && - $request['method'] === 'POST' && - !empty($request['body']) - ) { - if ($curl !== false) { - $curl = $this->__generateCurlQuery('post', $request, $url); - } - if ($python !== false) { - $python = $this->__generatePythonScript($request, $url); - } - $response = $HttpSocket->post($url, $request['body'], array('header' => $request['header'])); - } elseif ( - !empty($request['method']) && - $request['method'] === 'DELETE' - ) { - if ($curl !== false) { - $curl = $this->__generateCurlQuery('delete', $request, $url); - } - if ($python !== false) { - $python = $this->__generatePythonScript($request, $url); - } - $response = $HttpSocket->delete($url, false, array('header' => $request['header'])); - } else { - return false; - } - $viewData = [ - 'duration' => round((microtime(true) - $start) * 1000, 2) . ' ms', - 'url' => $url, - 'code' => $response->code, - 'headers' => $response->headers, - ]; - - if (!empty($request['show_result'])) { - $viewData['data'] = $response->body; - } else { - if ($response->isOk()) { - $viewData['data'] = 'Success.'; - } else { - $viewData['data'] = 'Something went wrong.'; - } - } - $rest_history_item['outcome'] = $response->code; - - $this->loadModel('RestClientHistory'); - $this->RestClientHistory->create(); - $this->RestClientHistory->save($rest_history_item); - $this->RestClientHistory->cleanup($this->Auth->user('id')); - - return $viewData; - } - - private function __generatePythonScript($request, $url) - { - $slashCounter = 0; - $baseurl = ''; - $relative = ''; - $verifyCert = ($url[4] === 's') ? 'True' : 'False'; - for ($i = 0; $i < strlen($url); $i++) { - //foreach ($url as $url[$i]) { - if ($url[$i] === '/') { - $slashCounter += 1; - if ($slashCounter == 3) { - continue; - } - } - if ($slashCounter < 3) { - $baseurl .= $url[$i]; - } else { - $relative .= $url[$i]; - } - } - $python_script = - sprintf( -'misp_url = \'%s\' -misp_key = \'%s\' -misp_verifycert = %s -relative_path = \'%s\' -body = %s - -from pymisp import ExpandedPyMISP - -misp = ExpandedPyMISP(misp_url, misp_key, misp_verifycert) -misp.direct_call(relative_path, body) -', - $baseurl, - $request['header']['Authorization'], - $verifyCert, - $relative, - (empty($request['body']) ? 'None' : $request['body']) - ); - return $python_script; - } - - private function __generateCurlQuery($type, $request, $url) - { - if ($type === 'get') { - $curl = sprintf( - 'curl \%s -H "Authorization: %s" \%s -H "Accept: %s" \%s -H "Content-type: %s" \%s %s', - PHP_EOL, - $request['header']['Authorization'], - PHP_EOL, - $request['header']['Accept'], - PHP_EOL, - $request['header']['Content-Type'], - PHP_EOL, - $url - ); - } else { - $curl = sprintf( - 'curl \%s -d \'%s\' \%s -H "Authorization: %s" \%s -H "Accept: %s" \%s -H "Content-type: %s" \%s -X POST %s', - PHP_EOL, - json_encode(json_decode($request['body'])), - PHP_EOL, - $request['header']['Authorization'], - PHP_EOL, - $request['header']['Accept'], - PHP_EOL, - $request['header']['Content-Type'], - PHP_EOL, - $url - ); - } - return $curl; - } - - public function getApiInfo() - { - $relative_path = $this->request->data['url']; - $result = $this->RestResponse->getApiInfo($relative_path); - if ($this->_isRest()) { - if (!empty($result)) { - $result['api_info'] = $result; - } - return $this->RestResponse->viewData($result, $this->response->type()); - } else { - if (empty($result)) { - return $this->RestResponse->viewData(' ', $this->response->type()); - } - $this->layout = false; - $this->autoRender = false; - $this->set('api_info', $result); - $this->render('ajax/get_api_info'); - } - } - public function cache($id = 'all') { if (Configure::read('MISP.background_jobs')) { @@ -2525,17 +2226,6 @@ misp.direct_call(relative_path, body) return new CakeResponse(['status' => 204]); } - public function viewDeprecatedFunctionUse() - { - $data = $this->Deprecation->getDeprecatedAccessList($this->Server); - if ($this->_isRest()) { - return $this->RestResponse->viewData($data, $this->response->type()); - } else { - $this->layout = false; - $this->set('data', $data); - } - } - /** * List all tags for the rule picker. * @@ -2590,9 +2280,6 @@ misp.direct_call(relative_path, body) return $this->RestResponse->viewData($syncFilteringRules); } - public function openapi() { - } - public function pruneDuplicateUUIDs() { if (!$this->request->is('post')) { @@ -2762,4 +2449,13 @@ misp.direct_call(relative_path, body) } return $this->RestResponse->viewData($users, $this->response->type()); } + + /** + * @deprecated + * @return void + */ + public function rest() + { + $this->redirect(['controller' => 'api', 'action' => 'rest']); + } } diff --git a/app/Controller/TaxonomiesController.php b/app/Controller/TaxonomiesController.php index 5a752e6ab..65ed6d367 100644 --- a/app/Controller/TaxonomiesController.php +++ b/app/Controller/TaxonomiesController.php @@ -183,27 +183,17 @@ class TaxonomiesController extends AppController public function enable($id) { - if (!$this->_isSiteAdmin() || !$this->request->is('Post')) { - throw new MethodNotAllowedException(__('You don\'t have permission to do that.')); - } + $this->request->allowMethod(['post']); + $taxonomy = $this->Taxonomy->find('first', array( 'recursive' => -1, 'conditions' => array('Taxonomy.id' => $id), )); $taxonomy['Taxonomy']['enabled'] = true; $this->Taxonomy->save($taxonomy); - $this->Log = ClassRegistry::init('Log'); - $this->Log->create(); - $this->Log->save(array( - 'org' => $this->Auth->user('Organisation')['name'], - 'model' => 'Taxonomy', - 'model_id' => $id, - 'email' => $this->Auth->user('email'), - 'action' => 'enable', - 'user_id' => $this->Auth->user('id'), - 'title' => 'Taxonomy enabled', - 'change' => $taxonomy['Taxonomy']['namespace'] . ' - enabled', - )); + + $this->__log('enable', $id, 'Taxonomy enabled', $taxonomy['Taxonomy']['namespace'] . ' - enabled'); + if ($this->_isRest()) { return $this->RestResponse->saveSuccessResponse('Taxonomy', 'enable', $id, $this->response->type()); } else { @@ -214,28 +204,18 @@ class TaxonomiesController extends AppController public function disable($id) { - if (!$this->_isSiteAdmin() || !$this->request->is('Post')) { - throw new MethodNotAllowedException(__('You don\'t have permission to do that.')); - } + $this->request->allowMethod(['post']); + $taxonomy = $this->Taxonomy->find('first', array( - 'recursive' => -1, - 'conditions' => array('Taxonomy.id' => $id), + 'recursive' => -1, + 'conditions' => array('Taxonomy.id' => $id), )); $this->Taxonomy->disableTags($id); $taxonomy['Taxonomy']['enabled'] = 0; $this->Taxonomy->save($taxonomy); - $this->Log = ClassRegistry::init('Log'); - $this->Log->create(); - $this->Log->save(array( - 'org' => $this->Auth->user('Organisation')['name'], - 'model' => 'Taxonomy', - 'model_id' => $id, - 'email' => $this->Auth->user('email'), - 'action' => 'disable', - 'user_id' => $this->Auth->user('id'), - 'title' => 'Taxonomy disabled', - 'change' => $taxonomy['Taxonomy']['namespace'] . ' - disabled', - )); + + $this->__log('disable', $id, 'Taxonomy disabled', $taxonomy['Taxonomy']['namespace'] . ' - disabled'); + if ($this->_isRest()) { return $this->RestResponse->saveSuccessResponse('Taxonomy', 'disable', $id, $this->response->type()); } else { @@ -246,9 +226,8 @@ class TaxonomiesController extends AppController public function import() { - if (!$this->request->is('post')) { - throw new MethodNotAllowedException('This endpoint requires a POST request.'); - } + $this->request->allowMethod(['post']); + try { $id = $this->Taxonomy->import($this->request->data); return $this->view($id); @@ -260,7 +239,6 @@ class TaxonomiesController extends AppController public function update() { $result = $this->Taxonomy->update(); - $this->Log = ClassRegistry::init('Log'); $fails = 0; $successes = 0; if (!empty($result)) { @@ -271,50 +249,19 @@ class TaxonomiesController extends AppController } else { $change = $success['namespace'] . ' v' . $success['new'] . ' installed'; } - $this->Log->create(); - $this->Log->save(array( - 'org' => $this->Auth->user('Organisation')['name'], - 'model' => 'Taxonomy', - 'model_id' => $id, - 'email' => $this->Auth->user('email'), - 'action' => 'update', - 'user_id' => $this->Auth->user('id'), - 'title' => 'Taxonomy updated', - 'change' => $change, - )); + $this->__log('update', $id, 'Taxonomy updated', $change); $successes++; } } if (isset($result['fails'])) { foreach ($result['fails'] as $id => $fail) { - $this->Log->create(); - $this->Log->save(array( - 'org' => $this->Auth->user('Organisation')['name'], - 'model' => 'Taxonomy', - 'model_id' => $id, - 'email' => $this->Auth->user('email'), - 'action' => 'update', - 'user_id' => $this->Auth->user('id'), - 'title' => 'Taxonomy failed to update', - 'change' => $fail['namespace'] . ' could not be installed/updated. Error: ' . $fail['fail'], - )); + $this->__log('update', $id, 'Taxonomy failed to update', $fail['namespace'] . ' could not be installed/updated. Error: ' . $fail['fail']); $fails++; } } } else { - $this->Log->create(); - $this->Log->save(array( - 'org' => $this->Auth->user('Organisation')['name'], - 'model' => 'Taxonomy', - 'model_id' => 0, - 'email' => $this->Auth->user('email'), - 'action' => 'update', - 'user_id' => $this->Auth->user('id'), - 'title' => 'Taxonomy update (nothing to update)', - 'change' => 'Executed an update of the taxonomy library, but there was nothing to update.', - )); + $this->__log('update', 0, 'Taxonomy update (nothing to update)', 'Executed an update of the taxonomy library, but there was nothing to update.'); } - $message = ''; if ($successes == 0 && $fails == 0) { $flashType = 'info'; $message = __('All taxonomy libraries are up to date already.'); @@ -338,9 +285,6 @@ class TaxonomiesController extends AppController public function addTag($taxonomy_id = false) { - if ((!$this->_isSiteAdmin() && !$this->userRole['perm_tagger'])) { - throw new NotFoundException(__('You don\'t have permission to do that.')); - } if ($this->request->is('get')) { if (empty($taxonomy_id) && !empty($this->request->params['named']['taxonomy_id'])) { $taxonomy_id = $this->request->params['named']['taxonomy_id']; @@ -391,9 +335,8 @@ class TaxonomiesController extends AppController public function hideTag($taxonomy_id = false) { - if ((!$this->_isSiteAdmin() && !$this->userRole['perm_tagger']) || !$this->request->is('post')) { - throw new NotFoundException(__('You don\'t have permission to do that.')); - } + $this->request->allowMethod(['post']); + if ($taxonomy_id) { $result = $this->Taxonomy->hideTags($taxonomy_id); } else { @@ -421,9 +364,8 @@ class TaxonomiesController extends AppController public function unhideTag($taxonomy_id = false) { - if ((!$this->_isSiteAdmin() && !$this->userRole['perm_tagger']) || !$this->request->is('post')) { - throw new NotFoundException(__('You don\'t have permission to do that.')); - } + $this->request->allowMethod(['post']); + if ($taxonomy_id) { $result = $this->Taxonomy->unhideTags($taxonomy_id); } else { @@ -451,9 +393,6 @@ class TaxonomiesController extends AppController public function disableTag($taxonomy_id = false) { - if ((!$this->_isSiteAdmin() && !$this->userRole['perm_tagger'])) { - throw new NotFoundException(__('You don\'t have permission to do that.')); - } if ($this->request->is('get')) { if (empty($taxonomy_id) && !empty($this->request->params['named']['taxonomy_id'])) { $taxonomy_id = $this->request->params['named']['taxonomy_id']; @@ -559,6 +498,21 @@ class TaxonomiesController extends AppController $this->render('ajax/toggle_required'); } + /** + * @param string $action + * @param int $modelId + * @param string $title + * @param string $change + * @return void + * @throws Exception + */ + private function __log($action, $modelId, $title, $change) + { + /** @var Log $log */ + $log = ClassRegistry::init('Log'); + $log->createLogEntry($this->Auth->user(), $action, 'Taxonomy', $modelId, $title, $change); + } + /** * Attach tag counts. * @param array $taxonomies diff --git a/app/Controller/UsersController.php b/app/Controller/UsersController.php index 186a27aa0..6f3f5f289 100644 --- a/app/Controller/UsersController.php +++ b/app/Controller/UsersController.php @@ -154,7 +154,7 @@ class UsersController extends AppController } } } - if (!$abortPost && !$this->_isRest()) { + if (!$abortPost && (!$this->_isRest() || empty($this->request->header('Authorization')))) { if (Configure::read('Security.require_password_confirmation')) { if (!empty($this->request->data['User']['current_password'])) { $hashed = $this->User->verifyPassword($this->Auth->user('id'), $this->request->data['User']['current_password']); @@ -853,7 +853,7 @@ class UsersController extends AppController $this->request->data['User'] = $this->request->data; } $abortPost = false; - if (!$this->_isRest()) { + if (!$this->_isRest() || empty($this->request->header('Authorization'))) { if (Configure::read('Security.require_password_confirmation')) { if (!empty($this->request->data['User']['current_password'])) { $hashed = $this->User->verifyPassword($this->Auth->user('id'), $this->request->data['User']['current_password']); diff --git a/app/Lib/Tools/ComplexTypeTool.php b/app/Lib/Tools/ComplexTypeTool.php index 8d5145eef..c93a9f44a 100644 --- a/app/Lib/Tools/ComplexTypeTool.php +++ b/app/Lib/Tools/ComplexTypeTool.php @@ -40,9 +40,6 @@ class ComplexTypeTool 128 => array('single' => array('sha512'), 'composite' => array('filename|sha512')) ); - // algorithms to run through in order, without Hashes that are checked separately - const CHECKS = array('Email', 'IP', 'DomainOrFilename', 'SimpleRegex', 'AS', 'BTC'); - private $__tlds = null; public static function refangValue($value, $type) @@ -179,6 +176,9 @@ class ComplexTypeTool continue; } foreach ($row as $elementPos => $element) { + if (empty($element)) { + continue; + } if (empty($values) || in_array(($elementPos + 1), $values)) { $element = trim($element, " \t\n\r\0\x0B\"\'"); if (empty($element)) { @@ -198,23 +198,28 @@ class ComplexTypeTool return $iocArray; } - public function checkFreeText($input, $settings = array()) + /** + * @param string $input + * @param array $settings + * @return array + */ + public function checkFreeText($input, array $settings = []) { - $charactersToTrim = '\'".,() ' . "\t\n\r\0\x0B"; // custom + default PHP trim $input = str_replace("\xc2\xa0", ' ', $input); // non breaking space to normal space $input = preg_replace('/\p{C}+/u', ' ', $input); $iocArray = preg_split("/\r\n|\n|\r|\s|\s+|,|\<|\>|;/", $input); - preg_match_all('/\"([^\"]*)\"/', $input, $matches); - foreach ($matches[1] as $match) { - if ($match !== '') { - $iocArray[] = $match; - } - } + preg_match_all('/\"([^\"]*)\"/', $input, $matches); + foreach ($matches[1] as $match) { + if ($match !== '') { + $iocArray[] = $match; + } + } + unset($matches); $resultArray = []; foreach ($iocArray as $ioc) { - $ioc = trim($ioc, $charactersToTrim); + $ioc = trim($ioc, '\'".,() ' . "\t\n\r\0\x0B"); // custom + default PHP trim if (empty($ioc)) { continue; } @@ -251,23 +256,37 @@ class ComplexTypeTool ]; } - $input = array('raw' => $raw_input); + $input = ['raw' => $raw_input]; // Check hashes before refang and port extracting, it is not necessary for hashes. This speedups parsing // freetexts or CSVs with a lot of hashes. - $hashes = $this->__checkForHashes($input); - if ($hashes) { - return $hashes; + if ($result = $this->__checkForHashes($input)) { + return $result; } $input = $this->__refangInput($input); - $input = $this->__extractPort($input); - foreach (self::CHECKS as $check) { - $result = $this->{'__checkFor' . $check}($input); - if ($result) { - return $result; - } + // Check email before port extracting, it is not necessary for email. This speedups parsing + // freetexts or CSVs with a lot of emails. + if ($result = $this->__checkForEmail($input)) { + return $result; + } + + $input = $this->__extractPort($input); + if ($result = $this->__checkForIP($input)) { + return $result; + } + if ($result = $this->__checkForDomainOrFilename($input)) { + return $result; + } + if ($result = $this->__checkForSimpleRegex($input)) { + return $result; + } + if ($result = $this->__checkForAS($input)) { + return $result; + } + if ($result = $this->__checkForBTC($input)) { + return $result; } return false; } @@ -290,7 +309,12 @@ class ComplexTypeTool // quick filter for an @ to see if we should validate a potential e-mail address if (strpos($input['refanged'], '@') !== false) { if (filter_var($input['refanged'], FILTER_VALIDATE_EMAIL)) { - return array('types' => array('email', 'email-src', 'email-dst', 'target-email', 'whois-registrant-email'), 'to_ids' => true, 'default_type' => 'email-src', 'value' => $input['refanged']); + return [ + 'types' => array('email', 'email-src', 'email-dst', 'target-email', 'whois-registrant-email'), + 'to_ids' => true, + 'default_type' => 'email-src', + 'value' => $input['refanged'], + ]; } } return false; @@ -356,17 +380,17 @@ class ComplexTypeTool private function __refangInput($input) { - $input['refanged'] = $input['raw']; + $refanged = $input['raw']; foreach (self::REFANG_REGEX_TABLE as $regex) { - $input['refanged'] = preg_replace($regex['from'], $regex['to'], $input['refanged']); + $refanged = preg_replace($regex['from'], $regex['to'], $refanged); } - $input['refanged'] = rtrim($input['refanged'], "."); + $refanged = rtrim($refanged, "."); $input['refanged'] = preg_replace_callback( '/\[.\]/', function ($matches) { return trim($matches[0], '[]'); }, - $input['refanged'] + $refanged ); return $input; } diff --git a/app/Lib/Tools/CryptGpgExtended.php b/app/Lib/Tools/CryptGpgExtended.php index 3ea777b43..6a71884f0 100644 --- a/app/Lib/Tools/CryptGpgExtended.php +++ b/app/Lib/Tools/CryptGpgExtended.php @@ -173,4 +173,21 @@ class CryptGpgExtended extends Crypt_GPG return $armored; } + + /** + * @param mixed $data + * @param bool $isFile + * @param bool $allowEmpty + * @return resource|string|null + * @throws Crypt_GPG_FileException + * @throws Crypt_GPG_NoDataException + */ + protected function _prepareInput($data, $isFile = false, $allowEmpty = true) + { + if ($isFile && $data instanceof TmpFileTool) { + return $data->resource(); + } + + return parent::_prepareInput($data, $isFile, $allowEmpty); + } } diff --git a/app/Lib/Tools/GpgTool.php b/app/Lib/Tools/GpgTool.php index 82af45273..689bc1ba6 100644 --- a/app/Lib/Tools/GpgTool.php +++ b/app/Lib/Tools/GpgTool.php @@ -1,6 +1,9 @@ $homedir, 'gpgconf' => Configure::read('GnuPG.gpgconf'), 'binary' => Configure::read('GnuPG.binary') ?: '/usr/bin/gpg', - ); + ]; return new CryptGpgExtended($options); } - /** @var CryptGpgExtended */ - private $gpg; - - public function __construct($gpg) + public function __construct(CryptGpgExtended $gpg = null) { $this->gpg = $gpg; } @@ -47,11 +47,13 @@ class GpgTool public function searchGpgKey($search) { $uri = 'https://openpgp.circl.lu/pks/lookup?search=' . urlencode($search) . '&op=index&fingerprint=on&options=mr'; - $response = $this->keyServerLookup($uri); - if ($response->code == 404) { - return array(); // no keys found - } else if ($response->code != 200) { - throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}"); + try { + $response = $this->keyServerLookup($uri); + } catch (HttpSocketHttpException $e) { + if ($e->getCode() === 404) { + return []; + } + throw $e; } return $this->extractKeySearch($response->body); } @@ -64,11 +66,13 @@ class GpgTool public function fetchGpgKey($fingerprint) { $uri = 'https://openpgp.circl.lu/pks/lookup?search=0x' . urlencode($fingerprint) . '&op=get&options=mr'; - $response = $this->keyServerLookup($uri); - if ($response->code == 404) { - return null; // key with given fingerprint not found - } else if ($response->code != 200) { - throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}"); + try { + $response = $this->keyServerLookup($uri); + } catch (HttpSocketHttpException $e) { + if ($e->getCode() === 404) { + return null; + } + throw $e; } $key = $response->body; @@ -169,30 +173,20 @@ class GpgTool $advancedUrl = "https://openpgpkey.$domain/.well-known/openpgpkey/" . strtolower($domain) . "/hu/$localPartHash"; try { $response = $this->keyServerLookup($advancedUrl); - return $this->processWkdResponse($response); + return $this->gpg->enarmor($response->body()); } catch (Exception $e) { // pass, continue to direct method } $directUrl = "https://$domain/.well-known/openpgpkey/hu/$localPartHash"; - $response = $this->keyServerLookup($directUrl); - return $this->processWkdResponse($response); - } - - /** - * @param HttpSocketResponse $response - * @return string - * @throws Crypt_GPG_Exception - * @throws Crypt_GPG_InvalidOperationException - */ - private function processWkdResponse(HttpSocketResponse $response) - { - if ($response->code == 404) { - throw new NotFoundException("Key not found"); - } else if (!$response->isOk()) { - throw new Exception("Fetching the WKD failed with HTTP error {$response->code}: {$response->reasonPhrase}"); + try { + $response = $this->keyServerLookup($directUrl); + } catch (HttpSocketHttpException $e) { + if ($e->getCode() === 404) { + throw new NotFoundException("Key not found"); + } + throw $e; } - return $this->gpg->enarmor($response->body()); } @@ -232,17 +226,18 @@ class GpgTool /** * @param string $uri - * @return HttpSocketResponse + * @return HttpSocketResponseExtended + * @throws HttpSocketHttpException * @throws Exception */ private function keyServerLookup($uri) { App::uses('SyncTool', 'Tools'); $syncTool = new SyncTool(); - $HttpSocket = $syncTool->setupHttpSocket(); + $HttpSocket = $syncTool->createHttpSocket(['compress' => true]); $response = $HttpSocket->get($uri); - if ($response === false) { - throw new Exception("Could not fetch '$uri'."); + if (!$response->isOk()) { + throw new HttpSocketHttpException($response, $uri); } return $response; } diff --git a/app/Lib/Tools/JSONConverterTool.php b/app/Lib/Tools/JSONConverterTool.php index 15b614991..0e66184bf 100644 --- a/app/Lib/Tools/JSONConverterTool.php +++ b/app/Lib/Tools/JSONConverterTool.php @@ -93,7 +93,7 @@ class JSONConverterTool if ($raw) { return $result; } - return json_encode($result, JSON_PRETTY_PRINT); + return json_encode($result, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); } /** @@ -127,7 +127,7 @@ class JSONConverterTool } } if (isset($event['errors'])) { - yield '},"errors":' . json_encode($event['errors']) . '}'; + yield '},"errors":' . json_encode($event['errors'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . '}'; } else { yield "}}"; } diff --git a/app/Lib/Tools/JsonTool.php b/app/Lib/Tools/JsonTool.php index 8751c76f7..d79432d03 100644 --- a/app/Lib/Tools/JsonTool.php +++ b/app/Lib/Tools/JsonTool.php @@ -26,7 +26,15 @@ class JsonTool */ public static function decode($value) { - $flags = defined('JSON_THROW_ON_ERROR') ? JSON_THROW_ON_ERROR : 0; // Throw exception on error if supported - return json_decode($value, true, 512, $flags); + if (defined('JSON_THROW_ON_ERROR')) { + // JSON_THROW_ON_ERROR is supported since PHP 7.3 + return json_decode($value, true, 512, JSON_THROW_ON_ERROR); + } + + $decoded = json_decode($value, true); + if ($decoded === null) { + throw new UnexpectedValueException('Could not parse JSON: ' . json_last_error_msg(), json_last_error()); + } + return $decoded; } } diff --git a/app/Lib/Tools/RandomTool.php b/app/Lib/Tools/RandomTool.php index 52439c129..a729a6ace 100644 --- a/app/Lib/Tools/RandomTool.php +++ b/app/Lib/Tools/RandomTool.php @@ -19,7 +19,7 @@ class RandomTool * @return string * @throws Exception */ - public function random_str($crypto_secure = true, $length = 32, $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') + public static function random_str($crypto_secure = true, $length = 32, $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ') { // Type checks: if (!is_bool($crypto_secure)) { diff --git a/app/Lib/Tools/SendEmailTemplate.php b/app/Lib/Tools/SendEmailTemplate.php index 6f953c79e..3c385cc22 100644 --- a/app/Lib/Tools/SendEmailTemplate.php +++ b/app/Lib/Tools/SendEmailTemplate.php @@ -71,14 +71,26 @@ class SendEmailTemplate $View->viewPath = $View->layoutPath = 'Emails' . DS . 'html'; try { - $html = $View->render($this->viewName); + $View->viewPath = $View->layoutPath = 'Emails' . DS . 'html' . DS . 'Custom'; + $html = $View->render($this->viewName); // Attempt to load a custom template if it exists } catch (MissingViewException $e) { - $html = null; // HTMl template is optional + $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); + try { + $View->viewPath = $View->layoutPath = 'Emails' . DS . 'text' . DS . 'Custom'; + $text = $View->render($this->viewName); // Attempt to load a custom template if it exists + } catch (MissingViewException $e) { + $View->viewPath = $View->layoutPath = 'Emails' . DS . 'text'; + $text = $View->render($this->viewName); + } // Template can change default subject. if ($View->get('subject')) { diff --git a/app/Lib/Tools/ServerSyncTool.php b/app/Lib/Tools/ServerSyncTool.php index ffeca4e7b..cff26a9f4 100644 --- a/app/Lib/Tools/ServerSyncTool.php +++ b/app/Lib/Tools/ServerSyncTool.php @@ -20,6 +20,9 @@ class ServerSyncTool /** @var HttpSocketExtended */ private $socket; + /** @var CryptographicKey */ + private $cryptographicKey; + /** @var array|null */ private $info; @@ -418,8 +421,10 @@ class ServerSyncTool throw new Exception(__('Remote instance is not protected event aware yet (< 2.4.156), aborting.')); } - $this->CryptographicKey = ClassRegistry::init('CryptographicKey'); - $signature = $this->CryptographicKey->signWithInstanceKey($data); + if (!$this->cryptographicKey) { + $this->cryptographicKey = ClassRegistry::init('CryptographicKey'); + } + $signature = $this->cryptographicKey->signWithInstanceKey($data); if (empty($signature)) { throw new Exception(__("Invalid signing key. This should never happen.")); } diff --git a/app/Lib/Tools/TmpFileTool.php b/app/Lib/Tools/TmpFileTool.php index 39a90f9ed..9f910673e 100644 --- a/app/Lib/Tools/TmpFileTool.php +++ b/app/Lib/Tools/TmpFileTool.php @@ -1,14 +1,14 @@ * @throws Exception */ public function intoParsedCsv($delimiter = ',', $enclosure = '"', $escape = "\\") @@ -144,20 +144,17 @@ class TmpFileTool } /** - * @param boolean $close * @return string * @throws Exception */ - public function intoString($close = true) + public function intoString() { $this->rewind(); $string = stream_get_contents($this->tmpfile); if ($string === false) { throw new Exception('Could not read from temporary file.'); } - if ($close) { - $this->close(); - } + $this->close(); return $string; } @@ -175,6 +172,16 @@ class TmpFileTool $this->close(); } + /** + * @return resource + * @throws Exception + */ + public function resource() + { + $this->rewind(); + return $this->tmpfile; + } + /** * @return int * @throws Exception diff --git a/app/Model/AppModel.php b/app/Model/AppModel.php index 08cdccd58..5d21e9b29 100644 --- a/app/Model/AppModel.php +++ b/app/Model/AppModel.php @@ -51,6 +51,9 @@ class AppModel extends Model { parent::__construct($id, $table, $ds); $this->findMethods['column'] = true; + if (in_array('phar', stream_get_wrappers())) { + stream_wrapper_unregister('phar'); + } } // deprecated, use $db_changes @@ -2585,6 +2588,7 @@ class AppModel extends Model App::uses('KafkaPubTool', 'Tools'); $kafkaPubTool = new KafkaPubTool(); $rdkafkaIni = Configure::read('Plugin.Kafka_rdkafka_config'); + $rdkafkaIni = mb_ereg_replace("/\:\/\//", '', $rdkafkaIni); $kafkaConf = array(); if (!empty($rdkafkaIni)) { $kafkaConf = parse_ini_file($rdkafkaIni); @@ -2780,7 +2784,7 @@ class AppModel extends Model * @return array[] * @throws JsonException */ - protected function setupSyncRequest(array $server, $model = 'Server') + public function setupSyncRequest(array $server, $model = 'Server') { $version = implode('.', $this->checkMISPVersion()); $commit = $this->checkMIPSCommit(); @@ -3197,16 +3201,7 @@ class AppModel extends Model */ public function jsonDecode($json) { - if (defined('JSON_THROW_ON_ERROR')) { - // JSON_THROW_ON_ERROR is supported since PHP 7.3 - $decoded = json_decode($json, true, 512, JSON_THROW_ON_ERROR); - } else { - $decoded = json_decode($json, true); - if ($decoded === null) { - throw new UnexpectedValueException('Could not parse JSON: ' . json_last_error_msg(), json_last_error()); - } - } - + $decoded = JsonTool::decode($json); if (!is_array($decoded)) { throw new UnexpectedValueException('JSON must be array type, get ' . gettype($decoded)); } diff --git a/app/Model/Attribute.php b/app/Model/Attribute.php index a3b065f6f..7ed7ac0f8 100644 --- a/app/Model/Attribute.php +++ b/app/Model/Attribute.php @@ -1023,7 +1023,7 @@ class Attribute extends AppModel 'order' => false, 'limit' => 11, 'flatten' => 1, - 'contain' => ['AttributeTag' => false], + 'contain' => ['AttributeTag' => false, 'Object' => false], ); $resultArray[$key]['related'] = $this->fetchAttributes($user, $options); } @@ -3154,6 +3154,7 @@ class Attribute extends AppModel } $simple_params = array( 'Attribute' => array( + 'sharinggroup' => array('function' => 'set_filter_sharing_group'), 'value' => array('function' => 'set_filter_value'), 'category' => array('function' => 'set_filter_simple_attribute'), 'type' => array('function' => 'set_filter_type'), @@ -3169,6 +3170,7 @@ class Attribute extends AppModel 'comment' => array('function' => 'set_filter_comment') ), 'Event' => array( + 'sharinggroup' => array('function' => 'set_filter_sharing_group'), 'eventid' => array('function' => 'set_filter_eventid'), 'eventinfo' => array('function' => 'set_filter_eventinfo'), 'ignore' => array('function' => 'set_filter_ignore'), diff --git a/app/Model/AuditLog.php b/app/Model/AuditLog.php index 2a0002ae7..3b53ad9f9 100644 --- a/app/Model/AuditLog.php +++ b/app/Model/AuditLog.php @@ -272,7 +272,7 @@ class AuditLog extends AppModel if ($title) { $entry .= " -- $title"; } - $this->syslog->write('info', $entry); + $this->syslog->write(LOG_INFO, $entry); } return true; } diff --git a/app/Model/AuthKey.php b/app/Model/AuthKey.php index b06e902f9..954e0a693 100644 --- a/app/Model/AuthKey.php +++ b/app/Model/AuthKey.php @@ -1,8 +1,6 @@ array( - 'userModel' => 'User', - 'userKey' => 'user_id', - 'change' => 'full'), + 'userModel' => 'User', + 'userKey' => 'user_id', + 'change' => 'full' + ), 'Containable', ); @@ -25,9 +24,20 @@ class AuthKey extends AppModel 'User' ); - public $authkey_raw = false; + public $validate = [ + 'uuid' => [ + 'rule' => 'uuid', + 'message' => 'Please provide a valid RFC 4122 UUID', + ], + 'user_id' => [ + 'rule' => 'userExists', + 'message' => 'User doesn\'t exists', + ], + 'read_only' => [ + 'rule' => 'boolean', + ], + ]; - // massage the data before we send it off for validation before saving anything public function beforeValidate($options = array()) { if (empty($this->data['AuthKey']['id'])) { @@ -35,16 +45,14 @@ class AuthKey extends AppModel $this->data['AuthKey']['uuid'] = CakeText::uuid(); } if (empty($this->data['AuthKey']['authkey'])) { - $authkey = (new RandomTool())->random_str(true, 40); + $authkey = RandomTool::random_str(true, 40); } else { $authkey = $this->data['AuthKey']['authkey']; } - $passwordHasher = $this->getHasher(); - $this->data['AuthKey']['authkey'] = $passwordHasher->hash($authkey); + $this->data['AuthKey']['authkey'] = $this->getHasher()->hash($authkey); $this->data['AuthKey']['authkey_start'] = substr($authkey, 0, 4); $this->data['AuthKey']['authkey_end'] = substr($authkey, -4); $this->data['AuthKey']['authkey_raw'] = $authkey; - $this->authkey_raw = $authkey; } if (!empty($this->data['AuthKey']['allowed_ips'])) { @@ -54,6 +62,7 @@ class AuthKey extends AppModel if (empty($allowedIps)) { $allowedIps = []; } else { + // Split by new line char or by comma $allowedIps = preg_split('/([\n,])/', $allowedIps); $allowedIps = array_map('trim', $allowedIps); } @@ -336,6 +345,16 @@ class AuthKey extends AppModel $this->User->updateAll(['date_modified' => time()], ['User.id' => $userId]); } + /** + * Validation + * @param array $check + * @return bool + */ + public function userExists(array $check) + { + return $this->User->hasAny(['id' => $check['user_id']]); + } + /** * @return AbstractPasswordHasher */ diff --git a/app/Model/CryptographicKey.php b/app/Model/CryptographicKey.php index 2619f0c53..2f2f94179 100644 --- a/app/Model/CryptographicKey.php +++ b/app/Model/CryptographicKey.php @@ -35,6 +35,9 @@ class CryptographicKey extends AppModel public $validate = []; + /** @var CryptGpgExtended|null */ + private $gpg; + public function __construct($id = false, $table = null, $ds = null) { parent::__construct($id, $table, $ds); @@ -44,6 +47,12 @@ class CryptographicKey extends AppModel $this->gpg = null; } $this->validate = [ + 'uuid' => [ + 'uuid' => [ + 'rule' => 'uuid', + 'message' => 'Please provide a valid RFC 4122 UUID', + ], + ], 'type' => [ 'rule' => ['inList', $this->validTypes], 'message' => __('Invalid key type'), @@ -76,16 +85,32 @@ class CryptographicKey extends AppModel $this->data['CryptographicKey']['uuid'] = CakeText::uuid(); $this->data['CryptographicKey']['fingerprint'] = $this->extractKeyData($this->data['CryptographicKey']['type'], $this->data['CryptographicKey']['key_data']); } - $existingKeyForObject = $this->find('first', [ - 'recursive' - ]); return true; } + /** + * @return string Instance key fingerprint + * @throws Crypt_GPG_BadPassphraseException + * @throws Crypt_GPG_Exception + */ public function ingestInstanceKey() { + // If instance just key stored just in GPG homedir, use that key. + if (Configure::read('MISP.download_gpg_from_homedir')) { + if (!$this->gpg) { + throw new Exception("Could not initiate GPG"); + } + /** @var Crypt_GPG_Key[] $keys */ + $keys = $this->gpg->getKeys(Configure::read('GnuPG.email')); + if (empty($keys)) { + return false; + } + $this->gpg->addSignKey($keys[0], Configure::read('GnuPG.password')); + return $keys[0]->getPrimaryKey()->getFingerprint(); + } + try { - $redis = $this->setupRedis(); + $redis = $this->setupRedisWithException(); } catch (Exception $e) { $redis = false; } @@ -123,25 +148,29 @@ class CryptographicKey extends AppModel return $fingerprint; } + /** + * @param string $data + * @return false|string + * @throws Crypt_GPG_BadPassphraseException + * @throws Crypt_GPG_Exception + * @throws Crypt_GPG_KeyNotFoundException + */ public function signWithInstanceKey($data) { if (!$this->ingestInstanceKey()) { return false; } $data = preg_replace("/\s+/", "", $data); - $signature = $this->gpg->sign($data, Crypt_GPG::SIGN_MODE_DETACHED); - return $signature; - } - - public function signFileWithInstanceKey($path) - { - if (!$this->ingestInstanceKey()) { - return false; - } - $signature = $this->gpg->signFile($path, Crypt_GPG::SIGN_MODE_DETACHED); + $signature = $this->gpg->sign($data, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_BINARY); return $signature; } + /** + * @param string $data + * @param string $signature + * @param string $key + * @return bool + */ public function verifySignature($data, $signature, $key) { $this->error = false; @@ -217,20 +246,15 @@ class CryptographicKey extends AppModel public function uniqueKeyForElement($data) { - $existingKey = $this->find('first', [ - 'recursive' => -1, - 'conditions' => [ - 'parent_type' => $this->data['CryptographicKey']['parent_type'], - 'parent_id' => $this->data['CryptographicKey']['parent_id'], - 'key_data' => $this->data['CryptographicKey']['key_data'], - 'type' => $this->data['CryptographicKey']['type'] - ], - 'fields' => ['id'] + return !$this->hasAny([ + 'parent_type' => $this->data['CryptographicKey']['parent_type'], + 'parent_id' => $this->data['CryptographicKey']['parent_id'], + 'key_data' => $this->data['CryptographicKey']['key_data'], + 'type' => $this->data['CryptographicKey']['type'], ]); - return empty($existingKey); } - public function validateProtectedEvent($raw_data, $user, $pgp_signature, $event) + public function validateProtectedEvent($raw_data, array $user, $pgp_signature, array $event) { $eventCryptoGraphicKey = []; if (!empty($event['Event']['CryptographicKey'])) { // Depending if $event comes from fetchEvent or from pushed data @@ -240,8 +264,7 @@ class CryptographicKey extends AppModel } if (empty($eventCryptoGraphicKey)) { $message = __('No valid signatures found for validating the signature.'); - $this->Log = ClassRegistry::init('Log'); - $this->Log->createLogEntry($user, 'validateSig', 'Event', $event['Event']['id'], $message); + $this->loadLog()->createLogEntry($user, 'validateSig', 'Event', $event['Event']['id'], $message); return false; } foreach ($eventCryptoGraphicKey as $supplied_key) { @@ -249,19 +272,26 @@ class CryptographicKey extends AppModel return true; } } - $this->Log = ClassRegistry::init('Log'); $message = __('Could not validate the signature.'); - $this->Log->createLogEntry($user, 'validateSig', 'Event', $event['Event']['id'], $message); + $this->loadLog()->createLogEntry($user, 'validateSig', 'Event', $event['Event']['id'], $message); return false; } - public function captureCryptographicKeyUpdate($user, $cryptographicKeys, $parent_id, $type) + /** + * @param array $user + * @param array $cryptographicKeys + * @param int $parent_id + * @param string $type + * @return void + * @throws Exception + */ + public function captureCryptographicKeyUpdate(array $user, array $cryptographicKeys, $parent_id, $type) { $existingKeys = $this->find('first', [ 'recursive' => -1, 'conditions' => [ 'parent_type' => $type, - 'parent_id' => $parent_id + 'parent_id' => $parent_id, ], 'fields' => [ 'id', @@ -269,15 +299,14 @@ class CryptographicKey extends AppModel 'parent_type', 'parent_id', 'revoked', - 'fingerprint' + 'fingerprint', ] ]); $toRemove = []; $results = ['add' => [], 'remove' => []]; - foreach ($existingKeys as $k => $existingKey) { + foreach ($existingKeys as $existingKey) { foreach ($cryptographicKeys as $k2 => $cryptographicKey) { if ($existingKey['fingerprint'] === $cryptographicKey['fingerprint']) { - $found = true; if ($cryptographicKey['revoked'] && !$existingKey['CryptographicKey']['revoked']) { $existingKey['CryptographicKey']['revoked'] = 1; $this->save($existingKey['CryptographicKey']); @@ -314,7 +343,6 @@ class CryptographicKey extends AppModel $parent_id ); $this->deleteAll(['CryptographicKey.id' => $toRemove]); - $this->Log = ClassRegistry::init('Log'); - $this->Log->createLogEntry($user, 'updateCryptoKeys', $cryptographicKey['parent_type'], $cryptographicKey['parent_id'], $message); + $this->loadLog()->createLogEntry($user, 'updateCryptoKeys', $cryptographicKey['parent_type'], $cryptographicKey['parent_id'], $message); } } diff --git a/app/Model/Event.php b/app/Model/Event.php index 47d22fae8..41dfeee0a 100755 --- a/app/Model/Event.php +++ b/app/Model/Event.php @@ -94,8 +94,6 @@ class Event extends AppModel public $shortDist = array(0 => 'Organisation', 1 => 'Community', 2 => 'Connected', 3 => 'All', 4 => ' sharing Group'); - public $export_types = []; - public $validFormats = array( 'attack' => array('html', 'AttackExport', 'html'), 'attack-sightings' => array('json', 'AttackSightingsExport', 'json'), @@ -301,118 +299,6 @@ 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 allowedlist 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 allowedlist 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 allowedlist 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 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 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.') - ), - ); - } - private $assetCache = []; public function beforeDelete($cascade = true) @@ -2667,6 +2553,19 @@ class Event extends AppModel return $container; } + public function set_filter_sharing_group(&$params, $conditions, $options) + { + if (!empty($params['sharinggroup'])) { + $params['sharinggroup'] = $this->convert_filters($params['sharinggroup']); + if ($options['scope'] === 'Attribute') { + $conditions = $this->generic_add_filter($conditions, $params['sharinggroup'], ['Event.sharing_group_id', 'Attribute.sharing_group_id']); + } else { + $conditions = $this->generic_add_filter($conditions, $params['sharinggroup'], 'Event.sharing_group_id'); + } + } + return $conditions; + } + public function set_filter_org(&$params, $conditions, $options) { if (!empty($params['org'])) { @@ -5581,7 +5480,7 @@ class Event extends AppModel } $this->Warninglist = ClassRegistry::init('Warninglist'); $complexTypeTool->setTLDs($this->Warninglist->fetchTLDLists()); - $freetextResults = array_merge($freetextResults, $complexTypeTool->checkComplexRouter($value, 'FreeText')); + $freetextResults = array_merge($freetextResults, $complexTypeTool->checkFreeText($value)); if (!empty($freetextResults)) { foreach ($freetextResults as &$ft) { $temp = array(); @@ -6772,7 +6671,7 @@ class Event extends AppModel return $attribute_save; } - public function processFreeTextDataRouter($user, $attributes, $id, $default_comment = '', $proposals = false, $adhereToWarninglists = false, $returnRawResults = false) + public function processFreeTextDataRouter(array $user, array $attributes, $id, $default_comment = '', $proposals = false, $adhereToWarninglists = false, $returnRawResults = false) { if (Configure::read('MISP.background_jobs') && count($attributes) > 5) { // on background process just big attributes batch /** @var Job $job */ @@ -7588,4 +7487,118 @@ class Event extends AppModel $banStatus['message'] = __('Emailing republishing ban setting is not enabled'); return $banStatus; } + + /** + * @return array[] + * @deprecated + */ + public function exportTypes() + { + return 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 allowedlist 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 allowedlist 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 allowedlist 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 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 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.') + ), + ); + } } diff --git a/app/Model/Feed.php b/app/Model/Feed.php index f85c038ae..239009b3b 100644 --- a/app/Model/Feed.php +++ b/app/Model/Feed.php @@ -74,6 +74,11 @@ class Feed extends AppModel 'url_params' => '' ]; + const SUPPORTED_URL_PARAM_FILTERS = [ + 'timestamp', + 'publish_timestamp', + ]; + const CACHE_DIR = APP . 'tmp' . DS . 'cache' . DS . 'feeds' . DS; /* @@ -126,12 +131,13 @@ class Feed extends AppModel public function urlOrExistingFilepath($fields) { if ($this->isFeedLocal($this->data)) { + $path = mb_ereg_replace("/\:\/\//", '', $this->data['Feed']['url']); if ($this->data['Feed']['source_format'] == 'misp') { - if (!is_dir($this->data['Feed']['url'])) { + if (!is_dir($path)) { return 'For MISP type local feeds, please specify the containing directory.'; } } else { - if (!file_exists($this->data['Feed']['url'])) { + if (!file_exists($path)) { return 'Invalid path or file not found. Make sure that the path points to an existing file that is readable and watch out for typos.'; } } @@ -776,6 +782,10 @@ class Feed extends AppModel } } } + $url_params = !empty($filterRules['url_params']) ? $filterRules['url_params'] : []; + if (!$this->passesURLParamFilters($url_params, $event['Event'])) { + return false; + } return true; } @@ -813,7 +823,7 @@ class Feed extends AppModel } } if (!$found) { - unset($k); + unset($events[$k]); continue; } } @@ -828,14 +838,48 @@ class Feed extends AppModel } } if ($found) { - unset($k); + unset($events[$k]); } } } + $url_params = !empty($filterRules['url_params']) ? $filterRules['url_params'] : []; + if (!$this->passesURLParamFilters($url_params, $event)) { + unset($events[$k]); + } } return $events; } + private function passesURLParamFilters($url_params, $event): bool + { + $this->Attribute = ClassRegistry::init('Attribute'); + if (!empty($url_params['timestamp'])) { + $timestamps = $this->Attribute->setTimestampConditions($url_params['timestamp'], [], '', true); + if (is_array($timestamps)) { + if ($event['timestamp'] < $timestamps[0] || $event['timestamp'] > $timestamps[1]) { + return false; + } + } else { + if ($event['timestamp'] < $timestamps) { + return false; + } + } + } + if (!empty($url_params['publish_timestamp'])) { + $timestamps = $this->Attribute->setTimestampConditions($url_params['publish_timestamp'], [], '', true); + if (is_array($timestamps)) { + if ($event['timestamp'] < $timestamps[0] || $event['timestamp'] > $timestamps[1]) { + return false; + } + } else { + if ($event['timestamp'] < $timestamps) { + return false; + } + } + } + return true; + } + /** * @param array $feed * @param string $uuid @@ -997,6 +1041,7 @@ class Feed extends AppModel if ($filterRules === null) { throw new Exception('Could not parse feed filter rules JSON: ' . json_last_error_msg(), json_last_error()); } + $filterRules['url_params'] = !empty($filterRules['url_params']) ? $this->jsonDecode($filterRules['url_params']) : []; } return $filterRules; } @@ -1929,6 +1974,7 @@ class Feed extends AppModel private function feedGetUri($feed, $uri, HttpSocket $HttpSocket = null) { if ($this->isFeedLocal($feed)) { + $uri = mb_ereg_replace("/\:\/\//", '', $uri); if (file_exists($uri)) { return FileAccessTool::readFromFile($uri); } else { diff --git a/app/Model/Galaxy.php b/app/Model/Galaxy.php index a14da5c44..8faa91fa4 100644 --- a/app/Model/Galaxy.php +++ b/app/Model/Galaxy.php @@ -101,6 +101,11 @@ class Galaxy extends AppModel ]; } + /** + * @param array $galaxies + * @param array $cluster_package + * @return array + */ private function __getPreExistingClusters(array $galaxies, array $cluster_package) { $temp = $this->GalaxyCluster->find('all', array( @@ -110,11 +115,7 @@ class Galaxy extends AppModel 'recursive' => -1, 'fields' => array('version', 'id', 'value', 'uuid') )); - $existingClusters = []; - foreach ($temp as $v) { - $existingClusters[$v['GalaxyCluster']['value']] = $v; - } - return $existingClusters; + return array_column(array_column($temp, 'GalaxyCluster'), null, 'value'); } private function __deleteOutdated(bool $force, array $cluster_package, array $existingClusters) @@ -132,19 +133,20 @@ class Galaxy extends AppModel } else { $cluster_package['values'][$k]['version'] = 0; } - if (!empty($existingClusters[$cluster['value']])) { - if ($force || $existingClusters[$cluster['value']]['GalaxyCluster']['version'] < $cluster_package['values'][$k]['version']) { - $cluster_ids_to_delete[] = $existingClusters[$cluster['value']]['GalaxyCluster']['id']; - $cluster_uuids_to_delete[] = $existingClusters[$cluster['value']]['GalaxyCluster']['uuid']; + if (isset($existingClusters[$cluster['value']])) { + $existing = $existingClusters[$cluster['value']]; + if ($force || $existing['version'] < $cluster_package['values'][$k]['version']) { + $cluster_ids_to_delete[] = $existing['id']; + $cluster_uuids_to_delete[] = $existing['uuid']; } else { unset($cluster_package['values'][$k]); } } } if (!empty($cluster_ids_to_delete)) { - $this->GalaxyCluster->GalaxyElement->deleteAll(array('GalaxyElement.galaxy_cluster_id' => $cluster_ids_to_delete), false, false); - $this->GalaxyCluster->GalaxyClusterRelation->deleteRelations(array('GalaxyClusterRelation.galaxy_cluster_uuid' => $cluster_uuids_to_delete)); - $this->GalaxyCluster->deleteAll(array('GalaxyCluster.id' => $cluster_ids_to_delete), false, false); + $this->GalaxyCluster->GalaxyElement->deleteAll(array('GalaxyElement.galaxy_cluster_id' => $cluster_ids_to_delete), false); + $this->GalaxyCluster->GalaxyClusterRelation->deleteAll(array('GalaxyClusterRelation.galaxy_cluster_uuid' => $cluster_uuids_to_delete)); + $this->GalaxyCluster->deleteAll(array('GalaxyCluster.id' => $cluster_ids_to_delete), false); } return $cluster_package; } diff --git a/app/Model/GalaxyCluster.php b/app/Model/GalaxyCluster.php index addfdb26a..dc609a808 100644 --- a/app/Model/GalaxyCluster.php +++ b/app/Model/GalaxyCluster.php @@ -5,6 +5,7 @@ App::uses('TmpFileTool', 'Tools'); /** * @property Tag $Tag * @property GalaxyClusterRelation $GalaxyClusterRelation + * @property GalaxyElement $GalaxyElement */ class GalaxyCluster extends AppModel { @@ -108,10 +109,10 @@ class GalaxyCluster extends AppModel if (!isset($cluster['published'])) { $cluster['published'] = false; } - if (!isset($cluster['authors']) || $cluster['authors'] === null) { + if (!isset($cluster['authors'])) { $cluster['authors'] = ''; } elseif (is_array($cluster['authors'])) { - $cluster['authors'] = json_encode($cluster['authors']); + $cluster['authors'] = JsonTool::encode($cluster['authors']); } return true; } @@ -120,24 +121,24 @@ class GalaxyCluster extends AppModel { foreach ($results as $k => $result) { if (isset($result[$this->alias]['authors'])) { - $results[$k][$this->alias]['authors'] = json_decode($results[$k][$this->alias]['authors'], true); + $results[$k][$this->alias]['authors'] = json_decode($result[$this->alias]['authors'], true); } - if (isset($result[$this->alias]['distribution']) && $results[$k][$this->alias]['distribution'] != 4) { + if (isset($result[$this->alias]['distribution']) && $result[$this->alias]['distribution'] != 4) { unset($results[$k]['SharingGroup']); } - if (isset($result[$this->alias]['org_id']) && $results[$k][$this->alias]['org_id'] == 0) { + if (isset($result[$this->alias]['org_id']) && $result[$this->alias]['org_id'] == 0) { if (isset($results[$k]['Org'])) { $results[$k]['Org'] = Organisation::GENERIC_MISP_ORGANISATION; } } - if (isset($result[$this->alias]['orgc_id']) && $results[$k][$this->alias]['orgc_id'] == 0) { + if (isset($result[$this->alias]['orgc_id']) && $result[$this->alias]['orgc_id'] == 0) { if (isset($results[$k]['Orgc'])) { $results[$k]['Orgc'] = Organisation::GENERIC_MISP_ORGANISATION; } } if (!empty($result['GalaxyClusterRelation'])) { - foreach ($results[$k]['GalaxyClusterRelation'] as $i => $relation) { + foreach ($result['GalaxyClusterRelation'] as $i => $relation) { if (isset($relation['distribution']) && $relation['distribution'] != 4) { unset($results[$k]['GalaxyClusterRelation'][$i]['SharingGroup']); } diff --git a/app/Model/GalaxyClusterRelation.php b/app/Model/GalaxyClusterRelation.php index 00b69153b..7119c9417 100644 --- a/app/Model/GalaxyClusterRelation.php +++ b/app/Model/GalaxyClusterRelation.php @@ -157,11 +157,6 @@ class GalaxyClusterRelation extends AppModel return array_unique(array_merge($existingRelationships, $objectRelationships)); } - public function deleteRelations($conditions) - { - $this->deleteAll($conditions, false, false); - } - /** * saveRelations * diff --git a/app/Model/Job.php b/app/Model/Job.php index 945ae9de0..a6c2fcb10 100644 --- a/app/Model/Job.php +++ b/app/Model/Job.php @@ -43,7 +43,7 @@ class Job extends AppModel $this->Event = ClassRegistry::init('Event'); - if (in_array($type, array_keys($this->Event->export_types)) && $type !== 'bro') { + if (in_array($type, array_keys($this->Event->exportTypes())) && $type !== 'bro') { $this->getBackgroundJobsTool()->enqueue( BackgroundJobsTool::CACHE_QUEUE, diff --git a/app/Model/Log.php b/app/Model/Log.php index 7d6c224f6..ab963c585 100644 --- a/app/Model/Log.php +++ b/app/Model/Log.php @@ -204,7 +204,7 @@ class Log extends AppModel */ public function createLogEntry($user, $action, $model, $modelId = 0, $title = '', $change = '') { - if (in_array($action, ['tag', 'galaxy', 'publish', 'publish_sightings'], true) && Configure::read('MISP.log_new_audit')) { + if (in_array($action, ['tag', 'galaxy', 'publish', 'publish_sightings', 'enable'], true) && Configure::read('MISP.log_new_audit')) { return; // Do not store tag changes when new audit is enabled } if ($user === 'SYSTEM') { @@ -383,13 +383,13 @@ class Log extends AppModel } } if ($this->syslog) { - $action = 'info'; + $action = LOG_INFO; if (isset($data['Log']['action'])) { if (in_array($data['Log']['action'], self::ERROR_ACTIONS, true)) { - $action = 'err'; + $action = LOG_ERR; } if (in_array($data['Log']['action'], self::WARNING_ACTIONS, true)) { - $action = 'warning'; + $action = LOG_WARNING; } } diff --git a/app/Model/RestClientHistory.php b/app/Model/RestClientHistory.php index e6121dc91..bc64d1885 100644 --- a/app/Model/RestClientHistory.php +++ b/app/Model/RestClientHistory.php @@ -4,19 +4,35 @@ App::uses('AppModel', 'Model'); class RestClientHistory extends AppModel { public $belongsTo = array( - 'Org' => array( - 'className' => 'Organisation', - 'foreignKey' => 'org_id', - 'order' => array(), - 'fields' => array('id', 'name', 'uuid') - ), - 'User' => array( - 'className' => 'User', - 'foreignKey' => 'user_id', - 'order' => array(), - 'fields' => array('id', 'email') - ), - ); + 'Org' => array( + 'className' => 'Organisation', + 'foreignKey' => 'org_id', + 'order' => array(), + 'fields' => array('id', 'name', 'uuid') + ), + 'User' => array( + 'className' => 'User', + 'foreignKey' => 'user_id', + 'order' => array(), + 'fields' => array('id', 'email') + ), + ); + + /** + * @param array $user + * @param array $history + * @return void + * @throws Exception + */ + public function insert(array $user, array $history) + { + $history['org_id'] = $user['org_id']; + $history['user_id'] = $user['id']; + + $this->create(); + $this->save($history, ['atomic' => false]); + $this->cleanup($user['id']); + } public function cleanup($user_id) { @@ -35,6 +51,6 @@ class RestClientHistory extends AppModel 'NOT' => array( 'RestClientHistory.id' => $keepIds ) - )); + ), false); } } diff --git a/app/Model/Server.php b/app/Model/Server.php index 553f7fb3f..463ffff47 100644 --- a/app/Model/Server.php +++ b/app/Model/Server.php @@ -1980,7 +1980,7 @@ class Server extends AppModel App::uses('Folder', 'Utility'); App::uses('File', 'Utility'); // delete all cache files - foreach ($this->Event->export_types as $type => $settings) { + foreach ($this->Event->exportTypes() as $type => $settings) { $dir = new Folder(APP . 'tmp/cached_exports/' . $type); // No caches created for this type of export, move on if ($dir == null) { @@ -4614,6 +4614,28 @@ class Server extends AppModel return $this->saveMany($toSave, ['validate' => false, 'fields' => ['authkey']]); } + /** + * Return all Attribute and Object types + */ + public function getAllTypes(): array + { + $allTypes = []; + $this->Attribute = ClassRegistry::init('Attribute'); + $this->ObjectTemplate = ClassRegistry::init('ObjectTemplate'); + $objects = $this->ObjectTemplate->find('all', [ + 'recursive' => -1, + 'fields' => ['uuid', 'name'], + 'group' => ['uuid', 'name'], + ]); + $allTypes = [ + 'attribute' => array_unique(Hash::extract(Hash::extract($this->Attribute->categoryDefinitions, '{s}.types'), '{n}.{n}')), + 'object' => Hash::map($objects, '{n}.ObjectTemplate', function ($item) { + return ['id' => $item['uuid'], 'name' => sprintf('%s (%s)', $item['name'], $item['uuid'])]; + }) + ]; + return $allTypes; + } + /** * Invalidate config.php from php opcode cache */ @@ -5666,6 +5688,15 @@ class Server extends AppModel 'type' => 'boolean', 'null' => true, ], + 'download_gpg_from_homedir' => [ + 'level' => self::SETTING_OPTIONAL, + 'description' => __('Fetch GPG instance key from GPG homedir.'), + 'value' => false, + 'test' => 'testBool', + 'type' => 'boolean', + 'null' => true, + 'cli_only' => true, + ], ), 'GnuPG' => array( 'branch' => 1, diff --git a/app/Model/User.php b/app/Model/User.php index 36098453d..a61000fcc 100644 --- a/app/Model/User.php +++ b/app/Model/User.php @@ -4,6 +4,7 @@ App::uses('AuthComponent', 'Controller/Component'); App::uses('RandomTool', 'Tools'); App::uses('GpgTool', 'Tools'); App::uses('SendEmail', 'Tools'); +App::uses('SendEmailTemplate', 'Tools'); App::uses('BlowfishConstantPasswordHasher', 'Controller/Component/Auth'); /** @@ -832,6 +833,7 @@ class User extends AppModel $gpg = $this->initializeGpg(); $sendEmail = new SendEmail($gpg); + $result = $sendEmail->sendToUser($user, $subject, $body, $bodyNoEnc,$replyToUser ?: []); try { $result = $sendEmail->sendToUser($user, $subject, $body, $bodyNoEnc,$replyToUser ?: []); @@ -867,16 +869,6 @@ class User extends AppModel return true; } - public function adminMessageResolve($message) - { - $resolveVars = array('$contact' => 'MISP.contact', '$org' => 'MISP.org', '$misp' => 'MISP.baseurl'); - foreach ($resolveVars as $k => $v) { - $v = Configure::read($v); - $message = str_replace($k, $v, $message); - } - return $message; - } - /** * @param string $email * @return array @@ -955,24 +947,15 @@ class User extends AppModel public function initiatePasswordReset($user, $firstTime = false, $simpleReturn = false, $fixedPassword = false) { $org = Configure::read('MISP.org'); - $options = array('newUserText', 'passwordResetText'); $subjects = array('[' . $org . ' MISP] New user registration', '[' . $org . ' MISP] Password reset'); - $textToFetch = $options[($firstTime ? 0 : 1)]; $subject = $subjects[($firstTime ? 0 : 1)]; $this->Server = ClassRegistry::init('Server'); - $body = Configure::read('MISP.' . $textToFetch); - if (!$body) { - $body = $this->Server->serverSettings['MISP'][$textToFetch]['value']; - } - $body = $this->adminMessageResolve($body); if ($fixedPassword) { $password = $fixedPassword; } else { $password = $this->generateRandomPassword(); } - $body = str_replace('$password', $password, $body); - $body = str_replace('$username', $user['User']['email'], $body); - $body = str_replace('\n', PHP_EOL, $body); + $body = $this->preparePasswordResetEmail($user, $password, $firstTime, $subject); $result = $this->sendEmail($user, $body, false, $subject); if ($result) { $this->id = $user['User']['id']; @@ -991,6 +974,22 @@ class User extends AppModel } } + private function preparePasswordResetEmail($user, $password, $firstTime, $subject) + { + $textToFetch = $firstTime ? 'newUserText': 'passwordResetText'; + $this->Server = ClassRegistry::init('Server'); + $bodyTemplate = Configure::read('MISP.' . $textToFetch); + if (!$bodyTemplate) { + $bodyTemplate = $this->Server->serverSettings['MISP'][$textToFetch]['value']; + } + $template = new SendEmailTemplate('password_reset'); + $template->set('body', $bodyTemplate); + $template->set('user', $user); + $template->set('password', $password); + $template->subject($subject); + return $template; + } + public function getOrgAdminsForOrg($org_id, $excludeUserId = false) { $adminRoles = $this->Role->find('column', array( @@ -1223,13 +1222,12 @@ class User extends AppModel } // query - $this->Log = ClassRegistry::init('Log'); - $result = $this->Log->createLogEntry($user, $action, $model, $modelId, $description, $fieldsResult); + $result = $this->loadLog()->createLogEntry($user, $action, $model, $modelId, $description, $fieldsResult); // write to syslogd as well App::import('Lib', 'SysLog.SysLog'); $syslog = new SysLog(); - $syslog->write('notice', "$description -- $action" . (empty($fieldsResult) ? '' : ' -- ' . $result['Log']['change'])); + $syslog->write(LOG_NOTICE, "$description -- $action" . (empty($fieldsResult) ? '' : ' -- ' . $result['Log']['change'])); } /** diff --git a/app/Model/Warninglist.php b/app/Model/Warninglist.php index 3ae8f2218..cdf7c2886 100644 --- a/app/Model/Warninglist.php +++ b/app/Model/Warninglist.php @@ -383,7 +383,7 @@ class Warninglist extends AppModel $keys = array_keys($list['list']); if ($keys === array_keys($keys)) { - foreach (array_chunk($list['list'], 500) as $chunk) { + foreach (array_chunk($list['list'], 1000) as $chunk) { $valuesToInsert = []; foreach ($chunk as $value) { if (!empty($value)) { @@ -393,7 +393,7 @@ class Warninglist extends AppModel $result = $db->insertMulti('warninglist_entries', ['value', 'warninglist_id'], $valuesToInsert); } } else { // import warninglist with comments - foreach (array_chunk($list['list'], 500, true) as $chunk) { + foreach (array_chunk($list['list'], 1000, true) as $chunk) { $valuesToInsert = []; foreach ($chunk as $value => $comment) { if (!empty($value)) { diff --git a/app/Plugin/CertAuth/Controller/Component/Auth/CertificateAuthenticate.php b/app/Plugin/CertAuth/Controller/Component/Auth/CertificateAuthenticate.php index 521ffc8db..c91fd85d7 100644 --- a/app/Plugin/CertAuth/Controller/Component/Auth/CertificateAuthenticate.php +++ b/app/Plugin/CertAuth/Controller/Component/Auth/CertificateAuthenticate.php @@ -21,274 +21,275 @@ App::uses('BaseAuthenticate', 'Controller/Component/Auth'); class CertificateAuthenticate extends BaseAuthenticate { - /** - * Holds the certificate issuer information (available at SSL_CLIENT_I_DN) - * - * @var array - */ - protected static $ca; + /** + * Holds the certificate issuer information (available at SSL_CLIENT_I_DN) + * + * @var array + */ + protected static $ca; - /** - * Holds the certificate user information (available at SSL_CLIENT_S_DN) - * - * @var array - */ - protected static $client; + /** + * Holds the certificate user information (available at SSL_CLIENT_S_DN) + * + * @var array + */ + protected static $client; - /** - * Holds the user information - * - * @var array - */ - protected static $user; + /** + * Holds the user information + * + * @var array + */ + protected static $user; - /** - * Class constructor. - * - * This should only be called once per request, so it doesn't need to store values in - * the instance. Simply checks if the certificate is valid (against configured valid issuers) - * and returns the user information encoded. - */ - public function __construct() - { - self::$ca = self::$client = false; + /** + * Class constructor. + * + * This should only be called once per request, so it doesn't need to store values in + * the instance. Simply checks if the certificate is valid (against configured valid issuers) + * and returns the user information encoded. + */ + public function __construct() + { + self::$ca = self::$client = false; - if (isset($_SERVER['SSL_CLIENT_I_DN'])) { - $CA = self::parse($_SERVER['SSL_CLIENT_I_DN'], Configure::read('CertAuth.mapCa')); - // only valid CAs, if this was configured - if ($ca=Configure::read('CertAuth.ca')) { - $k = Configure::read('CertAuth.caId'); - if (!$k) $k = 'CN'; - $id = (isset($CA[$k]))?($CA[$k]):(false); + if (isset($_SERVER['SSL_CLIENT_I_DN'])) { + $CA = self::parse($_SERVER['SSL_CLIENT_I_DN'], Configure::read('CertAuth.mapCa')); + // only valid CAs, if this was configured + if ($ca=Configure::read('CertAuth.ca')) { + $k = Configure::read('CertAuth.caId'); + if (!$k) $k = 'CN'; + $id = (isset($CA[$k]))?($CA[$k]):(false); - if (!$id) { - $CA = false; - } else if (is_array($ca)) { - if (!in_array($id, $ca)) $CA = false; - } else if ($ca!=$id) { - $CA = false; - } - unset($id, $k); - } - self::$ca = $CA; - unset($CA, $ca); - } + if (!$id) { + $CA = false; + } else if (is_array($ca)) { + if (!in_array($id, $ca)) $CA = false; + } else if ($ca!=$id) { + $CA = false; + } + unset($id, $k); + } + self::$ca = $CA; + unset($CA, $ca); + } - if (self::$ca) { - $map = Configure::read('CertAuth.map'); - if(isset($_SERVER['SSL_CLIENT_S_DN'])) { - self::$client = self::parse($_SERVER['SSL_CLIENT_S_DN'], $map); - } else { - self::$client = array(); - } - foreach($map as $n=>$d) { - if(isset($_SERVER[$n])) { - self::$client[$d] = $_SERVER[$n]; - } - unset($map[$n], $n, $d); - } - unset($map); - if(!self::$client) { - self::$client = false; - } - } - } + if (self::$ca) { + $map = Configure::read('CertAuth.map'); + if(isset($_SERVER['SSL_CLIENT_S_DN'])) { + self::$client = self::parse($_SERVER['SSL_CLIENT_S_DN'], $map); + } else { + self::$client = array(); + } + foreach($map as $n=>$d) { + if(isset($_SERVER[$n])) { + self::$client[$d] = $_SERVER[$n]; + } + unset($map[$n], $n, $d); + } + unset($map); + if(!self::$client) { + self::$client = false; + } + } + } - /** - * Parse certificate extensions - * - * @TODO this should properly address the RFC - * @param string $s text to be parsed - * @param (optional) array $map array of mapping extension to User fields - * @return array parsed values - */ - private static function parse($s, $map=null) - { - $r=array(); - if (preg_match_all('#(^/?|\/|\,)([a-zA-Z]+)\=([^\/\,]+)#', $s, $m)) { - foreach ($m[2] as $i=>$k) { - if ($map) { - if (isset($map[$k])) { - $k = $map[$k]; - } else { - $k = null; - } - } - if ($k) { - $v = $m[3][$i]; - $r[$k] = $v; - } - unset($m[0][$i], $m[1][$i], $m[2][$i], $m[3][$i], $k, $v, $i); - } - } - return $r; - } + /** + * Parse certificate extensions + * + * @TODO this should properly address the RFC + * @param string $s text to be parsed + * @param (optional) array $map array of mapping extension to User fields + * @return array parsed values + */ + private static function parse($s, $map=null) + { + $r=array(); + if (preg_match_all('#(^/?|\/|\,)([a-zA-Z]+)\=([^\/\,]+)#', $s, $m)) { + foreach ($m[2] as $i=>$k) { + if ($map) { + if (isset($map[$k])) { + $k = $map[$k]; + } else { + $k = null; + } + } + if ($k) { + $v = $m[3][$i]; + $r[$k] = $v; + } + unset($m[0][$i], $m[1][$i], $m[2][$i], $m[3][$i], $k, $v, $i); + } + } + return $r; + } - // to enable stateless authentication - public function getUser(CakeRequest $request) - { - if (empty(self::$user)) { - if (self::$client) { - self::$user = self::$client; - // If $sync is true, allow the creation of the user from the certificate - $sync = Configure::read('CertAuth.syncUser'); - $url = Configure::read('CertAuth.restApi.url'); - if ($sync && $url) { - if (!self::getRestUser()) return false; - } + // to enable stateless authentication + public function getUser(CakeRequest $request) + { + if (empty(self::$user)) { + if (self::$client) { + self::$user = self::$client; + // If $sync is true, allow the creation of the user from the certificate + $sync = Configure::read('CertAuth.syncUser'); + $url = Configure::read('CertAuth.restApi.url'); + if ($sync && $url) { + if (!self::getRestUser()) return false; + } - // find and fill user with model - $userModelKey = empty(Configure::read('CertAuth.userModelKey')) ? 'email' : Configure::read('CertAuth.userModelKey'); - $userDefaults = Configure::read('CertAuth.userDefaults'); - $this->User = ClassRegistry::init('User'); - if (!empty(self::$user[$userModelKey])) { - $existingUser = $this->User->find('first', array( - 'conditions' => array($userModelKey => self::$user[$userModelKey]), - 'recursive' => false - )); - } - if ($existingUser) { - if ($sync) { - if (!isset(self::$user['org_id']) && isset(self::$user['org'])) { - self::$user['org_id'] = $this->User->Organisation->createOrgFromName(self::$user['org'], $existingUser['User']['id'], true); - // reset user defaults in case it's a different org_id - if (self::$user['org_id'] && $existingUser['User']['org_id'] != self::$user['org_id']) { - if ($userDefaults && is_array($userDefaults)) { - self::$user = array_merge($userDefaults + self::$user); - } - } - unset(self::$user['org']); - } - $write = array(); - foreach (self::$user as $k => $v) { - if (isset($existingUser['User'][$k]) && trim($existingUser['User'][$k]) != trim($v)) { - $write[] = $k; - $existingUser['User'][$k] = trim($v); - } - } - if (!empty($write) && !$this->User->save($existingUser['User'], true, $write)) { - CakeLog::write('alert', 'Could not update model at database with RestAPI data.'); - } - } - self::$user = $this->User->getAuthUser($existingUser['User']['id']); - if (isset(self::$user['gpgkey'])) unset(self::$user['gpgkey']); - } else if ($sync && !empty(self::$user)) { - $org = isset(self::$client['org']) ? self::$client['org'] : null; - if ($org == null) return false; - if (!isset(self::$user['org_id']) && isset(self::$user['org'])) { - self::$user['org_id'] = $this->User->Organisation->createOrgFromName($org, 0, true); - unset(self::$user['org']); - } - if ($userDefaults && is_array($userDefaults)) { - self::$user = array_merge(self::$user, $userDefaults); - } - $this->User->create(); - if ($this->User->save(self::$user)) { - $id = $this->User->id; - self::$user = $this->User->getAuthUser($id); - if (isset(self::$user['gpgkey'])) unset(self::$user['gpgkey']); - } else { - CakeLog::write('alert', 'Could not insert model at database from RestAPI data. Reason: ' . json_encode($this->User->validationErrors)); - } - } else { - // No match -- User doesn't exist !!! - self::$user = false; - } - } - } - return self::$user; - } + // find and fill user with model + $userModelKey = empty(Configure::read('CertAuth.userModelKey')) ? 'email' : Configure::read('CertAuth.userModelKey'); + $userDefaults = Configure::read('CertAuth.userDefaults'); + $this->User = ClassRegistry::init('User'); + if (!empty(self::$user[$userModelKey])) { + $existingUser = $this->User->find('first', array( + 'conditions' => array($userModelKey => self::$user[$userModelKey]), + 'recursive' => false + )); + } + if ($existingUser) { + if ($sync) { + if (!isset(self::$user['org_id']) && isset(self::$user['org'])) { + self::$user['org_id'] = $this->User->Organisation->createOrgFromName(self::$user['org'], $existingUser['User']['id'], true); + // reset user defaults in case it's a different org_id + if (self::$user['org_id'] && $existingUser['User']['org_id'] != self::$user['org_id']) { + if ($userDefaults && is_array($userDefaults)) { + self::$user = array_merge($userDefaults + self::$user); + } + } + unset(self::$user['org']); + } + $write = array(); + foreach (self::$user as $k => $v) { + if (isset($existingUser['User'][$k]) && trim($existingUser['User'][$k]) != trim($v)) { + $write[] = $k; + $existingUser['User'][$k] = trim($v); + } + } + if (!empty($write) && !$this->User->save($existingUser['User'], true, $write)) { + CakeLog::write('alert', 'Could not update model at database with RestAPI data.'); + } + } + self::$user = $this->User->getAuthUser($existingUser['User']['id']); + if (isset(self::$user['gpgkey'])) unset(self::$user['gpgkey']); + } else if ($sync && !empty(self::$user)) { + $org = isset(self::$client['org']) ? self::$client['org'] : null; + if ($org == null) return false; + if (!isset(self::$user['org_id']) && isset(self::$user['org'])) { + self::$user['org_id'] = $this->User->Organisation->createOrgFromName($org, 0, true); + unset(self::$user['org']); + } + if ($userDefaults && is_array($userDefaults)) { + self::$user = array_merge(self::$user, $userDefaults); + } + $this->User->create(); + if ($this->User->save(self::$user)) { + $id = $this->User->id; + self::$user = $this->User->getAuthUser($id); + if (isset(self::$user['gpgkey'])) unset(self::$user['gpgkey']); + } else { + CakeLog::write('alert', 'Could not insert model at database from RestAPI data. Reason: ' . json_encode($this->User->validationErrors)); + } + } else { + // No match -- User doesn't exist !!! + self::$user = false; + } + } + } + return self::$user; + } - // to enable stateless authentication - public function authenticate(CakeRequest $request, CakeResponse $response) - { - return self::getUser($request); - } + // to enable stateless authentication + public function authenticate(CakeRequest $request, CakeResponse $response) + { + return self::getUser($request); + } - /** - * Fetches user information from external REST API - * - * Valid options (should be configured under CertAuth.restApi): - * - * @param (optional) array $options API configuration - * url (string) Where to fetch information from - * headers (array) list of additional headers to be used, reserved for authentication tokens - * params (array) mapping of additional params to be included at the url, uses $user values - * map (array) mapping of the return values to be added to the self::$user - * @return array updated user object - */ - public function getRestUser($options=null, $user=null) - { - if (is_null($options)) { - $options = Configure::read('CertAuth.restApi'); - } - if (!is_null($user)) { - self::$user = $user; - } + /** + * Fetches user information from external REST API + * + * Valid options (should be configured under CertAuth.restApi): + * + * @param (optional) array $options API configuration + * url (string) Where to fetch information from + * headers (array) list of additional headers to be used, reserved for authentication tokens + * params (array) mapping of additional params to be included at the url, uses $user values + * map (array) mapping of the return values to be added to the self::$user + * @return array updated user object + */ + public function getRestUser($options=null, $user=null) + { + if (is_null($options)) { + $options = Configure::read('CertAuth.restApi'); + } + if (!is_null($user)) { + self::$user = $user; + } - if (!isset($options['url'])) { - return null; - } + if (!isset($options['url'])) { + return null; + } - // Create a stream - $req = array( - 'http'=>array( - 'method'=>'GET', - 'header'=>"Accept: application/json\r\n" - ), - ); - if (isset($options['headers'])) { - foreach ($options['headers'] as $k=>$v) { - if (is_int($k)) { - $req['header'] .= "{$v}\r\n"; - } else { - $req['header'] .= "{$k}: {$v}\r\n"; - } - unset($k, $v); - } - } + // Create a stream + $req = array( + 'http'=>array( + 'method'=>'GET', + 'header'=>"Accept: application/json\r\n" + ), + ); + if (isset($options['headers'])) { + foreach ($options['headers'] as $k=>$v) { + if (is_int($k)) { + $req['header'] .= "{$v}\r\n"; + } else { + $req['header'] .= "{$k}: {$v}\r\n"; + } + unset($k, $v); + } + } - $url = $options['url']; - if (isset($options['param'])) { - foreach ($options['param'] as $k=>$v) { - if (isset(self::$user[$v])) { - $url .= ((strpos($url, '?'))?('&'):('?')) - . $k . '=' . urlencode(self::$user[$v]); - } - unset($k, $v); - } - } - $ctx = stream_context_create($req); - $a = file_get_contents($url, false, $ctx); - if (!$a) return null; + $url = $options['url']; + if (isset($options['param'])) { + foreach ($options['param'] as $k=>$v) { + if (isset(self::$user[$v])) { + $url .= ((strpos($url, '?'))?('&'):('?')) + . $k . '=' . urlencode(self::$user[$v]); + } + unset($k, $v); + } + } + $ctx = stream_context_create($req); + $url = mb_ereg_replace("/phar\:\/\//i", '', $url); + $a = file_get_contents($url, false, $ctx); + if (!$a) return null; - $A = json_decode($a, true); - if (!isset($A['data'][0])) { - self::$user = false; - } else if (isset($options['map'])) { - foreach ($options['map'] as $k=>$v) { - if (isset($A['data'][0][$k])) { - self::$user[$v] = $A['data'][0][$k]; - } - unset($k, $v); - } - } + $A = json_decode($a, true); + if (!isset($A['data'][0])) { + self::$user = false; + } else if (isset($options['map'])) { + foreach ($options['map'] as $k=>$v) { + if (isset($A['data'][0][$k])) { + self::$user[$v] = $A['data'][0][$k]; + } + unset($k, $v); + } + } - return self::$user; - } + return self::$user; + } - protected static $instance; + protected static $instance; - public static function ca() - { - if (is_null(self::$ca)) new CertificateAuthenticate(); - return self::$ca; - } + public static function ca() + { + if (is_null(self::$ca)) new CertificateAuthenticate(); + return self::$ca; + } - public static function client() - { - if (is_null(self::$client)) new CertificateAuthenticate(); - return self::$client; - } + public static function client() + { + if (is_null(self::$client)) new CertificateAuthenticate(); + return self::$client; + } } diff --git a/app/Plugin/SysLog/Lib/SysLog.php b/app/Plugin/SysLog/Lib/SysLog.php index 719300c6f..72fd443f4 100644 --- a/app/Plugin/SysLog/Lib/SysLog.php +++ b/app/Plugin/SysLog/Lib/SysLog.php @@ -38,9 +38,9 @@ class SysLog * @param array $options Options for the SysLog, see above. * @return void */ - public function __construct($options = array()) + public function __construct($options = []) { - $options += array('ident' => LOGS, 'facility' => LOG_LOCAL0, 'to_stderr' => true); + $options += ['ident' => LOGS, 'facility' => LOG_LOCAL0, 'to_stderr' => true]; $option = LOG_PID; // include PID with each message if ($options['to_stderr']) { $option |= LOG_PERROR; // print log message also to standard error @@ -51,7 +51,7 @@ class SysLog /** * Implements writing to the specified syslog * - * @param string $type The type of log you are making. + * @param int $type The type of log you are making. * @param string $message The message you want to log. * @return boolean success of write. */ @@ -60,14 +60,9 @@ class SysLog if (!$this->_log) { return false; } - $debugTypes = array('notice', 'info', 'debug'); - $priority = LOG_INFO; - if ($type == 'error' || $type == 'warning') { - $priority = LOG_ERR; - } else if (in_array($type, $debugTypes)) { - $priority = LOG_DEBUG; + if (!is_int($type)) { + throw new InvalidArgumentException("Invalid log type `$type`, must be one of LOG_* constant."); } - $output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message; - return syslog($priority, $output); + return syslog($type, $message); } } diff --git a/app/Test/CryptGpgExtendedTest.php b/app/Test/CryptGpgExtendedTest.php new file mode 100644 index 000000000..ce9a55e86 --- /dev/null +++ b/app/Test/CryptGpgExtendedTest.php @@ -0,0 +1,65 @@ +init(); + $this->assertInstanceOf('CryptGpgExtended', $gpg); + $this->assertIsString($gpg->getVersion()); + } + + public function testSignAndVerify() + { + $gpg = $this->init(); + include __DIR__ . '/../Config/config.php'; + $gpg->addSignKey($config['GnuPG']['email'], $config['GnuPG']['password']); + + $testString = 'ahojSvete'; + + $signature = $gpg->sign($testString, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_BINARY); + $this->assertIsString($signature); + + $verified = $gpg->verify($testString, $signature); + $this->assertIsArray($verified); + $this->assertCount(1, $verified); + $this->assertTrue($verified[0]->isValid()); + + $signature = $gpg->sign($testString, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_ASCII); + $this->assertIsString($signature); + + $verified = $gpg->verify($testString, $signature); + $this->assertIsArray($verified); + $this->assertCount(1, $verified); + $this->assertTrue($verified[0]->isValid()); + + // Tmp file + $tmpFile = new TmpFileTool(); + $tmpFile->write($testString); + $signature = $gpg->signFile($tmpFile, null, Crypt_GPG::SIGN_MODE_DETACHED, Crypt_GPG::ARMOR_BINARY); + $this->assertIsString($signature); + + $verified = $gpg->verify($testString, $signature); + $this->assertIsArray($verified); + $this->assertCount(1, $verified); + $this->assertTrue($verified[0]->isValid()); + } + + private function init(): CryptGpgExtended + { + require_once 'Crypt/GPG.php'; + include __DIR__ . '/../Config/config.php'; + + $options = [ + 'homedir' => $config['GnuPG']['homedir'], + 'gpgconf' => $config['GnuPG']['gpgconf'] ?? null, + 'binary' => $config['GnuPG']['binary'] ?? '/usr/bin/gpg', + ]; + return new CryptGpgExtended($options); + } +} diff --git a/app/Test/JSONConverterToolTest.php b/app/Test/JSONConverterToolTest.php index 983a90a30..5e7758148 100644 --- a/app/Test/JSONConverterToolTest.php +++ b/app/Test/JSONConverterToolTest.php @@ -35,12 +35,28 @@ class JSONConverterToolTest extends TestCase $this->check($event); } + public function testCheckJsonIsValidUnicodeSlashes(): void + { + $attribute = ['id' => 1, 'event_id' => 2, 'type' => 'ip-src', 'value' => '1.1.1.1']; + $event = ['Event' => ['id' => 2, 'info' => 'Test event ěšřžýáí \/'], 'errors' => 'chyba ě+š']; + for ($i = 0; $i < 5; $i++) { + $event['Attribute'][] = $attribute; + } + $this->check($event); + } + private function check(array $event): void { $json = ''; foreach (JSONConverterTool::streamConvert($event) as $part) { $json .= $part; } + + // Check if result is the same without spaces + $jsonStreamWithoutSpaces = preg_replace("/\s+/", "", $json); + $jsonNormalWithoutSpaces = preg_replace("/\s+/", "", JSONConverterTool::convert($event)); + $this->assertEquals($jsonNormalWithoutSpaces, $jsonStreamWithoutSpaces); + if (defined('JSON_THROW_ON_ERROR')) { json_decode($json, true, 512, JSON_THROW_ON_ERROR); $this->assertTrue(true); diff --git a/app/View/Servers/ajax/get_api_info.ctp b/app/View/Api/ajax/get_api_info.ctp similarity index 87% rename from app/View/Servers/ajax/get_api_info.ctp rename to app/View/Api/ajax/get_api_info.ctp index e6561bbf3..e5bcee0bd 100644 --- a/app/View/Servers/ajax/get_api_info.ctp +++ b/app/View/Api/ajax/get_api_info.ctp @@ -1,6 +1,6 @@
+

API info

API info'; foreach ($api_info as $key => $value) { if (!empty($value)) { if (is_array($value)) { @@ -30,11 +30,11 @@ } $temp[] = $fieldName . $infoHtml; } - $value = implode('
', $temp); + $value = implode('
', $temp); } else { $value = h($value); } - echo sprintf('%s:
%s
', ucfirst(h($key)), $value); + echo sprintf('%s:
%s
', ucfirst(h($key)), $value); } } ?> diff --git a/app/View/Servers/openapi.ctp b/app/View/Api/openapi.ctp similarity index 100% rename from app/View/Servers/openapi.ctp rename to app/View/Api/openapi.ctp diff --git a/app/View/Servers/rest.ctp b/app/View/Api/rest.ctp similarity index 91% rename from app/View/Servers/rest.ctp rename to app/View/Api/rest.ctp index b0eef465a..57715fbc8 100644 --- a/app/View/Servers/rest.ctp +++ b/app/View/Api/rest.ctp @@ -4,12 +4,12 @@ Query Builder None'; - foreach($allValidApisFormated as $scope => $actions) { - $options .= sprintf('', $scope); - foreach($actions as $action) { - $options .= sprintf('', $action['url'], $action['action']); - } + foreach ($allAccessibleApis as $scope => $actions) { + $options .= sprintf('', $scope); + foreach ($actions as $action => $url) { + $options .= sprintf('', $url, $action); } + } echo sprintf('', $options); ?>
@@ -63,7 +63,7 @@ 'class' => 'input-xxlarge' )); ?> -
+
Form->input('use_full_path', array( @@ -77,7 +77,7 @@ 'onChange' => 'toggleRestClientBookmark();' )); ?> -
+
-
+
Form->input('show_result', array( 'label' => __('Show result'), @@ -157,7 +157,7 @@ } echo '
'; } - if (!empty($data['data'])): ?> + if (isset($data['data'])): ?>

:
:
@@ -220,18 +220,6 @@ ) )); ?> - - - \ No newline at end of file diff --git a/app/View/Elements/sparkline_new.ctp b/app/View/Elements/sparkline_new.ctp new file mode 100644 index 000000000..4dd7142ec --- /dev/null +++ b/app/View/Elements/sparkline_new.ctp @@ -0,0 +1,6 @@ + +
+ + diff --git a/app/View/Emails/html/Custom/empty b/app/View/Emails/html/Custom/empty new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/app/View/Emails/html/Custom/empty @@ -0,0 +1 @@ + diff --git a/app/View/Emails/text/Custom/empty b/app/View/Emails/text/Custom/empty new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/app/View/Emails/text/Custom/empty @@ -0,0 +1 @@ + diff --git a/app/View/Emails/text/password_reset.ctp b/app/View/Emails/text/password_reset.ctp new file mode 100644 index 000000000..8dd972789 --- /dev/null +++ b/app/View/Emails/text/password_reset.ctp @@ -0,0 +1,14 @@ + 'MISP.contact', + '$org' => 'MISP.org', + '$misp' => 'MISP.baseurl' + ]; + foreach ($resolveVars as $k => $v) { + $v = Configure::read($v); + $body= str_replace($k, $v, $body); + } + echo $body; diff --git a/app/View/EventBlocklists/mass_delete.ctp b/app/View/EventBlocklists/mass_delete.ctp index 38f7f87a3..2fb591eb3 100644 --- a/app/View/EventBlocklists/mass_delete.ctp +++ b/app/View/EventBlocklists/mass_delete.ctp @@ -2,9 +2,9 @@ Form->create('EventBlocklist', array('style' => 'margin:0px;', 'id' => 'PromptForm')); echo $this->Form->input('ids', array( - 'type' => 'hidden', - 'div' => 'hidden', - 'value' => json_encode($event_ids) + 'type' => 'hidden', + 'div' => 'hidden', + 'value' => json_encode($event_ids), )); ?> @@ -13,7 +13,7 @@ @@ -23,7 +23,5 @@
- Yes +
- Form->end(); - ?> + Form->end(); ?>
diff --git a/app/View/EventReports/ajax/indexForEvent.ctp b/app/View/EventReports/ajax/indexForEvent.ctp index 127c2ed35..d0aaefe41 100644 --- a/app/View/EventReports/ajax/indexForEvent.ctp +++ b/app/View/EventReports/ajax/indexForEvent.ctp @@ -13,29 +13,29 @@ 'type' => 'simple', 'children' => array( array( - 'onClick' => 'openGenericModal', - 'onClickParams' => [$baseurl . '/eventReports/add/' . h($event_id)], + 'url' => $baseurl . '/eventReports/add/' . h($event_id), 'active' => true, 'text' => __('Add Event Report'), 'fa-icon' => 'plus', + 'class' => 'modal-open', 'requirement' => $canModify, ), array( - 'onClick' => 'openGenericModal', - 'onClickParams' => [$baseurl . '/eventReports/importReportFromUrl/' . h($event_id)], + 'url' => $baseurl . '/eventReports/importReportFromUrl/' . h($event_id), 'active' => true, 'text' => __('Import from URL'), 'title' => __('Content for this URL will be downloaded and converted to Markdown'), 'fa-icon' => 'link', + 'class' => 'modal-open', 'requirement' => $canModify && $importModuleEnabled, ), array( - 'onClick' => 'openGenericModal', - 'onClickParams' => [$baseurl . '/eventReports/reportFromEvent/' . h($event_id)], + 'url' => $baseurl . '/eventReports/reportFromEvent/' . h($event_id), 'active' => true, 'text' => __('Generate report from Event'), 'title' => __('Based on filters, create a report summarizing the event'), 'fa-icon' => 'list-alt', + 'class' => 'modal-open', 'requirement' => $canModify, ), ) @@ -114,18 +114,9 @@ 'icon' => 'trash', 'onclick' => 'simplePopup(\'' . $baseurl . '/event_reports/delete/[onclick_params_data_path]\');', 'onclick_params_data_path' => 'EventReport.id', - 'complex_requirement' => array( - 'function' => function ($row, $options) { - return ($options['me']['Role']['perm_site_admin'] || $options['me']['org_id'] == $options['datapath']['orgc']) && !$options['datapath']['deleted']; - }, - 'options' => array( - 'me' => $me, - 'datapath' => array( - 'orgc' => 'EventReport.orgc_id', - 'deleted' => 'EventReport.deleted' - ) - ) - ), + 'complex_requirement' => function (array $row) use ($me) { + return ($me['Role']['perm_site_admin'] || $me['org_id'] == $row['EventReport']['orgc_id']) && !$row['EventReport']['deleted']; + }, ), array( 'title' => __('Restore report'), @@ -134,25 +125,15 @@ 'icon' => 'trash-restore', 'postLink' => true, 'postLinkConfirm' => __('Are you sure you want to restore the Report?'), - 'complex_requirement' => array( - 'function' => function ($row, $options) { - return ($options['me']['Role']['perm_site_admin'] || $options['me']['org_id'] == $options['datapath']['orgc']) && $options['datapath']['deleted']; - }, - 'options' => array( - 'me' => $me, - 'datapath' => array( - 'orgc' => 'EventReport.orgc_id', - 'deleted' => 'EventReport.deleted' - ) - ) - ), + 'complex_requirement' => function (array $row) use ($me) { + return ($me['Role']['perm_site_admin'] || $me['org_id'] == $row['EventReport']['orgc_id']) && $row['EventReport']['deleted']; + } ), ) ) )); ?>
- diff --git a/app/View/Events/ajax/eventDeleteConfirmationForm.ctp b/app/View/Events/ajax/eventDeleteConfirmationForm.ctp index 3c65d8fbc..fc3907cb2 100644 --- a/app/View/Events/ajax/eventDeleteConfirmationForm.ctp +++ b/app/View/Events/ajax/eventDeleteConfirmationForm.ctp @@ -1,23 +1,22 @@
Form->create('Event', array('style' => 'margin:0px;', 'id' => 'PromptForm', 'url' => $baseurl . '/events/delete')); - echo $this->Form->hidden('id'); + echo $this->Form->hidden('id'); ?>
1) { - $message .= count($idArray) . ' Events?'; + $message = __('Are you sure you want to delete %s events?', count($idArray)); } else { - $message .= __(' Event #') . $idArray[0] . '?'; + $message = __('Are you sure you want to delete event #%s?', $idArray[0]); } ?> -

+

@@ -27,7 +26,5 @@
- +
-Form->end(); -?> +Form->end(); ?>
diff --git a/app/View/Events/ajax/eventPublishConfirmationForm.ctp b/app/View/Events/ajax/eventPublishConfirmationForm.ctp index 6da745041..b5b93403e 100644 --- a/app/View/Events/ajax/eventPublishConfirmationForm.ctp +++ b/app/View/Events/ajax/eventPublishConfirmationForm.ctp @@ -1,41 +1,40 @@
Form->create('Event', array('style' => 'margin:0px;', 'id' => 'PromptForm', 'url' => $baseurl . '/events/' . $type . '/' . $id)); - $extraTitle = ""; - if ($type == 'publish') $extraTitle = ' (no email)'; - $message = __('Publish Event%s', $extraTitle); + echo $this->Form->create('Event', ['style' => 'margin:0px;', 'id' => 'PromptForm', 'url' => $baseurl . '/events/' . $type . '/' . $id]); if ($type === 'unpublish') { - $message = __('Unpublish Event%s', $extraTitle); + $message = __('Unpublish Event'); + $buttonTitle = __('Unpublish'); + } else { + $extraTitle = $type === 'publish' ? ' (no email)' : ''; + $message = __('Publish Event%s', $extraTitle); + $buttonTitle = __('Publish'); } ?> - - +
- ' . __('Are you sure this event is complete and everyone should be informed?') . '

'; +

' . __('Are you sure you wish to unpublish the event?') . '

'; + echo __('Are you sure you wish to unpublish the event?'); } else if ($type === 'publishSightings') { - echo '

' . __('Are you sure you wish publish and synchronise all sightings attached to this event?') . '

'; + echo __('Are you sure you wish publish and synchronise all sightings attached to this event?'); } else { - echo '

' . __('Publish but do NOT send alert email? Only for minor changes!') . '

'; + echo __('Publish but do NOT send alert email? Only for minor changes!'); } - ?> + ?>

- + - +
- Form->end(); - ?> + Form->end(); ?>
diff --git a/app/View/Events/ajax/toggle_correlation.ctp b/app/View/Events/ajax/toggle_correlation.ctp index c2c7d3619..7b2d31aa9 100644 --- a/app/View/Events/ajax/toggle_correlation.ctp +++ b/app/View/Events/ajax/toggle_correlation.ctp @@ -17,7 +17,7 @@ @@ -27,7 +27,5 @@
- +
- Form->end(); - ?> + Form->end(); ?> diff --git a/app/View/Events/automation.ctp b/app/View/Events/automation.ctp index 510b893b8..0884c843b 100644 --- a/app/View/Events/automation.ctp +++ b/app/View/Events/automation.ctp @@ -6,7 +6,7 @@

here.', $baseurl . '/servers/openapi');?>

-
REST client to test your API queries against your MISP and export the resulting tuned queries as curl or python scripts.');?> +
REST client to test your API queries against your MISP and export the resulting tuned queries as curl or python scripts.');?> here.');?>

diff --git a/app/View/Events/filter_event_index.ctp b/app/View/Events/filter_event_index.ctp index bb0e0c22b..1cd9765fd 100644 --- a/app/View/Events/filter_event_index.ctp +++ b/app/View/Events/filter_event_index.ctp @@ -153,6 +153,12 @@ 'style' => 'display:none;width:424px;', 'div' => false )); + echo $this->Form->input('searchall', array( + 'label' => false, + 'class' => 'input-large', + 'style' => 'display:none;width:424px;', + 'div' => false + )); ?> @@ -167,7 +173,7 @@ $field): ?> @@ -242,9 +248,9 @@ var filtering = ; var operators = ["", ""]; -var allFields = ["published", "tag", "date", "eventinfo", "eventid", "threatlevel", "distribution", "sharinggroup", "analysis", "attribute", "hasproposal", "timestamp", "publishtimestamp"]; +var allFields = ["published", "tag", "date", "eventinfo", "eventid", "threatlevel", "distribution", "sharinggroup", "analysis", "attribute", "hasproposal", "timestamp", "publishtimestamp", "all"]; -var simpleFilters = ["tag", "eventinfo", "eventid", "threatlevel", "distribution", "sharinggroup", "analysis", "attribute"]; +var simpleFilters = ["tag", "eventinfo", "eventid", "threatlevel", "distribution", "sharinggroup", "analysis", "attribute", "all"]; var differentFilters = ["published", "date", "hasproposal", "timestamp", "publishtimestamp"]; diff --git a/app/View/Events/free_text_import.ctp b/app/View/Events/free_text_import.ctp index f1b7cc6ec..0d73584a2 100644 --- a/app/View/Events/free_text_import.ctp +++ b/app/View/Events/free_text_import.ctp @@ -1,7 +1,5 @@
-Form->create('Attribute', array('id')); -?> +Form->create('Attribute', array('id')); ?>
@@ -9,35 +7,32 @@ echo $this->Form->create('Attribute', array('id')); Form->hidden('event_id'); echo $this->Form->input('value', array( - 'type' => 'textarea', - 'error' => array('escape' => false), - 'div' => 'input clear', - 'class' => 'input-xxlarge', - 'label' => false + 'type' => 'textarea', + 'error' => array('escape' => false), + 'div' => 'input clear', + 'class' => 'input-xxlarge', + 'label' => false )); ?>
- -
- - - - - - -
- - - - - -
-
- Form->end(); - ?> +
+ + + + + + +
+ + + + + +
+
+ Form->end(); ?>
-Js->writeBuffer(); // Write cached scripts diff --git a/app/View/Events/index.ctp b/app/View/Events/index.ctp index ff0cc6770..bcb858c95 100644 --- a/app/View/Events/index.ctp +++ b/app/View/Events/index.ctp @@ -135,6 +135,7 @@ 'button' => __('Filter'), 'placeholder' => __('Enter value to search'), 'data' => '', + 'searchKey' => 'searcheventinfo', ) ) ); diff --git a/app/View/Events/resolved_attributes.ctp b/app/View/Events/resolved_attributes.ctp index 9461602cb..0e39eabd7 100644 --- a/app/View/Events/resolved_attributes.ctp +++ b/app/View/Events/resolved_attributes.ctp @@ -9,7 +9,6 @@ echo sprintf('

%s

', $missingTldLists); } ?> -

Form->create('Attribute', array('url' => $baseurl . '/events/saveFreeText/' . $event['Event']['id'], 'class' => 'mainForm')); if ($isSiteAdmin) { echo $this->Form->input('force', array( - 'checked' => false, - 'label' => __('Proposals instead of attributes'), + 'checked' => false, + 'label' => __('Proposals instead of attributes'), )); } echo $this->Form->input('JsonObject', array( - 'label' => false, - 'type' => 'text', - 'style' => 'display:none;', - 'value' => '', + 'label' => false, + 'type' => 'text', + 'style' => 'display:none;', + 'value' => '', )); echo $this->Form->input('default_comment', array( - 'label' => false, - 'type' => 'text', - 'style' => 'display:none;', - 'value' => $importComment, + 'label' => false, + 'type' => 'text', + 'style' => 'display:none;', + 'value' => $importComment, )); echo $this->Form->end(); ?> - - - - - - - - - - + + + + + + + + + + " class="freetext_row"> Form->input('Attribute' . $k . 'Save', array( - 'label' => false, - 'style' => 'display:none;', - 'value' => 1, + 'label' => false, + 'style' => 'display:none;', + 'value' => 1, )); echo $this->Form->input('Attribute' . $k . 'Data', array( - 'label' => false, - 'type' => 'hidden', - 'value' => isset($item['data']) ? $item['data'] : false, + 'label' => false, + 'type' => 'hidden', + 'value' => isset($item['data']) ? $item['data'] : false, )); echo $this->Form->input('Attribute' . $k . 'DataIsHandled', array( - 'label' => false, - 'type' => 'hidden', - 'value' => isset($item['data_is_handled']) ? h($item['data_is_handled']) : false, + 'label' => false, + 'type' => 'hidden', + 'value' => isset($item['data_is_handled']) ? h($item['data_is_handled']) : false, )); ?>
Form->input('Attribute' . $k . 'Value', array( - 'label' => false, - 'value' => $item['value'], - 'style' => 'padding:0px;height:20px;margin-bottom:0px;width:90%;min-width:400px;', - 'div' => false + 'label' => false, + 'value' => $item['value'], + 'style' => 'padding:0px;height:20px;margin-bottom:0px;width:90%;min-width:400px;', + 'div' => false )); ?> - + ' . $category . ''; } ?> @@ -140,33 +139,35 @@ -
>
- ' . h($type) . ''; + echo '' . h($type) . ''; } } ?>
- class="idsCheckbox" /> + class="idsCheckbox"> - class="dcCheckbox" /> + class="dcCheckbox"> - $distValue) { $default = isset($item['distribution']) ? $item['distribution'] : $instanceDefault; @@ -176,7 +177,7 @@ ?>
- $sgValue) { echo ''; @@ -186,13 +187,13 @@
- /> + > - /> + > - +
- + - + @@ -244,7 +245,7 @@ -element('/genericElements/SideMenu/side_menu', array('menuList' => 'event', 'menuItem' => 'freetextResults')); -?> +element('/genericElements/SideMenu/side_menu', array('menuList' => 'event', 'menuItem' => 'freetextResults')); diff --git a/app/View/Events/view.ctp b/app/View/Events/view.ctp index bfda87709..5bded4cd5 100644 --- a/app/View/Events/view.ctp +++ b/app/View/Events/view.ctp @@ -1,16 +1,12 @@ $mayPublish, 'mayModify' => $mayModify]); - $scripts = ['doT', 'extendext', 'moment.min', 'query-builder', 'network-distribution-graph']; - echo $this->element('genericElements/assetLoader', array( + echo $this->element('genericElements/assetLoader', [ 'css' => ['query-builder.default', 'attack_matrix'], - 'js' => ['doT', 'extendext', 'moment.min', 'query-builder', 'network-distribution-graph'] - )); + 'js' => ['doT', 'extendext', 'moment.min', 'query-builder', 'network-distribution-graph', 'd3', 'd3.custom'], + ]); echo $this->element( 'genericElements/SingleViews/single_view', [ - 'title' => ($extended ? '[' . __('Extended view') . '] ' : '') . h(nl2br($event['Event']['info'])), + 'title' => ($extended ? '[' . __('Extended view') . '] ' : '') . h(nl2br($event['Event']['info'], false)), 'data' => $event, 'fields' => [ [ @@ -182,7 +178,7 @@ ], [ 'key' => __('#Attributes'), - 'raw' => $attribute_count . __n(' (%s Object)', ' (%s Objects)', $object_count, h($object_count)) + 'raw' => __n('%s (%s Object)', '%s (%s Objects)', $object_count, $attribute_count, h($object_count)) ], [ 'key' => __('First recorded change'), @@ -195,7 +191,7 @@ [ 'key' => __('Modification map'), 'type' => 'element', - 'element' => 'sparkline', + 'element' => 'sparkline_new', 'element_params' => [ 'scope' => 'modification', 'id' => $event['Event']['id'], @@ -231,7 +227,7 @@ [ 'key' => __('Activity'), 'type' => 'element', - 'element' => 'sparkline', + 'element' => 'sparkline_new', 'element_params' => [ 'scope' => 'event', 'id' => $event['Event']['id'], diff --git a/app/View/Feeds/ajax/feedToggleConfirmation.ctp b/app/View/Feeds/ajax/feedToggleConfirmation.ctp index facc1e4c0..ba5160175 100644 --- a/app/View/Feeds/ajax/feedToggleConfirmation.ctp +++ b/app/View/Feeds/ajax/feedToggleConfirmation.ctp @@ -8,7 +8,7 @@ @@ -18,7 +18,5 @@
- Yes +
- Form->end(); - ?> + Form->end(); ?> diff --git a/app/View/GalaxyClusters/ajax/publishConfirmationForm.ctp b/app/View/GalaxyClusters/ajax/publishConfirmationForm.ctp index e18935307..3f5a2cef4 100644 --- a/app/View/GalaxyClusters/ajax/publishConfirmationForm.ctp +++ b/app/View/GalaxyClusters/ajax/publishConfirmationForm.ctp @@ -22,7 +22,7 @@ @@ -32,7 +32,5 @@
- +
- Form->end(); - ?> + Form->end(); ?> diff --git a/app/View/GalaxyClusters/view.ctp b/app/View/GalaxyClusters/view.ctp index 21ebc6021..64a318d69 100755 --- a/app/View/GalaxyClusters/view.ctp +++ b/app/View/GalaxyClusters/view.ctp @@ -56,7 +56,7 @@ $table_data[] = array('key' => __('Collection UUID'), 'value' => $cluster['Galax $table_data[] = array( 'key' => __('Source'), 'html' => filter_var($cluster['GalaxyCluster']['source'], FILTER_VALIDATE_URL) ? - '' . h($cluster['GalaxyCluster']['source']) : + '' . h($cluster['GalaxyCluster']['source']) : h($cluster['GalaxyCluster']['source']), ); $table_data[] = array('key' => __('Authors'), 'value' => !empty($cluster['GalaxyCluster']['authors']) ? implode(', ', $cluster['GalaxyCluster']['authors']) : __('N/A')); diff --git a/app/View/Helper/TimeHelper.php b/app/View/Helper/TimeHelper.php index cbeb18552..4d729aebc 100644 --- a/app/View/Helper/TimeHelper.php +++ b/app/View/Helper/TimeHelper.php @@ -26,4 +26,17 @@ class TimeHelper extends AppHelper return ''; } + + /** + * @param int $date + * @return string + */ + public function date($date) + { + if (empty($date)) { + return ''; + } + $date = date('Y-m-d', $date); + return ''; + } } diff --git a/app/View/Layouts/dashboard.ctp b/app/View/Layouts/dashboard.ctp index 479080ccb..1d7949df4 100644 --- a/app/View/Layouts/dashboard.ctp +++ b/app/View/Layouts/dashboard.ctp @@ -14,7 +14,6 @@ 'bootstrap-datepicker', 'bootstrap-colorpicker', 'font-awesome', - 'jquery-ui', 'chosen.min', 'main', 'gridstack.min', @@ -26,7 +25,6 @@ $js_collection = array( 'jquery', 'misp-touch', - 'jquery-ui', 'chosen.jquery.min', 'gridstack.all' ); @@ -76,6 +74,7 @@ 'bootstrap-datepicker', 'bootstrap-colorpicker', 'misp', + 'keyboard-shortcuts-definition', 'keyboard-shortcuts' ) )); diff --git a/app/View/Layouts/default.ctp b/app/View/Layouts/default.ctp index ae7fbd82f..91c48999a 100644 --- a/app/View/Layouts/default.ctp +++ b/app/View/Layouts/default.ctp @@ -12,7 +12,6 @@ 'bootstrap-datepicker', 'bootstrap-colorpicker', 'font-awesome', - 'jquery-ui', 'chosen.min', 'main', array('print', array('media' => 'print')) @@ -23,7 +22,6 @@ $js_collection = array( 'jquery', 'misp-touch', - 'jquery-ui', 'chosen.jquery.min' ); echo $this->element('genericElements/assetLoader', array( diff --git a/app/View/Objects/ajax/objectViewFieldForm.ctp b/app/View/Objects/ajax/objectViewFieldForm.ctp index 236555c43..00c6d4703 100644 --- a/app/View/Objects/ajax/objectViewFieldForm.ctp +++ b/app/View/Objects/ajax/objectViewFieldForm.ctp @@ -1,2 +1,6 @@ Time->date($value); +} else { + echo nl2br(h($value), false); +} diff --git a/app/View/RestClientHistory/index.ctp b/app/View/RestClientHistory/index.ctp index a753cc0cc..28e96f4f2 100644 --- a/app/View/RestClientHistory/index.ctp +++ b/app/View/RestClientHistory/index.ctp @@ -2,46 +2,46 @@ $data_container = $bookmarked ? 'rest_client_bookmarks' : 'rest_client_history'; foreach ($list as $k => $item) { $name = ''; - if (!empty($item['RestClientHistory']['bookmark_name'])) { + if (!empty($item['bookmark_name'])) { $name = sprintf( '%s - ', - h($item['RestClientHistory']['bookmark_name']) + h($item['bookmark_name']) ); } $name .= sprintf( '%s - %s', - h($item['RestClientHistory']['http_method']), + h($item['http_method']), sprintf( - '%s', + '%s', $k, $data_container, - h($item['RestClientHistory']['url']) + h($item['url']) ) ); - $colour = 'green'; - if (intval($item['RestClientHistory']['outcome']) >= 300) { - $colour = 'orange'; - } - if (intval($item['RestClientHistory']['outcome']) >= 400) { + if (intval($item['outcome']) >= 400) { $colour = 'red'; + } else if (intval($item['outcome']) >= 300) { + $colour = 'orange'; + } else { + $colour = 'green'; } echo sprintf( '
(%s) %s %s
', sprintf( "URL: %s\n\nHeaders: %s\n\nBody: %s", - h($item['RestClientHistory']['url']), - h($item['RestClientHistory']['headers']), - h($item['RestClientHistory']['body']) + h($item['url']), + h($item['headers']), + h($item['body']) ), sprintf( '%s', $colour, - h($item['RestClientHistory']['outcome']) + h($item['outcome']) ), $name, sprintf( '', - h($item['RestClientHistory']['id']) + h($item['id']) ) ); } diff --git a/app/View/TagCollections/get_row.ctp b/app/View/TagCollections/get_row.ctp index a52ff938e..7d196df1a 100644 --- a/app/View/TagCollections/get_row.ctp +++ b/app/View/TagCollections/get_row.ctp @@ -1,11 +1,3 @@ element('TagCollections/index_row'); ?> - - diff --git a/app/View/TagCollections/index.ctp b/app/View/TagCollections/index.ctp index 5253edd6e..7d4d0195a 100755 --- a/app/View/TagCollections/index.ctp +++ b/app/View/TagCollections/index.ctp @@ -19,11 +19,4 @@ )); echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'tag-collections', 'menuItem' => 'index')); -?> - + diff --git a/app/View/Taxonomies/ajax/taxonomy_mass_confirmation.ctp b/app/View/Taxonomies/ajax/taxonomy_mass_confirmation.ctp index 756321443..83d8d8c80 100644 --- a/app/View/Taxonomies/ajax/taxonomy_mass_confirmation.ctp +++ b/app/View/Taxonomies/ajax/taxonomy_mass_confirmation.ctp @@ -16,7 +16,7 @@ @@ -27,11 +27,9 @@
- +
-Form->end(); -?> +Form->end(); ?> diff --git a/app/View/Taxonomies/ajax/taxonomy_mass_hide.ctp b/app/View/Taxonomies/ajax/taxonomy_mass_hide.ctp index c24290509..fa174f329 100644 --- a/app/View/Taxonomies/ajax/taxonomy_mass_hide.ctp +++ b/app/View/Taxonomies/ajax/taxonomy_mass_hide.ctp @@ -16,7 +16,7 @@ @@ -27,12 +27,10 @@
- +
-Form->end(); -?> +Form->end(); ?> diff --git a/app/View/Taxonomies/ajax/taxonomy_mass_unhide.ctp b/app/View/Taxonomies/ajax/taxonomy_mass_unhide.ctp index 942ddb086..ed3a15de0 100644 --- a/app/View/Taxonomies/ajax/taxonomy_mass_unhide.ctp +++ b/app/View/Taxonomies/ajax/taxonomy_mass_unhide.ctp @@ -16,7 +16,7 @@ @@ -27,12 +27,10 @@
- +
-Form->end(); -?> +Form->end(); ?> diff --git a/app/View/Taxonomies/index.ctp b/app/View/Taxonomies/index.ctp index 0851fb19f..f2aefb7f0 100644 --- a/app/View/Taxonomies/index.ctp +++ b/app/View/Taxonomies/index.ctp @@ -72,7 +72,8 @@ ), 'sort' => 'required', 'class' => 'short', - 'data_path' => 'Taxonomy.required' + 'data_path' => 'Taxonomy.required', + 'disabled' => !$isSiteAdmin, ), array( 'name' => __('Active Tags'), diff --git a/app/View/Users/login.ctp b/app/View/Users/login.ctp index a6ec52534..b8433df97 100644 --- a/app/View/Users/login.ctp +++ b/app/View/Users/login.ctp @@ -45,7 +45,7 @@ echo sprintf( '%s LinOTP Selfservice %s', __('Visit'), - Configure::read('LinOTPAuth.baseUrl'), + h(Configure::read('LinOTPAuth.baseUrl')), __('for the One-Time-Password selfservice.') ); } @@ -92,7 +92,10 @@ function submitLoginForm() { var url = $form.attr('action') var email = $form.find('#UserEmail').val() var password = $form.find('#UserPassword').val() - if (!empty(Configure::read('LinOTPAuth')) && Configure::read('LinOTPAuth.enabled')) { + var LinOTPAuth = ; + var LinOTPAuthEnabled = ; + + if (LinOTPAuth && LinOTPAuthEnabled) { var otp = $form.find('#UserOtp').val() } if (!$form[0].checkValidity()) { @@ -107,7 +110,7 @@ function submitLoginForm() { var $tmpForm = $('#temp form#UserLoginForm') $tmpForm.find('#UserEmail').val(email) $tmpForm.find('#UserPassword').val(password) - if (!empty(Configure::read('LinOTPAuth')) && Configure::read('LinOTPAuth.enabled')) { + if (LinOTPAuth && LinOTPAuthEnabled) { $tmpForm.find('#UserOtp').val(otp) } $tmpForm.submit() diff --git a/app/webroot/css/jquery-ui.css b/app/webroot/css/jquery-ui.css deleted file mode 100644 index b3804dc08..000000000 --- a/app/webroot/css/jquery-ui.css +++ /dev/null @@ -1,1312 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2020-03-11 -* http://jqueryui.com -* Includes: draggable.css, core.css, resizable.css, selectable.css, sortable.css, accordion.css, autocomplete.css, menu.css, button.css, controlgroup.css, checkboxradio.css, datepicker.css, dialog.css, progressbar.css, selectmenu.css, slider.css, spinner.css, tabs.css, tooltip.css, theme.css -* To view and modify this theme, visit http://jqueryui.com/themeroller/?scope=&folderName=base&cornerRadiusShadow=8px&offsetLeftShadow=0px&offsetTopShadow=0px&thicknessShadow=5px&opacityShadow=30&bgImgOpacityShadow=0&bgTextureShadow=flat&bgColorShadow=666666&opacityOverlay=30&bgImgOpacityOverlay=0&bgTextureOverlay=flat&bgColorOverlay=aaaaaa&iconColorError=cc0000&fcError=5f3f3f&borderColorError=f1a899&bgTextureError=flat&bgColorError=fddfdf&iconColorHighlight=777620&fcHighlight=777620&borderColorHighlight=dad55e&bgTextureHighlight=flat&bgColorHighlight=fffa90&iconColorActive=ffffff&fcActive=ffffff&borderColorActive=003eff&bgTextureActive=flat&bgColorActive=007fff&iconColorHover=555555&fcHover=2b2b2b&borderColorHover=cccccc&bgTextureHover=flat&bgColorHover=ededed&iconColorDefault=777777&fcDefault=454545&borderColorDefault=c5c5c5&bgTextureDefault=flat&bgColorDefault=f6f6f6&iconColorContent=444444&fcContent=333333&borderColorContent=dddddd&bgTextureContent=flat&bgColorContent=ffffff&iconColorHeader=444444&fcHeader=333333&borderColorHeader=dddddd&bgTextureHeader=flat&bgColorHeader=e9e9e9&cornerRadius=3px&fwDefault=normal&fsDefault=1em&ffDefault=Arial%2CHelvetica%2Csans-serif -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -.ui-draggable-handle { - -ms-touch-action: none; - touch-action: none; -} -/* Layout helpers -----------------------------------*/ -.ui-helper-hidden { - display: none; -} -.ui-helper-hidden-accessible { - border: 0; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; -} -.ui-helper-reset { - margin: 0; - padding: 0; - border: 0; - outline: 0; - line-height: 1.3; - text-decoration: none; - font-size: 100%; - list-style: none; -} -.ui-helper-clearfix:before, -.ui-helper-clearfix:after { - content: ""; - display: table; - border-collapse: collapse; -} -.ui-helper-clearfix:after { - clear: both; -} -.ui-helper-zfix { - width: 100%; - height: 100%; - top: 0; - left: 0; - position: absolute; - opacity: 0; - filter:Alpha(Opacity=0); /* support: IE8 */ -} - -.ui-front { - z-index: 100; -} - - -/* Interaction Cues -----------------------------------*/ -.ui-state-disabled { - cursor: default !important; - pointer-events: none; -} - - -/* Icons -----------------------------------*/ -.ui-icon { - display: inline-block; - vertical-align: middle; - margin-top: -.25em; - position: relative; - text-indent: -99999px; - overflow: hidden; - background-repeat: no-repeat; -} - -.ui-widget-icon-block { - left: 50%; - margin-left: -8px; - display: block; -} - -/* Misc visuals -----------------------------------*/ - -/* Overlays */ -.ui-widget-overlay { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; -} -.ui-resizable { - position: relative; -} -.ui-resizable-handle { - position: absolute; - font-size: 0.1px; - display: block; - -ms-touch-action: none; - touch-action: none; -} -.ui-resizable-disabled .ui-resizable-handle, -.ui-resizable-autohide .ui-resizable-handle { - display: none; -} -.ui-resizable-n { - cursor: n-resize; - height: 7px; - width: 100%; - top: -5px; - left: 0; -} -.ui-resizable-s { - cursor: s-resize; - height: 7px; - width: 100%; - bottom: -5px; - left: 0; -} -.ui-resizable-e { - cursor: e-resize; - width: 7px; - right: -5px; - top: 0; - height: 100%; -} -.ui-resizable-w { - cursor: w-resize; - width: 7px; - left: -5px; - top: 0; - height: 100%; -} -.ui-resizable-se { - cursor: se-resize; - width: 12px; - height: 12px; - right: 1px; - bottom: 1px; -} -.ui-resizable-sw { - cursor: sw-resize; - width: 9px; - height: 9px; - left: -5px; - bottom: -5px; -} -.ui-resizable-nw { - cursor: nw-resize; - width: 9px; - height: 9px; - left: -5px; - top: -5px; -} -.ui-resizable-ne { - cursor: ne-resize; - width: 9px; - height: 9px; - right: -5px; - top: -5px; -} -.ui-selectable { - -ms-touch-action: none; - touch-action: none; -} -.ui-selectable-helper { - position: absolute; - z-index: 100; - border: 1px dotted black; -} -.ui-sortable-handle { - -ms-touch-action: none; - touch-action: none; -} -.ui-accordion .ui-accordion-header { - display: block; - cursor: pointer; - position: relative; - margin: 2px 0 0 0; - padding: .5em .5em .5em .7em; - font-size: 100%; -} -.ui-accordion .ui-accordion-content { - padding: 1em 2.2em; - border-top: 0; - overflow: auto; -} -.ui-autocomplete { - position: absolute; - top: 0; - left: 0; - cursor: default; -} -.ui-menu { - list-style: none; - padding: 0; - margin: 0; - display: block; - outline: 0; -} -.ui-menu .ui-menu { - position: absolute; -} -.ui-menu .ui-menu-item { - margin: 0; - cursor: pointer; - /* support: IE10, see #8844 */ - list-style-image: url("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"); -} -.ui-menu .ui-menu-item-wrapper { - position: relative; - padding: 3px 1em 3px .4em; -} -.ui-menu .ui-menu-divider { - margin: 5px 0; - height: 0; - font-size: 0; - line-height: 0; - border-width: 1px 0 0 0; -} -.ui-menu .ui-state-focus, -.ui-menu .ui-state-active { - margin: -1px; -} - -/* icon support */ -.ui-menu-icons { - position: relative; -} -.ui-menu-icons .ui-menu-item-wrapper { - padding-left: 2em; -} - -/* left-aligned */ -.ui-menu .ui-icon { - position: absolute; - top: 0; - bottom: 0; - left: .2em; - margin: auto 0; -} - -/* right-aligned */ -.ui-menu .ui-menu-icon { - left: auto; - right: 0; -} -.ui-button { - padding: .4em 1em; - display: inline-block; - position: relative; - line-height: normal; - margin-right: .1em; - cursor: pointer; - vertical-align: middle; - text-align: center; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; - - /* Support: IE <= 11 */ - overflow: visible; -} - -.ui-button, -.ui-button:link, -.ui-button:visited, -.ui-button:hover, -.ui-button:active { - text-decoration: none; -} - -/* to make room for the icon, a width needs to be set here */ -.ui-button-icon-only { - width: 2em; - box-sizing: border-box; - text-indent: -9999px; - white-space: nowrap; -} - -/* no icon support for input elements */ -input.ui-button.ui-button-icon-only { - text-indent: 0; -} - -/* button icon element(s) */ -.ui-button-icon-only .ui-icon { - position: absolute; - top: 50%; - left: 50%; - margin-top: -8px; - margin-left: -8px; -} - -.ui-button.ui-icon-notext .ui-icon { - padding: 0; - width: 2.1em; - height: 2.1em; - text-indent: -9999px; - white-space: nowrap; - -} - -input.ui-button.ui-icon-notext .ui-icon { - width: auto; - height: auto; - text-indent: 0; - white-space: normal; - padding: .4em 1em; -} - -/* workarounds */ -/* Support: Firefox 5 - 40 */ -input.ui-button::-moz-focus-inner, -button.ui-button::-moz-focus-inner { - border: 0; - padding: 0; -} -.ui-controlgroup { - vertical-align: middle; - display: inline-block; -} -.ui-controlgroup > .ui-controlgroup-item { - float: left; - margin-left: 0; - margin-right: 0; -} -.ui-controlgroup > .ui-controlgroup-item:focus, -.ui-controlgroup > .ui-controlgroup-item.ui-visual-focus { - z-index: 9999; -} -.ui-controlgroup-vertical > .ui-controlgroup-item { - display: block; - float: none; - width: 100%; - margin-top: 0; - margin-bottom: 0; - text-align: left; -} -.ui-controlgroup-vertical .ui-controlgroup-item { - box-sizing: border-box; -} -.ui-controlgroup .ui-controlgroup-label { - padding: .4em 1em; -} -.ui-controlgroup .ui-controlgroup-label span { - font-size: 80%; -} -.ui-controlgroup-horizontal .ui-controlgroup-label + .ui-controlgroup-item { - border-left: none; -} -.ui-controlgroup-vertical .ui-controlgroup-label + .ui-controlgroup-item { - border-top: none; -} -.ui-controlgroup-horizontal .ui-controlgroup-label.ui-widget-content { - border-right: none; -} -.ui-controlgroup-vertical .ui-controlgroup-label.ui-widget-content { - border-bottom: none; -} - -/* Spinner specific style fixes */ -.ui-controlgroup-vertical .ui-spinner-input { - - /* Support: IE8 only, Android < 4.4 only */ - width: 75%; - width: calc( 100% - 2.4em ); -} -.ui-controlgroup-vertical .ui-spinner .ui-spinner-up { - border-top-style: solid; -} - -.ui-checkboxradio-label .ui-icon-background { - box-shadow: inset 1px 1px 1px #ccc; - border-radius: .12em; - border: none; -} -.ui-checkboxradio-radio-label .ui-icon-background { - width: 16px; - height: 16px; - border-radius: 1em; - overflow: visible; - border: none; -} -.ui-checkboxradio-radio-label.ui-checkboxradio-checked .ui-icon, -.ui-checkboxradio-radio-label.ui-checkboxradio-checked:hover .ui-icon { - background-image: none; - width: 8px; - height: 8px; - border-width: 4px; - border-style: solid; -} -.ui-checkboxradio-disabled { - pointer-events: none; -} -.ui-datepicker { - width: 17em; - padding: .2em .2em 0; - display: none; -} -.ui-datepicker .ui-datepicker-header { - position: relative; - padding: .2em 0; -} -.ui-datepicker .ui-datepicker-prev, -.ui-datepicker .ui-datepicker-next { - position: absolute; - top: 2px; - width: 1.8em; - height: 1.8em; -} -.ui-datepicker .ui-datepicker-prev-hover, -.ui-datepicker .ui-datepicker-next-hover { - top: 1px; -} -.ui-datepicker .ui-datepicker-prev { - left: 2px; -} -.ui-datepicker .ui-datepicker-next { - right: 2px; -} -.ui-datepicker .ui-datepicker-prev-hover { - left: 1px; -} -.ui-datepicker .ui-datepicker-next-hover { - right: 1px; -} -.ui-datepicker .ui-datepicker-prev span, -.ui-datepicker .ui-datepicker-next span { - display: block; - position: absolute; - left: 50%; - margin-left: -8px; - top: 50%; - margin-top: -8px; -} -.ui-datepicker .ui-datepicker-title { - margin: 0 2.3em; - line-height: 1.8em; - text-align: center; -} -.ui-datepicker .ui-datepicker-title select { - font-size: 1em; - margin: 1px 0; -} -.ui-datepicker select.ui-datepicker-month, -.ui-datepicker select.ui-datepicker-year { - width: 45%; -} -.ui-datepicker table { - width: 100%; - font-size: .9em; - border-collapse: collapse; - margin: 0 0 .4em; -} -.ui-datepicker th { - padding: .7em .3em; - text-align: center; - font-weight: bold; - border: 0; -} -.ui-datepicker td { - border: 0; - padding: 1px; -} -.ui-datepicker td span, -.ui-datepicker td a { - display: block; - padding: .2em; - text-align: right; - text-decoration: none; -} -.ui-datepicker .ui-datepicker-buttonpane { - background-image: none; - margin: .7em 0 0 0; - padding: 0 .2em; - border-left: 0; - border-right: 0; - border-bottom: 0; -} -.ui-datepicker .ui-datepicker-buttonpane button { - float: right; - margin: .5em .2em .4em; - cursor: pointer; - padding: .2em .6em .3em .6em; - width: auto; - overflow: visible; -} -.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { - float: left; -} - -/* with multiple calendars */ -.ui-datepicker.ui-datepicker-multi { - width: auto; -} -.ui-datepicker-multi .ui-datepicker-group { - float: left; -} -.ui-datepicker-multi .ui-datepicker-group table { - width: 95%; - margin: 0 auto .4em; -} -.ui-datepicker-multi-2 .ui-datepicker-group { - width: 50%; -} -.ui-datepicker-multi-3 .ui-datepicker-group { - width: 33.3%; -} -.ui-datepicker-multi-4 .ui-datepicker-group { - width: 25%; -} -.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header, -.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { - border-left-width: 0; -} -.ui-datepicker-multi .ui-datepicker-buttonpane { - clear: left; -} -.ui-datepicker-row-break { - clear: both; - width: 100%; - font-size: 0; -} - -/* RTL support */ -.ui-datepicker-rtl { - direction: rtl; -} -.ui-datepicker-rtl .ui-datepicker-prev { - right: 2px; - left: auto; -} -.ui-datepicker-rtl .ui-datepicker-next { - left: 2px; - right: auto; -} -.ui-datepicker-rtl .ui-datepicker-prev:hover { - right: 1px; - left: auto; -} -.ui-datepicker-rtl .ui-datepicker-next:hover { - left: 1px; - right: auto; -} -.ui-datepicker-rtl .ui-datepicker-buttonpane { - clear: right; -} -.ui-datepicker-rtl .ui-datepicker-buttonpane button { - float: left; -} -.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current, -.ui-datepicker-rtl .ui-datepicker-group { - float: right; -} -.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header, -.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { - border-right-width: 0; - border-left-width: 1px; -} - -/* Icons */ -.ui-datepicker .ui-icon { - display: block; - text-indent: -99999px; - overflow: hidden; - background-repeat: no-repeat; - left: .5em; - top: .3em; -} -.ui-dialog { - position: absolute; - top: 0; - left: 0; - padding: .2em; - outline: 0; -} -.ui-dialog .ui-dialog-titlebar { - padding: .4em 1em; - position: relative; -} -.ui-dialog .ui-dialog-title { - float: left; - margin: .1em 0; - white-space: nowrap; - width: 90%; - overflow: hidden; - text-overflow: ellipsis; -} -.ui-dialog .ui-dialog-titlebar-close { - position: absolute; - right: .3em; - top: 50%; - width: 20px; - margin: -10px 0 0 0; - padding: 1px; - height: 20px; -} -.ui-dialog .ui-dialog-content { - position: relative; - border: 0; - padding: .5em 1em; - background: none; - overflow: auto; -} -.ui-dialog .ui-dialog-buttonpane { - text-align: left; - border-width: 1px 0 0 0; - background-image: none; - margin-top: .5em; - padding: .3em 1em .5em .4em; -} -.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { - float: right; -} -.ui-dialog .ui-dialog-buttonpane button { - margin: .5em .4em .5em 0; - cursor: pointer; -} -.ui-dialog .ui-resizable-n { - height: 2px; - top: 0; -} -.ui-dialog .ui-resizable-e { - width: 2px; - right: 0; -} -.ui-dialog .ui-resizable-s { - height: 2px; - bottom: 0; -} -.ui-dialog .ui-resizable-w { - width: 2px; - left: 0; -} -.ui-dialog .ui-resizable-se, -.ui-dialog .ui-resizable-sw, -.ui-dialog .ui-resizable-ne, -.ui-dialog .ui-resizable-nw { - width: 7px; - height: 7px; -} -.ui-dialog .ui-resizable-se { - right: 0; - bottom: 0; -} -.ui-dialog .ui-resizable-sw { - left: 0; - bottom: 0; -} -.ui-dialog .ui-resizable-ne { - right: 0; - top: 0; -} -.ui-dialog .ui-resizable-nw { - left: 0; - top: 0; -} -.ui-draggable .ui-dialog-titlebar { - cursor: move; -} -.ui-progressbar { - height: 2em; - text-align: left; - overflow: hidden; -} -.ui-progressbar .ui-progressbar-value { - margin: -1px; - height: 100%; -} -.ui-progressbar .ui-progressbar-overlay { - background: url("data:image/gif;base64,R0lGODlhKAAoAIABAAAAAP///yH/C05FVFNDQVBFMi4wAwEAAAAh+QQJAQABACwAAAAAKAAoAAACkYwNqXrdC52DS06a7MFZI+4FHBCKoDeWKXqymPqGqxvJrXZbMx7Ttc+w9XgU2FB3lOyQRWET2IFGiU9m1frDVpxZZc6bfHwv4c1YXP6k1Vdy292Fb6UkuvFtXpvWSzA+HycXJHUXiGYIiMg2R6W459gnWGfHNdjIqDWVqemH2ekpObkpOlppWUqZiqr6edqqWQAAIfkECQEAAQAsAAAAACgAKAAAApSMgZnGfaqcg1E2uuzDmmHUBR8Qil95hiPKqWn3aqtLsS18y7G1SzNeowWBENtQd+T1JktP05nzPTdJZlR6vUxNWWjV+vUWhWNkWFwxl9VpZRedYcflIOLafaa28XdsH/ynlcc1uPVDZxQIR0K25+cICCmoqCe5mGhZOfeYSUh5yJcJyrkZWWpaR8doJ2o4NYq62lAAACH5BAkBAAEALAAAAAAoACgAAAKVDI4Yy22ZnINRNqosw0Bv7i1gyHUkFj7oSaWlu3ovC8GxNso5fluz3qLVhBVeT/Lz7ZTHyxL5dDalQWPVOsQWtRnuwXaFTj9jVVh8pma9JjZ4zYSj5ZOyma7uuolffh+IR5aW97cHuBUXKGKXlKjn+DiHWMcYJah4N0lYCMlJOXipGRr5qdgoSTrqWSq6WFl2ypoaUAAAIfkECQEAAQAsAAAAACgAKAAAApaEb6HLgd/iO7FNWtcFWe+ufODGjRfoiJ2akShbueb0wtI50zm02pbvwfWEMWBQ1zKGlLIhskiEPm9R6vRXxV4ZzWT2yHOGpWMyorblKlNp8HmHEb/lCXjcW7bmtXP8Xt229OVWR1fod2eWqNfHuMjXCPkIGNileOiImVmCOEmoSfn3yXlJWmoHGhqp6ilYuWYpmTqKUgAAIfkECQEAAQAsAAAAACgAKAAAApiEH6kb58biQ3FNWtMFWW3eNVcojuFGfqnZqSebuS06w5V80/X02pKe8zFwP6EFWOT1lDFk8rGERh1TTNOocQ61Hm4Xm2VexUHpzjymViHrFbiELsefVrn6XKfnt2Q9G/+Xdie499XHd2g4h7ioOGhXGJboGAnXSBnoBwKYyfioubZJ2Hn0RuRZaflZOil56Zp6iioKSXpUAAAh+QQJAQABACwAAAAAKAAoAAACkoQRqRvnxuI7kU1a1UU5bd5tnSeOZXhmn5lWK3qNTWvRdQxP8qvaC+/yaYQzXO7BMvaUEmJRd3TsiMAgswmNYrSgZdYrTX6tSHGZO73ezuAw2uxuQ+BbeZfMxsexY35+/Qe4J1inV0g4x3WHuMhIl2jXOKT2Q+VU5fgoSUI52VfZyfkJGkha6jmY+aaYdirq+lQAACH5BAkBAAEALAAAAAAoACgAAAKWBIKpYe0L3YNKToqswUlvznigd4wiR4KhZrKt9Upqip61i9E3vMvxRdHlbEFiEXfk9YARYxOZZD6VQ2pUunBmtRXo1Lf8hMVVcNl8JafV38aM2/Fu5V16Bn63r6xt97j09+MXSFi4BniGFae3hzbH9+hYBzkpuUh5aZmHuanZOZgIuvbGiNeomCnaxxap2upaCZsq+1kAACH5BAkBAAEALAAAAAAoACgAAAKXjI8By5zf4kOxTVrXNVlv1X0d8IGZGKLnNpYtm8Lr9cqVeuOSvfOW79D9aDHizNhDJidFZhNydEahOaDH6nomtJjp1tutKoNWkvA6JqfRVLHU/QUfau9l2x7G54d1fl995xcIGAdXqMfBNadoYrhH+Mg2KBlpVpbluCiXmMnZ2Sh4GBqJ+ckIOqqJ6LmKSllZmsoq6wpQAAAh+QQJAQABACwAAAAAKAAoAAAClYx/oLvoxuJDkU1a1YUZbJ59nSd2ZXhWqbRa2/gF8Gu2DY3iqs7yrq+xBYEkYvFSM8aSSObE+ZgRl1BHFZNr7pRCavZ5BW2142hY3AN/zWtsmf12p9XxxFl2lpLn1rseztfXZjdIWIf2s5dItwjYKBgo9yg5pHgzJXTEeGlZuenpyPmpGQoKOWkYmSpaSnqKileI2FAAACH5BAkBAAEALAAAAAAoACgAAAKVjB+gu+jG4kORTVrVhRlsnn2dJ3ZleFaptFrb+CXmO9OozeL5VfP99HvAWhpiUdcwkpBH3825AwYdU8xTqlLGhtCosArKMpvfa1mMRae9VvWZfeB2XfPkeLmm18lUcBj+p5dnN8jXZ3YIGEhYuOUn45aoCDkp16hl5IjYJvjWKcnoGQpqyPlpOhr3aElaqrq56Bq7VAAAOw=="); - height: 100%; - filter: alpha(opacity=25); /* support: IE8 */ - opacity: 0.25; -} -.ui-progressbar-indeterminate .ui-progressbar-value { - background-image: none; -} -.ui-selectmenu-menu { - padding: 0; - margin: 0; - position: absolute; - top: 0; - left: 0; - display: none; -} -.ui-selectmenu-menu .ui-menu { - overflow: auto; - overflow-x: hidden; - padding-bottom: 1px; -} -.ui-selectmenu-menu .ui-menu .ui-selectmenu-optgroup { - font-size: 1em; - font-weight: bold; - line-height: 1.5; - padding: 2px 0.4em; - margin: 0.5em 0 0 0; - height: auto; - border: 0; -} -.ui-selectmenu-open { - display: block; -} -.ui-selectmenu-text { - display: block; - margin-right: 20px; - overflow: hidden; - text-overflow: ellipsis; -} -.ui-selectmenu-button.ui-button { - text-align: left; - white-space: nowrap; - width: 14em; -} -.ui-selectmenu-icon.ui-icon { - float: right; - margin-top: 0; -} -.ui-slider { - position: relative; - text-align: left; -} -.ui-slider .ui-slider-handle { - position: absolute; - z-index: 2; - width: 1.2em; - height: 1.2em; - cursor: default; - -ms-touch-action: none; - touch-action: none; -} -.ui-slider .ui-slider-range { - position: absolute; - z-index: 1; - font-size: .7em; - display: block; - border: 0; - background-position: 0 0; -} - -/* support: IE8 - See #6727 */ -.ui-slider.ui-state-disabled .ui-slider-handle, -.ui-slider.ui-state-disabled .ui-slider-range { - filter: inherit; -} - -.ui-slider-horizontal { - height: .8em; -} -.ui-slider-horizontal .ui-slider-handle { - top: -.3em; - margin-left: -.6em; -} -.ui-slider-horizontal .ui-slider-range { - top: 0; - height: 100%; -} -.ui-slider-horizontal .ui-slider-range-min { - left: 0; -} -.ui-slider-horizontal .ui-slider-range-max { - right: 0; -} - -.ui-slider-vertical { - width: .8em; - height: 100px; -} -.ui-slider-vertical .ui-slider-handle { - left: -.3em; - margin-left: 0; - margin-bottom: -.6em; -} -.ui-slider-vertical .ui-slider-range { - left: 0; - width: 100%; -} -.ui-slider-vertical .ui-slider-range-min { - bottom: 0; -} -.ui-slider-vertical .ui-slider-range-max { - top: 0; -} -.ui-spinner { - position: relative; - display: inline-block; - overflow: hidden; - padding: 0; - vertical-align: middle; -} -.ui-spinner-input { - border: none; - background: none; - color: inherit; - padding: .222em 0; - margin: .2em 0; - vertical-align: middle; - margin-left: .4em; - margin-right: 2em; -} -.ui-spinner-button { - width: 1.6em; - height: 50%; - font-size: .5em; - padding: 0; - margin: 0; - text-align: center; - position: absolute; - cursor: default; - display: block; - overflow: hidden; - right: 0; -} -/* more specificity required here to override default borders */ -.ui-spinner a.ui-spinner-button { - border-top-style: none; - border-bottom-style: none; - border-right-style: none; -} -.ui-spinner-up { - top: 0; -} -.ui-spinner-down { - bottom: 0; -} -.ui-tabs { - position: relative;/* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ - padding: .2em; -} -.ui-tabs .ui-tabs-nav { - margin: 0; - padding: .2em .2em 0; -} -.ui-tabs .ui-tabs-nav li { - list-style: none; - float: left; - position: relative; - top: 0; - margin: 1px .2em 0 0; - border-bottom-width: 0; - padding: 0; - white-space: nowrap; -} -.ui-tabs .ui-tabs-nav .ui-tabs-anchor { - float: left; - padding: .5em 1em; - text-decoration: none; -} -.ui-tabs .ui-tabs-nav li.ui-tabs-active { - margin-bottom: -1px; - padding-bottom: 1px; -} -.ui-tabs .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor, -.ui-tabs .ui-tabs-nav li.ui-state-disabled .ui-tabs-anchor, -.ui-tabs .ui-tabs-nav li.ui-tabs-loading .ui-tabs-anchor { - cursor: text; -} -.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active .ui-tabs-anchor { - cursor: pointer; -} -.ui-tabs .ui-tabs-panel { - display: block; - border-width: 0; - padding: 1em 1.4em; - background: none; -} -.ui-tooltip { - padding: 8px; - position: absolute; - z-index: 9999; - max-width: 300px; -} -body .ui-tooltip { - border-width: 2px; -} - -/* Component containers -----------------------------------*/ -.ui-widget { - font-family: Arial,Helvetica,sans-serif; - font-size: 1em; -} -.ui-widget .ui-widget { - font-size: 1em; -} -.ui-widget input, -.ui-widget select, -.ui-widget textarea, -.ui-widget button { - font-family: Arial,Helvetica,sans-serif; - font-size: 1em; -} -.ui-widget.ui-widget-content { - border: 1px solid #c5c5c5; -} -.ui-widget-content { - border: 1px solid #dddddd; - background: #ffffff; - color: #333333; -} -.ui-widget-content a { - color: #333333; -} -.ui-widget-header { - border: 1px solid #dddddd; - background: #e9e9e9; - color: #333333; - font-weight: bold; -} -.ui-widget-header a { - color: #333333; -} - -/* Interaction states -----------------------------------*/ -.ui-state-default, -.ui-widget-content .ui-state-default, -.ui-widget-header .ui-state-default, -.ui-button, - -/* We use html here because we need a greater specificity to make sure disabled -works properly when clicked or hovered */ -html .ui-button.ui-state-disabled:hover, -html .ui-button.ui-state-disabled:active { - border: 1px solid #c5c5c5; - background: #f6f6f6; - font-weight: normal; - color: #454545; -} -.ui-state-default a, -.ui-state-default a:link, -.ui-state-default a:visited, -a.ui-button, -a:link.ui-button, -a:visited.ui-button, -.ui-button { - color: #454545; - text-decoration: none; -} -.ui-state-hover, -.ui-widget-content .ui-state-hover, -.ui-widget-header .ui-state-hover, -.ui-state-focus, -.ui-widget-content .ui-state-focus, -.ui-widget-header .ui-state-focus, -.ui-button:hover, -.ui-button:focus { - border: 1px solid #cccccc; - background: #ededed; - font-weight: normal; - color: #2b2b2b; -} -.ui-state-hover a, -.ui-state-hover a:hover, -.ui-state-hover a:link, -.ui-state-hover a:visited, -.ui-state-focus a, -.ui-state-focus a:hover, -.ui-state-focus a:link, -.ui-state-focus a:visited, -a.ui-button:hover, -a.ui-button:focus { - color: #2b2b2b; - text-decoration: none; -} - -.ui-visual-focus { - box-shadow: 0 0 3px 1px rgb(94, 158, 214); -} -.ui-state-active, -.ui-widget-content .ui-state-active, -.ui-widget-header .ui-state-active, -a.ui-button:active, -.ui-button:active, -.ui-button.ui-state-active:hover { - border: 1px solid #003eff; - background: #007fff; - font-weight: normal; - color: #ffffff; -} -.ui-icon-background, -.ui-state-active .ui-icon-background { - border: #003eff; - background-color: #ffffff; -} -.ui-state-active a, -.ui-state-active a:link, -.ui-state-active a:visited { - color: #ffffff; - text-decoration: none; -} - -/* Interaction Cues -----------------------------------*/ -.ui-state-highlight, -.ui-widget-content .ui-state-highlight, -.ui-widget-header .ui-state-highlight { - border: 1px solid #dad55e; - background: #fffa90; - color: #777620; -} -.ui-state-checked { - border: 1px solid #dad55e; - background: #fffa90; -} -.ui-state-highlight a, -.ui-widget-content .ui-state-highlight a, -.ui-widget-header .ui-state-highlight a { - color: #777620; -} -.ui-state-error, -.ui-widget-content .ui-state-error, -.ui-widget-header .ui-state-error { - border: 1px solid #f1a899; - background: #fddfdf; - color: #5f3f3f; -} -.ui-state-error a, -.ui-widget-content .ui-state-error a, -.ui-widget-header .ui-state-error a { - color: #5f3f3f; -} -.ui-state-error-text, -.ui-widget-content .ui-state-error-text, -.ui-widget-header .ui-state-error-text { - color: #5f3f3f; -} -.ui-priority-primary, -.ui-widget-content .ui-priority-primary, -.ui-widget-header .ui-priority-primary { - font-weight: bold; -} -.ui-priority-secondary, -.ui-widget-content .ui-priority-secondary, -.ui-widget-header .ui-priority-secondary { - opacity: .7; - filter:Alpha(Opacity=70); /* support: IE8 */ - font-weight: normal; -} -.ui-state-disabled, -.ui-widget-content .ui-state-disabled, -.ui-widget-header .ui-state-disabled { - opacity: .35; - filter:Alpha(Opacity=35); /* support: IE8 */ - background-image: none; -} -.ui-state-disabled .ui-icon { - filter:Alpha(Opacity=35); /* support: IE8 - See #6059 */ -} - -/* Icons -----------------------------------*/ - -/* states and images */ -.ui-icon { - width: 16px; - height: 16px; -} -.ui-icon, -.ui-widget-content .ui-icon { - background-image: url("images/ui-icons_444444_256x240.png"); -} -.ui-widget-header .ui-icon { - background-image: url("images/ui-icons_444444_256x240.png"); -} -.ui-state-hover .ui-icon, -.ui-state-focus .ui-icon, -.ui-button:hover .ui-icon, -.ui-button:focus .ui-icon { - background-image: url("images/ui-icons_555555_256x240.png"); -} -.ui-state-active .ui-icon, -.ui-button:active .ui-icon { - background-image: url("images/ui-icons_ffffff_256x240.png"); -} -.ui-state-highlight .ui-icon, -.ui-button .ui-state-highlight.ui-icon { - background-image: url("images/ui-icons_777620_256x240.png"); -} -.ui-state-error .ui-icon, -.ui-state-error-text .ui-icon { - background-image: url("images/ui-icons_cc0000_256x240.png"); -} -.ui-button .ui-icon { - background-image: url("images/ui-icons_777777_256x240.png"); -} - -/* positioning */ -.ui-icon-blank { background-position: 16px 16px; } -.ui-icon-caret-1-n { background-position: 0 0; } -.ui-icon-caret-1-ne { background-position: -16px 0; } -.ui-icon-caret-1-e { background-position: -32px 0; } -.ui-icon-caret-1-se { background-position: -48px 0; } -.ui-icon-caret-1-s { background-position: -65px 0; } -.ui-icon-caret-1-sw { background-position: -80px 0; } -.ui-icon-caret-1-w { background-position: -96px 0; } -.ui-icon-caret-1-nw { background-position: -112px 0; } -.ui-icon-caret-2-n-s { background-position: -128px 0; } -.ui-icon-caret-2-e-w { background-position: -144px 0; } -.ui-icon-triangle-1-n { background-position: 0 -16px; } -.ui-icon-triangle-1-ne { background-position: -16px -16px; } -.ui-icon-triangle-1-e { background-position: -32px -16px; } -.ui-icon-triangle-1-se { background-position: -48px -16px; } -.ui-icon-triangle-1-s { background-position: -65px -16px; } -.ui-icon-triangle-1-sw { background-position: -80px -16px; } -.ui-icon-triangle-1-w { background-position: -96px -16px; } -.ui-icon-triangle-1-nw { background-position: -112px -16px; } -.ui-icon-triangle-2-n-s { background-position: -128px -16px; } -.ui-icon-triangle-2-e-w { background-position: -144px -16px; } -.ui-icon-arrow-1-n { background-position: 0 -32px; } -.ui-icon-arrow-1-ne { background-position: -16px -32px; } -.ui-icon-arrow-1-e { background-position: -32px -32px; } -.ui-icon-arrow-1-se { background-position: -48px -32px; } -.ui-icon-arrow-1-s { background-position: -65px -32px; } -.ui-icon-arrow-1-sw { background-position: -80px -32px; } -.ui-icon-arrow-1-w { background-position: -96px -32px; } -.ui-icon-arrow-1-nw { background-position: -112px -32px; } -.ui-icon-arrow-2-n-s { background-position: -128px -32px; } -.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } -.ui-icon-arrow-2-e-w { background-position: -160px -32px; } -.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } -.ui-icon-arrowstop-1-n { background-position: -192px -32px; } -.ui-icon-arrowstop-1-e { background-position: -208px -32px; } -.ui-icon-arrowstop-1-s { background-position: -224px -32px; } -.ui-icon-arrowstop-1-w { background-position: -240px -32px; } -.ui-icon-arrowthick-1-n { background-position: 1px -48px; } -.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } -.ui-icon-arrowthick-1-e { background-position: -32px -48px; } -.ui-icon-arrowthick-1-se { background-position: -48px -48px; } -.ui-icon-arrowthick-1-s { background-position: -64px -48px; } -.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } -.ui-icon-arrowthick-1-w { background-position: -96px -48px; } -.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } -.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } -.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } -.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } -.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } -.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } -.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } -.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } -.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } -.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } -.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } -.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } -.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } -.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } -.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } -.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } -.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } -.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } -.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } -.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } -.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } -.ui-icon-arrow-4 { background-position: 0 -80px; } -.ui-icon-arrow-4-diag { background-position: -16px -80px; } -.ui-icon-extlink { background-position: -32px -80px; } -.ui-icon-newwin { background-position: -48px -80px; } -.ui-icon-refresh { background-position: -64px -80px; } -.ui-icon-shuffle { background-position: -80px -80px; } -.ui-icon-transfer-e-w { background-position: -96px -80px; } -.ui-icon-transferthick-e-w { background-position: -112px -80px; } -.ui-icon-folder-collapsed { background-position: 0 -96px; } -.ui-icon-folder-open { background-position: -16px -96px; } -.ui-icon-document { background-position: -32px -96px; } -.ui-icon-document-b { background-position: -48px -96px; } -.ui-icon-note { background-position: -64px -96px; } -.ui-icon-mail-closed { background-position: -80px -96px; } -.ui-icon-mail-open { background-position: -96px -96px; } -.ui-icon-suitcase { background-position: -112px -96px; } -.ui-icon-comment { background-position: -128px -96px; } -.ui-icon-person { background-position: -144px -96px; } -.ui-icon-print { background-position: -160px -96px; } -.ui-icon-trash { background-position: -176px -96px; } -.ui-icon-locked { background-position: -192px -96px; } -.ui-icon-unlocked { background-position: -208px -96px; } -.ui-icon-bookmark { background-position: -224px -96px; } -.ui-icon-tag { background-position: -240px -96px; } -.ui-icon-home { background-position: 0 -112px; } -.ui-icon-flag { background-position: -16px -112px; } -.ui-icon-calendar { background-position: -32px -112px; } -.ui-icon-cart { background-position: -48px -112px; } -.ui-icon-pencil { background-position: -64px -112px; } -.ui-icon-clock { background-position: -80px -112px; } -.ui-icon-disk { background-position: -96px -112px; } -.ui-icon-calculator { background-position: -112px -112px; } -.ui-icon-zoomin { background-position: -128px -112px; } -.ui-icon-zoomout { background-position: -144px -112px; } -.ui-icon-search { background-position: -160px -112px; } -.ui-icon-wrench { background-position: -176px -112px; } -.ui-icon-gear { background-position: -192px -112px; } -.ui-icon-heart { background-position: -208px -112px; } -.ui-icon-star { background-position: -224px -112px; } -.ui-icon-link { background-position: -240px -112px; } -.ui-icon-cancel { background-position: 0 -128px; } -.ui-icon-plus { background-position: -16px -128px; } -.ui-icon-plusthick { background-position: -32px -128px; } -.ui-icon-minus { background-position: -48px -128px; } -.ui-icon-minusthick { background-position: -64px -128px; } -.ui-icon-close { background-position: -80px -128px; } -.ui-icon-closethick { background-position: -96px -128px; } -.ui-icon-key { background-position: -112px -128px; } -.ui-icon-lightbulb { background-position: -128px -128px; } -.ui-icon-scissors { background-position: -144px -128px; } -.ui-icon-clipboard { background-position: -160px -128px; } -.ui-icon-copy { background-position: -176px -128px; } -.ui-icon-contact { background-position: -192px -128px; } -.ui-icon-image { background-position: -208px -128px; } -.ui-icon-video { background-position: -224px -128px; } -.ui-icon-script { background-position: -240px -128px; } -.ui-icon-alert { background-position: 0 -144px; } -.ui-icon-info { background-position: -16px -144px; } -.ui-icon-notice { background-position: -32px -144px; } -.ui-icon-help { background-position: -48px -144px; } -.ui-icon-check { background-position: -64px -144px; } -.ui-icon-bullet { background-position: -80px -144px; } -.ui-icon-radio-on { background-position: -96px -144px; } -.ui-icon-radio-off { background-position: -112px -144px; } -.ui-icon-pin-w { background-position: -128px -144px; } -.ui-icon-pin-s { background-position: -144px -144px; } -.ui-icon-play { background-position: 0 -160px; } -.ui-icon-pause { background-position: -16px -160px; } -.ui-icon-seek-next { background-position: -32px -160px; } -.ui-icon-seek-prev { background-position: -48px -160px; } -.ui-icon-seek-end { background-position: -64px -160px; } -.ui-icon-seek-start { background-position: -80px -160px; } -/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ -.ui-icon-seek-first { background-position: -80px -160px; } -.ui-icon-stop { background-position: -96px -160px; } -.ui-icon-eject { background-position: -112px -160px; } -.ui-icon-volume-off { background-position: -128px -160px; } -.ui-icon-volume-on { background-position: -144px -160px; } -.ui-icon-power { background-position: 0 -176px; } -.ui-icon-signal-diag { background-position: -16px -176px; } -.ui-icon-signal { background-position: -32px -176px; } -.ui-icon-battery-0 { background-position: -48px -176px; } -.ui-icon-battery-1 { background-position: -64px -176px; } -.ui-icon-battery-2 { background-position: -80px -176px; } -.ui-icon-battery-3 { background-position: -96px -176px; } -.ui-icon-circle-plus { background-position: 0 -192px; } -.ui-icon-circle-minus { background-position: -16px -192px; } -.ui-icon-circle-close { background-position: -32px -192px; } -.ui-icon-circle-triangle-e { background-position: -48px -192px; } -.ui-icon-circle-triangle-s { background-position: -64px -192px; } -.ui-icon-circle-triangle-w { background-position: -80px -192px; } -.ui-icon-circle-triangle-n { background-position: -96px -192px; } -.ui-icon-circle-arrow-e { background-position: -112px -192px; } -.ui-icon-circle-arrow-s { background-position: -128px -192px; } -.ui-icon-circle-arrow-w { background-position: -144px -192px; } -.ui-icon-circle-arrow-n { background-position: -160px -192px; } -.ui-icon-circle-zoomin { background-position: -176px -192px; } -.ui-icon-circle-zoomout { background-position: -192px -192px; } -.ui-icon-circle-check { background-position: -208px -192px; } -.ui-icon-circlesmall-plus { background-position: 0 -208px; } -.ui-icon-circlesmall-minus { background-position: -16px -208px; } -.ui-icon-circlesmall-close { background-position: -32px -208px; } -.ui-icon-squaresmall-plus { background-position: -48px -208px; } -.ui-icon-squaresmall-minus { background-position: -64px -208px; } -.ui-icon-squaresmall-close { background-position: -80px -208px; } -.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } -.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } -.ui-icon-grip-solid-vertical { background-position: -32px -224px; } -.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } -.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } -.ui-icon-grip-diagonal-se { background-position: -80px -224px; } - - -/* Misc visuals -----------------------------------*/ - -/* Corner radius */ -.ui-corner-all, -.ui-corner-top, -.ui-corner-left, -.ui-corner-tl { - border-top-left-radius: 3px; -} -.ui-corner-all, -.ui-corner-top, -.ui-corner-right, -.ui-corner-tr { - border-top-right-radius: 3px; -} -.ui-corner-all, -.ui-corner-bottom, -.ui-corner-left, -.ui-corner-bl { - border-bottom-left-radius: 3px; -} -.ui-corner-all, -.ui-corner-bottom, -.ui-corner-right, -.ui-corner-br { - border-bottom-right-radius: 3px; -} - -/* Overlays */ -.ui-widget-overlay { - background: #aaaaaa; - opacity: .3; - filter: Alpha(Opacity=30); /* support: IE8 */ -} -.ui-widget-shadow { - -webkit-box-shadow: 0px 0px 5px #666666; - box-shadow: 0px 0px 5px #666666; -} diff --git a/app/webroot/css/main.css b/app/webroot/css/main.css index a1af5ea46..9d8ba010a 100644 --- a/app/webroot/css/main.css +++ b/app/webroot/css/main.css @@ -2001,7 +2001,6 @@ a.discrete { .warning_container { border: 1px solid red; border-radius: 7px; - box-shadow: 0 0 6px #B2B2B2; padding: 0 10px 10px 10px; width: 100%; height: 100%; @@ -2009,6 +2008,10 @@ a.discrete { margin-top: 15px; } +.warning_container h4 { + color: red; +} + #galaxies_div { position: relative; padding: 35px 15px 15px 15px; @@ -2056,14 +2059,14 @@ a.discrete { font-size: 12px; } -button.addGalaxy { +button.addButton { line-height: 10px; padding: 2px; margin-right: 5px; margin-top: 1px; } -button.addGalaxy .fa-plus { +button.addButton .fa-plus { font-size: 11px; line-height: 13px; } diff --git a/app/webroot/doc/openapi.yaml b/app/webroot/doc/openapi.yaml index b4f173b1f..50e8ba856 100644 --- a/app/webroot/doc/openapi.yaml +++ b/app/webroot/doc/openapi.yaml @@ -2868,6 +2868,8 @@ components: eventinfo: description: "Quick event description" type: string + sharinggroup: + $ref: "#/components/schemas/SharingGroupIDRestSearchFilter" decayingModel: description: "Specify the decaying model from which the decaying score should be calculated" type: string @@ -5375,6 +5377,13 @@ components: type: string example: "tlp:amber" + SharingGroupIDRestSearchFilter: + description: "Sharing group ID(s), either as single string or list of IDs" + nullable: true + items: + type: string + example: "1" + MetadataRestSearchFilter: description: "Will only return the metadata of the given query scope, contained data is omitted." nullable: true @@ -6202,6 +6211,8 @@ components: description: "Extends the response with the base64 representation of the attachment, if there is one" type: boolean default: false + sharinggroup: + $ref: "#/components/schemas/SharingGroupIDRestSearchFilter" metadata: $ref: "#/components/schemas/MetadataRestSearchFilter" uuid: diff --git a/app/webroot/js/action_table.js b/app/webroot/js/action_table.js index 96a45b5bb..2bd54af97 100644 --- a/app/webroot/js/action_table.js +++ b/app/webroot/js/action_table.js @@ -1,371 +1,371 @@ class ActionTable { - constructor(options) { - this.__globalCounter = 0; - this.options = options; - this.id = options.id; - this.container = options.container; - this.classes = options.classes; - this.table_title = options.title; - this.header = options.header; - this.onAddition = options.onAddition; - this.onRemove = options.onRemove; - this.header.push("Action"); - this.row_num = this.header.length; - this.data = options.data === undefined ? [] : options.data; - this.tr_id_mapping = {}; - this.control_items = options.control_items; - this.header_action_button = options.header_action_button === undefined ? {} : options.header_action_button; - if (options.header_action_button !== undefined) { - this.header_action_button_style = this.header_action_button.style === undefined ? {} : this.header_action_button.style; - this.additionEnabled = this.header_action_button.additionEnabled === undefined ? true : this.header_action_button.additionEnabled; - this.additionButtonDisabled = this.header_action_button.disabled === undefined ? false : this.header_action_button.disabled; - } else { - this.header_action_button_style = {}; - this.additionEnabled = true; - this.additionButtonDisabled = false; - } + constructor(options) { + this.__globalCounter = 0; + this.options = options; + this.id = options.id; + this.container = options.container; + this.classes = options.classes; + this.table_title = options.title; + this.header = options.header; + this.onAddition = options.onAddition; + this.onRemove = options.onRemove; + this.header.push("Action"); + this.row_num = this.header.length; + this.data = options.data === undefined ? [] : options.data; + this.tr_id_mapping = {}; + this.control_items = options.control_items; + this.header_action_button = options.header_action_button === undefined ? {} : options.header_action_button; + if (options.header_action_button !== undefined) { + this.header_action_button_style = this.header_action_button.style === undefined ? {} : this.header_action_button.style; + this.additionEnabled = this.header_action_button.additionEnabled === undefined ? true : this.header_action_button.additionEnabled; + this.additionButtonDisabled = this.header_action_button.disabled === undefined ? false : this.header_action_button.disabled; + } else { + this.header_action_button_style = {}; + this.additionEnabled = true; + this.additionButtonDisabled = false; + } - this.row_action_button = options.row_action_button === undefined ? {} : options.row_action_button; - if (options.row_action_button !== undefined) { - this.row_action_button_style = this.row_action_button.style === undefined ? {} : this.row_action_button.style; - this.removalEnabled = this.row_action_button.removalEnabled === undefined ? true : this.row_action_button.removalEnabled; - } else { - this.row_action_button_style = {}; - this.removalEnabled = true; - } + this.row_action_button = options.row_action_button === undefined ? {} : options.row_action_button; + if (options.row_action_button !== undefined) { + this.row_action_button_style = this.row_action_button.style === undefined ? {} : this.row_action_button.style; + this.removalEnabled = this.row_action_button.removalEnabled === undefined ? true : this.row_action_button.removalEnabled; + } else { + this.row_action_button_style = {}; + this.removalEnabled = true; + } - this.selects = {}; + this.selects = {}; - this.__create_table(); - } + this.__create_table(); + } - __get_uniq_index() { - this.__globalCounter++; - return this.__globalCounter-1; - } + __get_uniq_index() { + this.__globalCounter++; + return this.__globalCounter-1; + } - add_row(row) { - if (!this.__data_already_exists(row)) { - var id = this.__add_row(row); - this.tr_id_mapping[this.data.length] = id; - this.data.push(row); - } - } + add_row(row) { + if (!this.__data_already_exists(row)) { + var id = this.__add_row(row); + this.tr_id_mapping[this.data.length] = id; + this.data.push(row); + } + } - delete_row(row_id) { - var tr = document.getElementById(row_id); - var array = this.__get_array_from_DOM_row(tr); - var data_index = this.__find_array_index(array, this.data); - tr.outerHTML = ""; - this.data.splice(data_index, 1); - } + delete_row(row_id) { + var tr = document.getElementById(row_id); + var array = this.__get_array_from_DOM_row(tr); + var data_index = this.__find_array_index(array, this.data); + tr.outerHTML = ""; + this.data.splice(data_index, 1); + } - delete_row_index(row_pos) { - var tr = this.get_DOM_row(row_pos); - var array = this.__get_array_from_DOM_row(tr); - var data_index = this.__find_array_index(array, this.data); - tr.outerHTML = ""; - this.data.splice(data_index, 1); - } + delete_row_index(row_pos) { + var tr = this.get_DOM_row(row_pos); + var array = this.__get_array_from_DOM_row(tr); + var data_index = this.__find_array_index(array, this.data); + tr.outerHTML = ""; + this.data.splice(data_index, 1); + } - get_DOM_row(row_pos) { - var row_id = this.tr_id_mapping[row_pos]; - var tr = document.getElementById(row_id); - return tr; - } + get_DOM_row(row_pos) { + var row_id = this.tr_id_mapping[row_pos]; + var tr = document.getElementById(row_id); + return tr; + } - get_data() { - return this.data; - } + get_data() { + return this.data; + } - clear_table() { - var dataLength = this.data.length; - for (var i=0; i= 0; - } + __data_already_exists(data) { + return this.__find_array_index(data, this.data) >= 0; + } - __find_array_index(value, array) { - for (var i in array) { - if (JSON.stringify(array[i]) === JSON.stringify(value)) { // compare array - return i; - } - } - return -1; - } + __find_array_index(value, array) { + for (var i in array) { + if (JSON.stringify(array[i]) === JSON.stringify(value)) { // compare array + return i; + } + } + return -1; + } - __create_table() { - if (this.table_title !== undefined) { - var label = document.createElement('label'); - label.innerHTML = this.table_title; - this.container.appendChild(label); - } - this.form = document.createElement('form'); - this.table = document.createElement('table'); - this.table.classList.add("table", "table-bordered", "action-table"); - if (this.classes !== undefined) { - for (var i in this.classes) { - this.table.classList.add(this.classes[i]); - } - } - this.thead = document.createElement('thead'); - this.tbody = document.createElement('tbody'); - var trHead = document.createElement('tr'); - for (var col of this.header) { - var th = document.createElement('th'); - th.innerHTML = col; - trHead.appendChild(th); - } - this.thead.appendChild(trHead); + __create_table() { + if (this.table_title !== undefined) { + var label = document.createElement('label'); + label.innerHTML = this.table_title; + this.container.appendChild(label); + } + this.form = document.createElement('form'); + this.table = document.createElement('table'); + this.table.classList.add("table", "table-bordered", "action-table"); + if (this.classes !== undefined) { + for (var i in this.classes) { + this.table.classList.add(this.classes[i]); + } + } + this.thead = document.createElement('thead'); + this.tbody = document.createElement('tbody'); + var trHead = document.createElement('tr'); + for (var col of this.header) { + var th = document.createElement('th'); + th.innerHTML = col; + trHead.appendChild(th); + } + this.thead.appendChild(trHead); - this.__add_control_row(); + this.__add_control_row(); - for (var row of this.data) { - this.__add_row(row); - } - this.table.appendChild(this.thead); - this.table.appendChild(this.tbody); - this.form.appendChild(this.table); - this.container.appendChild(this.form); - } + for (var row of this.data) { + this.__add_row(row); + } + this.table.appendChild(this.thead); + this.table.appendChild(this.tbody); + this.form.appendChild(this.table); + this.container.appendChild(this.form); + } - __add_row(row) { - var tr = document.createElement('tr'); - tr.id = "tr_" + this.__uuidv4(); - for (var col of row) { - var td = document.createElement('td'); - td.innerHTML = col; - tr.appendChild(td); - } - this.__add_action_button(tr); - this.tbody.appendChild(tr); - return tr.id; - } + __add_row(row) { + var tr = document.createElement('tr'); + tr.id = "tr_" + this.__uuidv4(); + for (var col of row) { + var td = document.createElement('td'); + td.innerHTML = col; + tr.appendChild(td); + } + this.__add_action_button(tr); + this.tbody.appendChild(tr); + return tr.id; + } - __add_control_row() { - var tr = document.createElement('tr'); - for (var itemOption of this.control_items) { - var td = document.createElement('td'); - var item = this.__add_control_item(itemOption); - if (itemOption.colspan !== undefined) { - td.colSpan = itemOption.colspan; - } - td.appendChild(item); - tr.appendChild(td); - } - var td = document.createElement('td'); + __add_control_row() { + var tr = document.createElement('tr'); + for (var itemOption of this.control_items) { + var td = document.createElement('td'); + var item = this.__add_control_item(itemOption); + if (itemOption.colspan !== undefined) { + td.colSpan = itemOption.colspan; + } + td.appendChild(item); + tr.appendChild(td); + } + var td = document.createElement('td'); - var btn = document.createElement('button'); - var header_action_button_style = this.header_action_button.style === undefined ? {} : this.header_action_button.style; - if (header_action_button_style.type !== undefined) { - btn.classList.add("btn", "btn-"+header_action_button_style.type); - } else { - btn.classList.add("btn", "btn-primary"); - } - if (header_action_button_style.tooltip !== undefined) { - btn.title = header_action_button_style.tooltip; - } - if (header_action_button_style.icon !== undefined) { - btn.innerHTML = ''; - } else { - btn.innerHTML = ''; - } - btn.type = "button"; - btn.disabled = this.additionButtonDisabled; + var btn = document.createElement('button'); + var header_action_button_style = this.header_action_button.style === undefined ? {} : this.header_action_button.style; + if (header_action_button_style.type !== undefined) { + btn.classList.add("btn", "btn-"+header_action_button_style.type); + } else { + btn.classList.add("btn", "btn-primary"); + } + if (header_action_button_style.tooltip !== undefined) { + btn.title = header_action_button_style.tooltip; + } + if (header_action_button_style.icon !== undefined) { + btn.innerHTML = ''; + } else { + btn.innerHTML = ''; + } + btn.type = "button"; + btn.disabled = this.additionButtonDisabled; - var that = this; - btn.addEventListener("click", function(evt) { - var data = []; - for (var elem of that.form.elements) { - if (elem.classList.contains('form-group')) { - data.push(elem.value); - } - } - if (that.additionEnabled) { - that.add_row(data); - } - if (that.onAddition !== undefined) { - that.onAddition(data, that); - } - }); + var that = this; + btn.addEventListener("click", function(evt) { + var data = []; + for (var elem of that.form.elements) { + if (elem.classList.contains('form-group')) { + data.push(elem.value); + } + } + if (that.additionEnabled) { + that.add_row(data); + } + if (that.onAddition !== undefined) { + that.onAddition(data, that); + } + }); - td.appendChild(btn); + td.appendChild(btn); - tr.appendChild(td); - this.thead.appendChild(tr); - } + tr.appendChild(td); + this.thead.appendChild(tr); + } - __add_control_item(options) { - var item; - switch(options.DOMType) { - case "select": - item = this.__create_select(options.item_options); - this.selects[item.id] = item; - break; - case "input": - item = this.__create_input(options.item_options); - break; - case "empty": - item = this.__create_empty(options.item_options); - break; - default: - break; - } - return item; - } + __add_control_item(options) { + var item; + switch(options.DOMType) { + case "select": + item = this.__create_select(options.item_options); + this.selects[item.id] = item; + break; + case "input": + item = this.__create_input(options.item_options); + break; + case "empty": + item = this.__create_empty(options.item_options); + break; + default: + break; + } + return item; + } - __add_action_button(tr) { - var that = this; - var td = document.createElement('td'); - var btn = document.createElement('button'); - btn.classList.add("btn", "btn-danger"); - btn.innerHTML = ''; - btn.type = "button"; - btn.setAttribute('rowID', tr.id); - if (that.row_action_button_style.tooltip !== undefined) { - btn.title = that.row_action_button_style.tooltip; - } - if (that.row_action_button_style.style !== undefined) { - btn.style = that.row_action_button_style.style; - } - var that = this; - btn.addEventListener("click", function(evt) { - if (that.onRemove !== undefined) { - var tr = document.getElementById(this.getAttribute('rowID')); - var data = that.__get_array_from_DOM_row(tr); - that.onRemove(data, that); - } - if (that.removalEnabled) { - that.delete_row(this.getAttribute('rowID')); - } - }); - td.appendChild(btn); + __add_action_button(tr) { + var that = this; + var td = document.createElement('td'); + var btn = document.createElement('button'); + btn.classList.add("btn", "btn-danger"); + btn.innerHTML = ''; + btn.type = "button"; + btn.setAttribute('rowID', tr.id); + if (that.row_action_button_style.tooltip !== undefined) { + btn.title = that.row_action_button_style.tooltip; + } + if (that.row_action_button_style.style !== undefined) { + btn.style = that.row_action_button_style.style; + } + var that = this; + btn.addEventListener("click", function(evt) { + if (that.onRemove !== undefined) { + var tr = document.getElementById(this.getAttribute('rowID')); + var data = that.__get_array_from_DOM_row(tr); + that.onRemove(data, that); + } + if (that.removalEnabled) { + that.delete_row(this.getAttribute('rowID')); + } + }); + td.appendChild(btn); - if (that.row_action_button.others !== undefined) { - for (var i in that.row_action_button.others) { - var newBtnOptions = that.row_action_button.others[i]; + if (that.row_action_button.others !== undefined) { + for (var i in that.row_action_button.others) { + var newBtnOptions = that.row_action_button.others[i]; - var btn_style = newBtnOptions.style !== undefined ? newBtnOptions.style : {}; - var btn = document.createElement('button'); - btn.type = "button"; - if (btn_style.type !== undefined) { - btn.classList.add("btn", "btn-"+btn_style.type); - } else { - btn.classList.add("btn", "btn-primary"); - } - if (btn_style.icon !== undefined) { - btn.innerHTML = ''; - } else { - btn.innerHTML = ''; - } - if (btn_style.title !== undefined) { - btn.title = btn_style.title; - } - if (btn_style.style !== undefined) { - btn.style = btn_style.style+"margin-left: 3px"; - } else { - btn.style = "margin-left: 3px"; - } - btn.setAttribute('rowID', tr.id); - if (newBtnOptions.event !== undefined) { - btn.addEventListener("click", function(evt) { - var tr = document.getElementById(this.getAttribute('rowID')); - var data = that.__get_array_from_DOM_row(tr); - newBtnOptions.event(data, that); - }); - } - td.appendChild(btn); - } - } + var btn_style = newBtnOptions.style !== undefined ? newBtnOptions.style : {}; + var btn = document.createElement('button'); + btn.type = "button"; + if (btn_style.type !== undefined) { + btn.classList.add("btn", "btn-"+btn_style.type); + } else { + btn.classList.add("btn", "btn-primary"); + } + if (btn_style.icon !== undefined) { + btn.innerHTML = ''; + } else { + btn.innerHTML = ''; + } + if (btn_style.title !== undefined) { + btn.title = btn_style.title; + } + if (btn_style.style !== undefined) { + btn.style = btn_style.style+"margin-left: 3px"; + } else { + btn.style = "margin-left: 3px"; + } + btn.setAttribute('rowID', tr.id); + if (newBtnOptions.event !== undefined) { + btn.addEventListener("click", function(evt) { + var tr = document.getElementById(this.getAttribute('rowID')); + var data = that.__get_array_from_DOM_row(tr); + newBtnOptions.event(data, that); + }); + } + td.appendChild(btn); + } + } - tr.appendChild(td); - } + tr.appendChild(td); + } - __create_empty(options) { - var empty = document.createElement('span'); - empty.classList.add("form-group"); - empty.id = options.id !== undefined ? options.id : 'actionTable_controlSelect_'+this.__get_uniq_index(); - return empty; - } + __create_empty(options) { + var empty = document.createElement('span'); + empty.classList.add("form-group"); + empty.id = options.id !== undefined ? options.id : 'actionTable_controlSelect_'+this.__get_uniq_index(); + return empty; + } - __create_input(options) { - var input = document.createElement('input'); - input.classList.add("form-group"); - input.id = options.id !== undefined ? options.id : 'actionTable_controlSelect_'+this.__get_uniq_index(); - if (options.style !== undefined) { - input.style = options.style; - } - if (options.placeholder !== undefined) { - input.placeholder = options.placeholder; - } - if (options.disabled !== undefined) { - input.disabled = options.disabled; - } - if (options.typeahead !== undefined) { - var typeaheadOption = options.typeahead; - $('#'+input.id).typeahead(typeaheadOption); - } - return input; - } + __create_input(options) { + var input = document.createElement('input'); + input.classList.add("form-group"); + input.id = options.id !== undefined ? options.id : 'actionTable_controlSelect_'+this.__get_uniq_index(); + if (options.style !== undefined) { + input.style = options.style; + } + if (options.placeholder !== undefined) { + input.placeholder = options.placeholder; + } + if (options.disabled !== undefined) { + input.disabled = options.disabled; + } + if (options.typeahead !== undefined) { + var typeaheadOption = options.typeahead; + $('#'+input.id).typeahead(typeaheadOption); + } + return input; + } - __create_select(select_options) { - var select = document.createElement('select'); - select.classList.add("form-group"); - select.id = select_options.id !== undefined ? select_options.id : 'actionTable_controlSelect_'+this.__get_uniq_index(); - select.style.width = "100%"; - this.__add_options_to_select(select, select_options.options); - if(select_options.default !== undefined) { - select.value = select_options.default; - } - return select; - } + __create_select(select_options) { + var select = document.createElement('select'); + select.classList.add("form-group"); + select.id = select_options.id !== undefined ? select_options.id : 'actionTable_controlSelect_'+this.__get_uniq_index(); + select.style.width = "100%"; + this.__add_options_to_select(select, select_options.options); + if(select_options.default !== undefined) { + select.value = select_options.default; + } + return select; + } - __add_options_to_select(select, options) { - for(var value of options) { - var option = document.createElement('option'); - if (Array.isArray(value)) { // array of type [value, text] - option.value = value[1]; - option.innerHTML = value[1]; - } else { // only value, text=value - option.value = value; - option.innerHTML = value; - } - select.appendChild(option); - } - } + __add_options_to_select(select, options) { + for(var value of options) { + var option = document.createElement('option'); + if (Array.isArray(value)) { // array of type [value, text] + option.value = value[1]; + option.textContent = value[1]; + } else { // only value, text=value + option.value = value; + option.textContent = value; + } + select.appendChild(option); + } + } - __uuidv4() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } + __uuidv4() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); + }); + } } diff --git a/app/webroot/js/d3.custom.js b/app/webroot/js/d3.custom.js new file mode 100644 index 000000000..dbf8e288b --- /dev/null +++ b/app/webroot/js/d3.custom.js @@ -0,0 +1,34 @@ +function sparkline(elemId, data) { + data = d3.csv.parse(data); + var width = 100; + var height = 25; + var x = d3.scale.linear().range([0, width - 2]); + var y = d3.scale.linear().range([height - 4, 0]); + var parseDate = d3.time.format("%Y-%m-%d").parse; + var line = d3.svg.line() + .interpolate("linear") + .x(function(d) { return x(d.date); }) + .y(function(d) { return y(d.close); }); + + data.forEach(function(d) { + d.date = parseDate(d.Date); + d.close = +d.Close; + }); + x.domain(d3.extent(data, function(d) { return d.date; })); + y.domain(d3.extent(data, function(d) { return d.close; })); + var svg = d3.select(elemId) + .append('svg') + .attr('width', width) + .attr('height', height) + .append('g') + .attr('transform', 'translate(0, 2)'); + svg.append('path') + .datum(data) + .attr('class', 'sparkline') + .attr('d', line); + svg.append('circle') + .attr('class', 'sparkcircle') + .attr('cx', x(data[data.length - 1].date)) + .attr('cy', y(data[data.length - 1].close)) + .attr('r', 2); +} diff --git a/app/webroot/js/jquery-ui.js b/app/webroot/js/jquery-ui.js deleted file mode 100644 index 1a7a0dfcb..000000000 --- a/app/webroot/js/jquery-ui.js +++ /dev/null @@ -1,18706 +0,0 @@ -/*! jQuery UI - v1.12.1 - 2020-03-11 -* http://jqueryui.com -* Includes: widget.js, position.js, data.js, disable-selection.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/draggable.js, widgets/droppable.js, widgets/resizable.js, widgets/selectable.js, widgets/sortable.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/selectmenu.js, widgets/slider.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js -* Copyright jQuery Foundation and other contributors; Licensed MIT */ - -(function( factory ) { - if ( typeof define === "function" && define.amd ) { - - // AMD. Register as an anonymous module. - define([ "jquery" ], factory ); - } else { - - // Browser globals - factory( jQuery ); - } -}(function( $ ) { - -$.ui = $.ui || {}; - -var version = $.ui.version = "1.12.1"; - - -/*! - * jQuery UI Widget 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: Widget -//>>group: Core -//>>description: Provides a factory for creating stateful widgets with a common API. -//>>docs: http://api.jqueryui.com/jQuery.widget/ -//>>demos: http://jqueryui.com/widget/ - - - -var widgetUuid = 0; -var widgetSlice = Array.prototype.slice; - -$.cleanData = ( function( orig ) { - return function( elems ) { - var events, elem, i; - for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) { - try { - - // Only trigger remove when necessary to save time - events = $._data( elem, "events" ); - if ( events && events.remove ) { - $( elem ).triggerHandler( "remove" ); - } - - // Http://bugs.jquery.com/ticket/8235 - } catch ( e ) {} - } - orig( elems ); - }; -} )( $.cleanData ); - -$.widget = function( name, base, prototype ) { - var existingConstructor, constructor, basePrototype; - - // ProxiedPrototype allows the provided prototype to remain unmodified - // so that it can be used as a mixin for multiple widgets (#8876) - var proxiedPrototype = {}; - - var namespace = name.split( "." )[ 0 ]; - name = name.split( "." )[ 1 ]; - var fullName = namespace + "-" + name; - - if ( !prototype ) { - prototype = base; - base = $.Widget; - } - - if ( $.isArray( prototype ) ) { - prototype = $.extend.apply( null, [ {} ].concat( prototype ) ); - } - - // Create selector for plugin - $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { - return !!$.data( elem, fullName ); - }; - - $[ namespace ] = $[ namespace ] || {}; - existingConstructor = $[ namespace ][ name ]; - constructor = $[ namespace ][ name ] = function( options, element ) { - - // Allow instantiation without "new" keyword - if ( !this._createWidget ) { - return new constructor( options, element ); - } - - // Allow instantiation without initializing for simple inheritance - // must use "new" keyword (the code above always passes args) - if ( arguments.length ) { - this._createWidget( options, element ); - } - }; - - // Extend with the existing constructor to carry over any static properties - $.extend( constructor, existingConstructor, { - version: prototype.version, - - // Copy the object used to create the prototype in case we need to - // redefine the widget later - _proto: $.extend( {}, prototype ), - - // Track widgets that inherit from this widget in case this widget is - // redefined after a widget inherits from it - _childConstructors: [] - } ); - - basePrototype = new base(); - - // We need to make the options hash a property directly on the new instance - // otherwise we'll modify the options hash on the prototype that we're - // inheriting from - basePrototype.options = $.widget.extend( {}, basePrototype.options ); - $.each( prototype, function( prop, value ) { - if ( !$.isFunction( value ) ) { - proxiedPrototype[ prop ] = value; - return; - } - proxiedPrototype[ prop ] = ( function() { - function _super() { - return base.prototype[ prop ].apply( this, arguments ); - } - - function _superApply( args ) { - return base.prototype[ prop ].apply( this, args ); - } - - return function() { - var __super = this._super; - var __superApply = this._superApply; - var returnValue; - - this._super = _super; - this._superApply = _superApply; - - returnValue = value.apply( this, arguments ); - - this._super = __super; - this._superApply = __superApply; - - return returnValue; - }; - } )(); - } ); - constructor.prototype = $.widget.extend( basePrototype, { - - // TODO: remove support for widgetEventPrefix - // always use the name + a colon as the prefix, e.g., draggable:start - // don't prefix for widgets that aren't DOM-based - widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name - }, proxiedPrototype, { - constructor: constructor, - namespace: namespace, - widgetName: name, - widgetFullName: fullName - } ); - - // If this widget is being redefined then we need to find all widgets that - // are inheriting from it and redefine all of them so that they inherit from - // the new version of this widget. We're essentially trying to replace one - // level in the prototype chain. - if ( existingConstructor ) { - $.each( existingConstructor._childConstructors, function( i, child ) { - var childPrototype = child.prototype; - - // Redefine the child widget using the same prototype that was - // originally used, but inherit from the new version of the base - $.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, - child._proto ); - } ); - - // Remove the list of existing child constructors from the old constructor - // so the old child constructors can be garbage collected - delete existingConstructor._childConstructors; - } else { - base._childConstructors.push( constructor ); - } - - $.widget.bridge( name, constructor ); - - return constructor; -}; - -$.widget.extend = function( target ) { - var input = widgetSlice.call( arguments, 1 ); - var inputIndex = 0; - var inputLength = input.length; - var key; - var value; - - for ( ; inputIndex < inputLength; inputIndex++ ) { - for ( key in input[ inputIndex ] ) { - value = input[ inputIndex ][ key ]; - if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) { - - // Clone objects - if ( $.isPlainObject( value ) ) { - target[ key ] = $.isPlainObject( target[ key ] ) ? - $.widget.extend( {}, target[ key ], value ) : - - // Don't extend strings, arrays, etc. with objects - $.widget.extend( {}, value ); - - // Copy everything else by reference - } else { - target[ key ] = value; - } - } - } - } - return target; -}; - -$.widget.bridge = function( name, object ) { - var fullName = object.prototype.widgetFullName || name; - $.fn[ name ] = function( options ) { - var isMethodCall = typeof options === "string"; - var args = widgetSlice.call( arguments, 1 ); - var returnValue = this; - - if ( isMethodCall ) { - - // If this is an empty collection, we need to have the instance method - // return undefined instead of the jQuery instance - if ( !this.length && options === "instance" ) { - returnValue = undefined; - } else { - this.each( function() { - var methodValue; - var instance = $.data( this, fullName ); - - if ( options === "instance" ) { - returnValue = instance; - return false; - } - - if ( !instance ) { - return $.error( "cannot call methods on " + name + - " prior to initialization; " + - "attempted to call method '" + options + "'" ); - } - - if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { - return $.error( "no such method '" + options + "' for " + name + - " widget instance" ); - } - - methodValue = instance[ options ].apply( instance, args ); - - if ( methodValue !== instance && methodValue !== undefined ) { - returnValue = methodValue && methodValue.jquery ? - returnValue.pushStack( methodValue.get() ) : - methodValue; - return false; - } - } ); - } - } else { - - // Allow multiple hashes to be passed on init - if ( args.length ) { - options = $.widget.extend.apply( null, [ options ].concat( args ) ); - } - - this.each( function() { - var instance = $.data( this, fullName ); - if ( instance ) { - instance.option( options || {} ); - if ( instance._init ) { - instance._init(); - } - } else { - $.data( this, fullName, new object( options, this ) ); - } - } ); - } - - return returnValue; - }; -}; - -$.Widget = function( /* options, element */ ) {}; -$.Widget._childConstructors = []; - -$.Widget.prototype = { - widgetName: "widget", - widgetEventPrefix: "", - defaultElement: "
", - - options: { - classes: {}, - disabled: false, - - // Callbacks - create: null - }, - - _createWidget: function( options, element ) { - element = $( element || this.defaultElement || this )[ 0 ]; - this.element = $( element ); - this.uuid = widgetUuid++; - this.eventNamespace = "." + this.widgetName + this.uuid; - - this.bindings = $(); - this.hoverable = $(); - this.focusable = $(); - this.classesElementLookup = {}; - - if ( element !== this ) { - $.data( element, this.widgetFullName, this ); - this._on( true, this.element, { - remove: function( event ) { - if ( event.target === element ) { - this.destroy(); - } - } - } ); - this.document = $( element.style ? - - // Element within the document - element.ownerDocument : - - // Element is window or document - element.document || element ); - this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow ); - } - - this.options = $.widget.extend( {}, - this.options, - this._getCreateOptions(), - options ); - - this._create(); - - if ( this.options.disabled ) { - this._setOptionDisabled( this.options.disabled ); - } - - this._trigger( "create", null, this._getCreateEventData() ); - this._init(); - }, - - _getCreateOptions: function() { - return {}; - }, - - _getCreateEventData: $.noop, - - _create: $.noop, - - _init: $.noop, - - destroy: function() { - var that = this; - - this._destroy(); - $.each( this.classesElementLookup, function( key, value ) { - that._removeClass( value, key ); - } ); - - // We can probably remove the unbind calls in 2.0 - // all event bindings should go through this._on() - this.element - .off( this.eventNamespace ) - .removeData( this.widgetFullName ); - this.widget() - .off( this.eventNamespace ) - .removeAttr( "aria-disabled" ); - - // Clean up events and states - this.bindings.off( this.eventNamespace ); - }, - - _destroy: $.noop, - - widget: function() { - return this.element; - }, - - option: function( key, value ) { - var options = key; - var parts; - var curOption; - var i; - - if ( arguments.length === 0 ) { - - // Don't return a reference to the internal hash - return $.widget.extend( {}, this.options ); - } - - if ( typeof key === "string" ) { - - // Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } } - options = {}; - parts = key.split( "." ); - key = parts.shift(); - if ( parts.length ) { - curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] ); - for ( i = 0; i < parts.length - 1; i++ ) { - curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {}; - curOption = curOption[ parts[ i ] ]; - } - key = parts.pop(); - if ( arguments.length === 1 ) { - return curOption[ key ] === undefined ? null : curOption[ key ]; - } - curOption[ key ] = value; - } else { - if ( arguments.length === 1 ) { - return this.options[ key ] === undefined ? null : this.options[ key ]; - } - options[ key ] = value; - } - } - - this._setOptions( options ); - - return this; - }, - - _setOptions: function( options ) { - var key; - - for ( key in options ) { - this._setOption( key, options[ key ] ); - } - - return this; - }, - - _setOption: function( key, value ) { - if ( key === "classes" ) { - this._setOptionClasses( value ); - } - - this.options[ key ] = value; - - if ( key === "disabled" ) { - this._setOptionDisabled( value ); - } - - return this; - }, - - _setOptionClasses: function( value ) { - var classKey, elements, currentElements; - - for ( classKey in value ) { - currentElements = this.classesElementLookup[ classKey ]; - if ( value[ classKey ] === this.options.classes[ classKey ] || - !currentElements || - !currentElements.length ) { - continue; - } - - // We are doing this to create a new jQuery object because the _removeClass() call - // on the next line is going to destroy the reference to the current elements being - // tracked. We need to save a copy of this collection so that we can add the new classes - // below. - elements = $( currentElements.get() ); - this._removeClass( currentElements, classKey ); - - // We don't use _addClass() here, because that uses this.options.classes - // for generating the string of classes. We want to use the value passed in from - // _setOption(), this is the new value of the classes option which was passed to - // _setOption(). We pass this value directly to _classes(). - elements.addClass( this._classes( { - element: elements, - keys: classKey, - classes: value, - add: true - } ) ); - } - }, - - _setOptionDisabled: function( value ) { - this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value ); - - // If the widget is becoming disabled, then nothing is interactive - if ( value ) { - this._removeClass( this.hoverable, null, "ui-state-hover" ); - this._removeClass( this.focusable, null, "ui-state-focus" ); - } - }, - - enable: function() { - return this._setOptions( { disabled: false } ); - }, - - disable: function() { - return this._setOptions( { disabled: true } ); - }, - - _classes: function( options ) { - var full = []; - var that = this; - - options = $.extend( { - element: this.element, - classes: this.options.classes || {} - }, options ); - - function processClassString( classes, checkOption ) { - var current, i; - for ( i = 0; i < classes.length; i++ ) { - current = that.classesElementLookup[ classes[ i ] ] || $(); - if ( options.add ) { - current = $( $.unique( current.get().concat( options.element.get() ) ) ); - } else { - current = $( current.not( options.element ).get() ); - } - that.classesElementLookup[ classes[ i ] ] = current; - full.push( classes[ i ] ); - if ( checkOption && options.classes[ classes[ i ] ] ) { - full.push( options.classes[ classes[ i ] ] ); - } - } - } - - this._on( options.element, { - "remove": "_untrackClassesElement" - } ); - - if ( options.keys ) { - processClassString( options.keys.match( /\S+/g ) || [], true ); - } - if ( options.extra ) { - processClassString( options.extra.match( /\S+/g ) || [] ); - } - - return full.join( " " ); - }, - - _untrackClassesElement: function( event ) { - var that = this; - $.each( that.classesElementLookup, function( key, value ) { - if ( $.inArray( event.target, value ) !== -1 ) { - that.classesElementLookup[ key ] = $( value.not( event.target ).get() ); - } - } ); - }, - - _removeClass: function( element, keys, extra ) { - return this._toggleClass( element, keys, extra, false ); - }, - - _addClass: function( element, keys, extra ) { - return this._toggleClass( element, keys, extra, true ); - }, - - _toggleClass: function( element, keys, extra, add ) { - add = ( typeof add === "boolean" ) ? add : extra; - var shift = ( typeof element === "string" || element === null ), - options = { - extra: shift ? keys : extra, - keys: shift ? element : keys, - element: shift ? this.element : element, - add: add - }; - options.element.toggleClass( this._classes( options ), add ); - return this; - }, - - _on: function( suppressDisabledCheck, element, handlers ) { - var delegateElement; - var instance = this; - - // No suppressDisabledCheck flag, shuffle arguments - if ( typeof suppressDisabledCheck !== "boolean" ) { - handlers = element; - element = suppressDisabledCheck; - suppressDisabledCheck = false; - } - - // No element argument, shuffle and use this.element - if ( !handlers ) { - handlers = element; - element = this.element; - delegateElement = this.widget(); - } else { - element = delegateElement = $( element ); - this.bindings = this.bindings.add( element ); - } - - $.each( handlers, function( event, handler ) { - function handlerProxy() { - - // Allow widgets to customize the disabled handling - // - disabled as an array instead of boolean - // - disabled class as method for disabling individual parts - if ( !suppressDisabledCheck && - ( instance.options.disabled === true || - $( this ).hasClass( "ui-state-disabled" ) ) ) { - return; - } - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - - // Copy the guid so direct unbinding works - if ( typeof handler !== "string" ) { - handlerProxy.guid = handler.guid = - handler.guid || handlerProxy.guid || $.guid++; - } - - var match = event.match( /^([\w:-]*)\s*(.*)$/ ); - var eventName = match[ 1 ] + instance.eventNamespace; - var selector = match[ 2 ]; - - if ( selector ) { - delegateElement.on( eventName, selector, handlerProxy ); - } else { - element.on( eventName, handlerProxy ); - } - } ); - }, - - _off: function( element, eventName ) { - eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) + - this.eventNamespace; - element.off( eventName ).off( eventName ); - - // Clear the stack to avoid memory leaks (#10056) - this.bindings = $( this.bindings.not( element ).get() ); - this.focusable = $( this.focusable.not( element ).get() ); - this.hoverable = $( this.hoverable.not( element ).get() ); - }, - - _delay: function( handler, delay ) { - function handlerProxy() { - return ( typeof handler === "string" ? instance[ handler ] : handler ) - .apply( instance, arguments ); - } - var instance = this; - return setTimeout( handlerProxy, delay || 0 ); - }, - - _hoverable: function( element ) { - this.hoverable = this.hoverable.add( element ); - this._on( element, { - mouseenter: function( event ) { - this._addClass( $( event.currentTarget ), null, "ui-state-hover" ); - }, - mouseleave: function( event ) { - this._removeClass( $( event.currentTarget ), null, "ui-state-hover" ); - } - } ); - }, - - _focusable: function( element ) { - this.focusable = this.focusable.add( element ); - this._on( element, { - focusin: function( event ) { - this._addClass( $( event.currentTarget ), null, "ui-state-focus" ); - }, - focusout: function( event ) { - this._removeClass( $( event.currentTarget ), null, "ui-state-focus" ); - } - } ); - }, - - _trigger: function( type, event, data ) { - var prop, orig; - var callback = this.options[ type ]; - - data = data || {}; - event = $.Event( event ); - event.type = ( type === this.widgetEventPrefix ? - type : - this.widgetEventPrefix + type ).toLowerCase(); - - // The original event may come from any element - // so we need to reset the target on the new event - event.target = this.element[ 0 ]; - - // Copy original event properties over to the new event - orig = event.originalEvent; - if ( orig ) { - for ( prop in orig ) { - if ( !( prop in event ) ) { - event[ prop ] = orig[ prop ]; - } - } - } - - this.element.trigger( event, data ); - return !( $.isFunction( callback ) && - callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false || - event.isDefaultPrevented() ); - } -}; - -$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) { - $.Widget.prototype[ "_" + method ] = function( element, options, callback ) { - if ( typeof options === "string" ) { - options = { effect: options }; - } - - var hasOptions; - var effectName = !options ? - method : - options === true || typeof options === "number" ? - defaultEffect : - options.effect || defaultEffect; - - options = options || {}; - if ( typeof options === "number" ) { - options = { duration: options }; - } - - hasOptions = !$.isEmptyObject( options ); - options.complete = callback; - - if ( options.delay ) { - element.delay( options.delay ); - } - - if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) { - element[ method ]( options ); - } else if ( effectName !== method && element[ effectName ] ) { - element[ effectName ]( options.duration, options.easing, callback ); - } else { - element.queue( function( next ) { - $( this )[ method ](); - if ( callback ) { - callback.call( element[ 0 ] ); - } - next(); - } ); - } - }; -} ); - -var widget = $.widget; - - -/*! - * jQuery UI Position 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - * http://api.jqueryui.com/position/ - */ - -//>>label: Position -//>>group: Core -//>>description: Positions elements relative to other elements. -//>>docs: http://api.jqueryui.com/position/ -//>>demos: http://jqueryui.com/position/ - - -( function() { -var cachedScrollbarWidth, - max = Math.max, - abs = Math.abs, - rhorizontal = /left|center|right/, - rvertical = /top|center|bottom/, - roffset = /[\+\-]\d+(\.[\d]+)?%?/, - rposition = /^\w+/, - rpercent = /%$/, - _position = $.fn.position; - -function getOffsets( offsets, width, height ) { - return [ - parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ), - parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 ) - ]; -} - -function parseCss( element, property ) { - return parseInt( $.css( element, property ), 10 ) || 0; -} - -function getDimensions( elem ) { - var raw = elem[ 0 ]; - if ( raw.nodeType === 9 ) { - return { - width: elem.width(), - height: elem.height(), - offset: { top: 0, left: 0 } - }; - } - if ( $.isWindow( raw ) ) { - return { - width: elem.width(), - height: elem.height(), - offset: { top: elem.scrollTop(), left: elem.scrollLeft() } - }; - } - if ( raw.preventDefault ) { - return { - width: 0, - height: 0, - offset: { top: raw.pageY, left: raw.pageX } - }; - } - return { - width: elem.outerWidth(), - height: elem.outerHeight(), - offset: elem.offset() - }; -} - -$.position = { - scrollbarWidth: function() { - if ( cachedScrollbarWidth !== undefined ) { - return cachedScrollbarWidth; - } - var w1, w2, - div = $( "
" + - "
" ), - innerDiv = div.children()[ 0 ]; - - $( "body" ).append( div ); - w1 = innerDiv.offsetWidth; - div.css( "overflow", "scroll" ); - - w2 = innerDiv.offsetWidth; - - if ( w1 === w2 ) { - w2 = div[ 0 ].clientWidth; - } - - div.remove(); - - return ( cachedScrollbarWidth = w1 - w2 ); - }, - getScrollInfo: function( within ) { - var overflowX = within.isWindow || within.isDocument ? "" : - within.element.css( "overflow-x" ), - overflowY = within.isWindow || within.isDocument ? "" : - within.element.css( "overflow-y" ), - hasOverflowX = overflowX === "scroll" || - ( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ), - hasOverflowY = overflowY === "scroll" || - ( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight ); - return { - width: hasOverflowY ? $.position.scrollbarWidth() : 0, - height: hasOverflowX ? $.position.scrollbarWidth() : 0 - }; - }, - getWithinInfo: function( element ) { - var withinElement = $( element || window ), - isWindow = $.isWindow( withinElement[ 0 ] ), - isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9, - hasOffset = !isWindow && !isDocument; - return { - element: withinElement, - isWindow: isWindow, - isDocument: isDocument, - offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 }, - scrollLeft: withinElement.scrollLeft(), - scrollTop: withinElement.scrollTop(), - width: withinElement.outerWidth(), - height: withinElement.outerHeight() - }; - } -}; - -$.fn.position = function( options ) { - if ( !options || !options.of ) { - return _position.apply( this, arguments ); - } - - // Make a copy, we don't want to modify arguments - options = $.extend( {}, options ); - - var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions, - target = $( options.of ), - within = $.position.getWithinInfo( options.within ), - scrollInfo = $.position.getScrollInfo( within ), - collision = ( options.collision || "flip" ).split( " " ), - offsets = {}; - - dimensions = getDimensions( target ); - if ( target[ 0 ].preventDefault ) { - - // Force left top to allow flipping - options.at = "left top"; - } - targetWidth = dimensions.width; - targetHeight = dimensions.height; - targetOffset = dimensions.offset; - - // Clone to reuse original targetOffset later - basePosition = $.extend( {}, targetOffset ); - - // Force my and at to have valid horizontal and vertical positions - // if a value is missing or invalid, it will be converted to center - $.each( [ "my", "at" ], function() { - var pos = ( options[ this ] || "" ).split( " " ), - horizontalOffset, - verticalOffset; - - if ( pos.length === 1 ) { - pos = rhorizontal.test( pos[ 0 ] ) ? - pos.concat( [ "center" ] ) : - rvertical.test( pos[ 0 ] ) ? - [ "center" ].concat( pos ) : - [ "center", "center" ]; - } - pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center"; - pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center"; - - // Calculate offsets - horizontalOffset = roffset.exec( pos[ 0 ] ); - verticalOffset = roffset.exec( pos[ 1 ] ); - offsets[ this ] = [ - horizontalOffset ? horizontalOffset[ 0 ] : 0, - verticalOffset ? verticalOffset[ 0 ] : 0 - ]; - - // Reduce to just the positions without the offsets - options[ this ] = [ - rposition.exec( pos[ 0 ] )[ 0 ], - rposition.exec( pos[ 1 ] )[ 0 ] - ]; - } ); - - // Normalize collision option - if ( collision.length === 1 ) { - collision[ 1 ] = collision[ 0 ]; - } - - if ( options.at[ 0 ] === "right" ) { - basePosition.left += targetWidth; - } else if ( options.at[ 0 ] === "center" ) { - basePosition.left += targetWidth / 2; - } - - if ( options.at[ 1 ] === "bottom" ) { - basePosition.top += targetHeight; - } else if ( options.at[ 1 ] === "center" ) { - basePosition.top += targetHeight / 2; - } - - atOffset = getOffsets( offsets.at, targetWidth, targetHeight ); - basePosition.left += atOffset[ 0 ]; - basePosition.top += atOffset[ 1 ]; - - return this.each( function() { - var collisionPosition, using, - elem = $( this ), - elemWidth = elem.outerWidth(), - elemHeight = elem.outerHeight(), - marginLeft = parseCss( this, "marginLeft" ), - marginTop = parseCss( this, "marginTop" ), - collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + - scrollInfo.width, - collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + - scrollInfo.height, - position = $.extend( {}, basePosition ), - myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() ); - - if ( options.my[ 0 ] === "right" ) { - position.left -= elemWidth; - } else if ( options.my[ 0 ] === "center" ) { - position.left -= elemWidth / 2; - } - - if ( options.my[ 1 ] === "bottom" ) { - position.top -= elemHeight; - } else if ( options.my[ 1 ] === "center" ) { - position.top -= elemHeight / 2; - } - - position.left += myOffset[ 0 ]; - position.top += myOffset[ 1 ]; - - collisionPosition = { - marginLeft: marginLeft, - marginTop: marginTop - }; - - $.each( [ "left", "top" ], function( i, dir ) { - if ( $.ui.position[ collision[ i ] ] ) { - $.ui.position[ collision[ i ] ][ dir ]( position, { - targetWidth: targetWidth, - targetHeight: targetHeight, - elemWidth: elemWidth, - elemHeight: elemHeight, - collisionPosition: collisionPosition, - collisionWidth: collisionWidth, - collisionHeight: collisionHeight, - offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ], - my: options.my, - at: options.at, - within: within, - elem: elem - } ); - } - } ); - - if ( options.using ) { - - // Adds feedback as second argument to using callback, if present - using = function( props ) { - var left = targetOffset.left - position.left, - right = left + targetWidth - elemWidth, - top = targetOffset.top - position.top, - bottom = top + targetHeight - elemHeight, - feedback = { - target: { - element: target, - left: targetOffset.left, - top: targetOffset.top, - width: targetWidth, - height: targetHeight - }, - element: { - element: elem, - left: position.left, - top: position.top, - width: elemWidth, - height: elemHeight - }, - horizontal: right < 0 ? "left" : left > 0 ? "right" : "center", - vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle" - }; - if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) { - feedback.horizontal = "center"; - } - if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) { - feedback.vertical = "middle"; - } - if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) { - feedback.important = "horizontal"; - } else { - feedback.important = "vertical"; - } - options.using.call( this, props, feedback ); - }; - } - - elem.offset( $.extend( position, { using: using } ) ); - } ); -}; - -$.ui.position = { - fit: { - left: function( position, data ) { - var within = data.within, - withinOffset = within.isWindow ? within.scrollLeft : within.offset.left, - outerWidth = within.width, - collisionPosLeft = position.left - data.collisionPosition.marginLeft, - overLeft = withinOffset - collisionPosLeft, - overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset, - newOverRight; - - // Element is wider than within - if ( data.collisionWidth > outerWidth ) { - - // Element is initially over the left side of within - if ( overLeft > 0 && overRight <= 0 ) { - newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - - withinOffset; - position.left += overLeft - newOverRight; - - // Element is initially over right side of within - } else if ( overRight > 0 && overLeft <= 0 ) { - position.left = withinOffset; - - // Element is initially over both left and right sides of within - } else { - if ( overLeft > overRight ) { - position.left = withinOffset + outerWidth - data.collisionWidth; - } else { - position.left = withinOffset; - } - } - - // Too far left -> align with left edge - } else if ( overLeft > 0 ) { - position.left += overLeft; - - // Too far right -> align with right edge - } else if ( overRight > 0 ) { - position.left -= overRight; - - // Adjust based on position and margin - } else { - position.left = max( position.left - collisionPosLeft, position.left ); - } - }, - top: function( position, data ) { - var within = data.within, - withinOffset = within.isWindow ? within.scrollTop : within.offset.top, - outerHeight = data.within.height, - collisionPosTop = position.top - data.collisionPosition.marginTop, - overTop = withinOffset - collisionPosTop, - overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset, - newOverBottom; - - // Element is taller than within - if ( data.collisionHeight > outerHeight ) { - - // Element is initially over the top of within - if ( overTop > 0 && overBottom <= 0 ) { - newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - - withinOffset; - position.top += overTop - newOverBottom; - - // Element is initially over bottom of within - } else if ( overBottom > 0 && overTop <= 0 ) { - position.top = withinOffset; - - // Element is initially over both top and bottom of within - } else { - if ( overTop > overBottom ) { - position.top = withinOffset + outerHeight - data.collisionHeight; - } else { - position.top = withinOffset; - } - } - - // Too far up -> align with top - } else if ( overTop > 0 ) { - position.top += overTop; - - // Too far down -> align with bottom edge - } else if ( overBottom > 0 ) { - position.top -= overBottom; - - // Adjust based on position and margin - } else { - position.top = max( position.top - collisionPosTop, position.top ); - } - } - }, - flip: { - left: function( position, data ) { - var within = data.within, - withinOffset = within.offset.left + within.scrollLeft, - outerWidth = within.width, - offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left, - collisionPosLeft = position.left - data.collisionPosition.marginLeft, - overLeft = collisionPosLeft - offsetLeft, - overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft, - myOffset = data.my[ 0 ] === "left" ? - -data.elemWidth : - data.my[ 0 ] === "right" ? - data.elemWidth : - 0, - atOffset = data.at[ 0 ] === "left" ? - data.targetWidth : - data.at[ 0 ] === "right" ? - -data.targetWidth : - 0, - offset = -2 * data.offset[ 0 ], - newOverRight, - newOverLeft; - - if ( overLeft < 0 ) { - newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - - outerWidth - withinOffset; - if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) { - position.left += myOffset + atOffset + offset; - } - } else if ( overRight > 0 ) { - newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + - atOffset + offset - offsetLeft; - if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) { - position.left += myOffset + atOffset + offset; - } - } - }, - top: function( position, data ) { - var within = data.within, - withinOffset = within.offset.top + within.scrollTop, - outerHeight = within.height, - offsetTop = within.isWindow ? within.scrollTop : within.offset.top, - collisionPosTop = position.top - data.collisionPosition.marginTop, - overTop = collisionPosTop - offsetTop, - overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop, - top = data.my[ 1 ] === "top", - myOffset = top ? - -data.elemHeight : - data.my[ 1 ] === "bottom" ? - data.elemHeight : - 0, - atOffset = data.at[ 1 ] === "top" ? - data.targetHeight : - data.at[ 1 ] === "bottom" ? - -data.targetHeight : - 0, - offset = -2 * data.offset[ 1 ], - newOverTop, - newOverBottom; - if ( overTop < 0 ) { - newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - - outerHeight - withinOffset; - if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) { - position.top += myOffset + atOffset + offset; - } - } else if ( overBottom > 0 ) { - newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + - offset - offsetTop; - if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) { - position.top += myOffset + atOffset + offset; - } - } - } - }, - flipfit: { - left: function() { - $.ui.position.flip.left.apply( this, arguments ); - $.ui.position.fit.left.apply( this, arguments ); - }, - top: function() { - $.ui.position.flip.top.apply( this, arguments ); - $.ui.position.fit.top.apply( this, arguments ); - } - } -}; - -} )(); - -var position = $.ui.position; - - -/*! - * jQuery UI :data 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: :data Selector -//>>group: Core -//>>description: Selects elements which have data stored under the specified key. -//>>docs: http://api.jqueryui.com/data-selector/ - - -var data = $.extend( $.expr[ ":" ], { - data: $.expr.createPseudo ? - $.expr.createPseudo( function( dataName ) { - return function( elem ) { - return !!$.data( elem, dataName ); - }; - } ) : - - // Support: jQuery <1.8 - function( elem, i, match ) { - return !!$.data( elem, match[ 3 ] ); - } -} ); - -/*! - * jQuery UI Disable Selection 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: disableSelection -//>>group: Core -//>>description: Disable selection of text content within the set of matched elements. -//>>docs: http://api.jqueryui.com/disableSelection/ - -// This file is deprecated - - -var disableSelection = $.fn.extend( { - disableSelection: ( function() { - var eventType = "onselectstart" in document.createElement( "div" ) ? - "selectstart" : - "mousedown"; - - return function() { - return this.on( eventType + ".ui-disableSelection", function( event ) { - event.preventDefault(); - } ); - }; - } )(), - - enableSelection: function() { - return this.off( ".ui-disableSelection" ); - } -} ); - - -/*! - * jQuery UI Focusable 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: :focusable Selector -//>>group: Core -//>>description: Selects elements which can be focused. -//>>docs: http://api.jqueryui.com/focusable-selector/ - - - -// Selectors -$.ui.focusable = function( element, hasTabindex ) { - var map, mapName, img, focusableIfVisible, fieldset, - nodeName = element.nodeName.toLowerCase(); - - if ( "area" === nodeName ) { - map = element.parentNode; - mapName = map.name; - if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) { - return false; - } - img = $( "img[usemap='#" + mapName + "']" ); - return img.length > 0 && img.is( ":visible" ); - } - - if ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) { - focusableIfVisible = !element.disabled; - - if ( focusableIfVisible ) { - - // Form controls within a disabled fieldset are disabled. - // However, controls within the fieldset's legend do not get disabled. - // Since controls generally aren't placed inside legends, we skip - // this portion of the check. - fieldset = $( element ).closest( "fieldset" )[ 0 ]; - if ( fieldset ) { - focusableIfVisible = !fieldset.disabled; - } - } - } else if ( "a" === nodeName ) { - focusableIfVisible = element.href || hasTabindex; - } else { - focusableIfVisible = hasTabindex; - } - - return focusableIfVisible && $( element ).is( ":visible" ) && visible( $( element ) ); -}; - -// Support: IE 8 only -// IE 8 doesn't resolve inherit to visible/hidden for computed values -function visible( element ) { - var visibility = element.css( "visibility" ); - while ( visibility === "inherit" ) { - element = element.parent(); - visibility = element.css( "visibility" ); - } - return visibility !== "hidden"; -} - -$.extend( $.expr[ ":" ], { - focusable: function( element ) { - return $.ui.focusable( element, $.attr( element, "tabindex" ) != null ); - } -} ); - -var focusable = $.ui.focusable; - - - - -// Support: IE8 Only -// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop -// with a string, so we need to find the proper form. -var form = $.fn.form = function() { - return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form ); -}; - - -/*! - * jQuery UI Form Reset Mixin 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: Form Reset Mixin -//>>group: Core -//>>description: Refresh input widgets when their form is reset -//>>docs: http://api.jqueryui.com/form-reset-mixin/ - - - -var formResetMixin = $.ui.formResetMixin = { - _formResetHandler: function() { - var form = $( this ); - - // Wait for the form reset to actually happen before refreshing - setTimeout( function() { - var instances = form.data( "ui-form-reset-instances" ); - $.each( instances, function() { - this.refresh(); - } ); - } ); - }, - - _bindFormResetHandler: function() { - this.form = this.element.form(); - if ( !this.form.length ) { - return; - } - - var instances = this.form.data( "ui-form-reset-instances" ) || []; - if ( !instances.length ) { - - // We don't use _on() here because we use a single event handler per form - this.form.on( "reset.ui-form-reset", this._formResetHandler ); - } - instances.push( this ); - this.form.data( "ui-form-reset-instances", instances ); - }, - - _unbindFormResetHandler: function() { - if ( !this.form.length ) { - return; - } - - var instances = this.form.data( "ui-form-reset-instances" ); - instances.splice( $.inArray( this, instances ), 1 ); - if ( instances.length ) { - this.form.data( "ui-form-reset-instances", instances ); - } else { - this.form - .removeData( "ui-form-reset-instances" ) - .off( "reset.ui-form-reset" ); - } - } -}; - - -/*! - * jQuery UI Support for jQuery core 1.7.x 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - * - */ - -//>>label: jQuery 1.7 Support -//>>group: Core -//>>description: Support version 1.7.x of jQuery core - - - -// Support: jQuery 1.7 only -// Not a great way to check versions, but since we only support 1.7+ and only -// need to detect <1.8, this is a simple check that should suffice. Checking -// for "1.7." would be a bit safer, but the version string is 1.7, not 1.7.0 -// and we'll never reach 1.70.0 (if we do, we certainly won't be supporting -// 1.7 anymore). See #11197 for why we're not using feature detection. -if ( $.fn.jquery.substring( 0, 3 ) === "1.7" ) { - - // Setters for .innerWidth(), .innerHeight(), .outerWidth(), .outerHeight() - // Unlike jQuery Core 1.8+, these only support numeric values to set the - // dimensions in pixels - $.each( [ "Width", "Height" ], function( i, name ) { - var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ], - type = name.toLowerCase(), - orig = { - innerWidth: $.fn.innerWidth, - innerHeight: $.fn.innerHeight, - outerWidth: $.fn.outerWidth, - outerHeight: $.fn.outerHeight - }; - - function reduce( elem, size, border, margin ) { - $.each( side, function() { - size -= parseFloat( $.css( elem, "padding" + this ) ) || 0; - if ( border ) { - size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0; - } - if ( margin ) { - size -= parseFloat( $.css( elem, "margin" + this ) ) || 0; - } - } ); - return size; - } - - $.fn[ "inner" + name ] = function( size ) { - if ( size === undefined ) { - return orig[ "inner" + name ].call( this ); - } - - return this.each( function() { - $( this ).css( type, reduce( this, size ) + "px" ); - } ); - }; - - $.fn[ "outer" + name ] = function( size, margin ) { - if ( typeof size !== "number" ) { - return orig[ "outer" + name ].call( this, size ); - } - - return this.each( function() { - $( this ).css( type, reduce( this, size, true, margin ) + "px" ); - } ); - }; - } ); - - $.fn.addBack = function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - }; -} - -; -/*! - * jQuery UI Keycode 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: Keycode -//>>group: Core -//>>description: Provide keycodes as keynames -//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/ - - -var keycode = $.ui.keyCode = { - BACKSPACE: 8, - COMMA: 188, - DELETE: 46, - DOWN: 40, - END: 35, - ENTER: 13, - ESCAPE: 27, - HOME: 36, - LEFT: 37, - PAGE_DOWN: 34, - PAGE_UP: 33, - PERIOD: 190, - RIGHT: 39, - SPACE: 32, - TAB: 9, - UP: 38 -}; - - - - -// Internal use only -var escapeSelector = $.ui.escapeSelector = ( function() { - var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g; - return function( selector ) { - return selector.replace( selectorEscape, "\\$1" ); - }; -} )(); - - -/*! - * jQuery UI Labels 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: labels -//>>group: Core -//>>description: Find all the labels associated with a given input -//>>docs: http://api.jqueryui.com/labels/ - - - -var labels = $.fn.labels = function() { - var ancestor, selector, id, labels, ancestors; - - // Check control.labels first - if ( this[ 0 ].labels && this[ 0 ].labels.length ) { - return this.pushStack( this[ 0 ].labels ); - } - - // Support: IE <= 11, FF <= 37, Android <= 2.3 only - // Above browsers do not support control.labels. Everything below is to support them - // as well as document fragments. control.labels does not work on document fragments - labels = this.eq( 0 ).parents( "label" ); - - // Look for the label based on the id - id = this.attr( "id" ); - if ( id ) { - - // We don't search against the document in case the element - // is disconnected from the DOM - ancestor = this.eq( 0 ).parents().last(); - - // Get a full set of top level ancestors - ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() ); - - // Create a selector for the label based on the id - selector = "label[for='" + $.ui.escapeSelector( id ) + "']"; - - labels = labels.add( ancestors.find( selector ).addBack( selector ) ); - - } - - // Return whatever we have found for labels - return this.pushStack( labels ); -}; - - -/*! - * jQuery UI Scroll Parent 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: scrollParent -//>>group: Core -//>>description: Get the closest ancestor element that is scrollable. -//>>docs: http://api.jqueryui.com/scrollParent/ - - - -var scrollParent = $.fn.scrollParent = function( includeHidden ) { - var position = this.css( "position" ), - excludeStaticParent = position === "absolute", - overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/, - scrollParent = this.parents().filter( function() { - var parent = $( this ); - if ( excludeStaticParent && parent.css( "position" ) === "static" ) { - return false; - } - return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) + - parent.css( "overflow-x" ) ); - } ).eq( 0 ); - - return position === "fixed" || !scrollParent.length ? - $( this[ 0 ].ownerDocument || document ) : - scrollParent; -}; - - -/*! - * jQuery UI Tabbable 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: :tabbable Selector -//>>group: Core -//>>description: Selects elements which can be tabbed to. -//>>docs: http://api.jqueryui.com/tabbable-selector/ - - - -var tabbable = $.extend( $.expr[ ":" ], { - tabbable: function( element ) { - var tabIndex = $.attr( element, "tabindex" ), - hasTabindex = tabIndex != null; - return ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex ); - } -} ); - - -/*! - * jQuery UI Unique ID 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: uniqueId -//>>group: Core -//>>description: Functions to generate and remove uniqueId's -//>>docs: http://api.jqueryui.com/uniqueId/ - - - -var uniqueId = $.fn.extend( { - uniqueId: ( function() { - var uuid = 0; - - return function() { - return this.each( function() { - if ( !this.id ) { - this.id = "ui-id-" + ( ++uuid ); - } - } ); - }; - } )(), - - removeUniqueId: function() { - return this.each( function() { - if ( /^ui-id-\d+$/.test( this.id ) ) { - $( this ).removeAttr( "id" ); - } - } ); - } -} ); - - - - -// This file is deprecated -var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() ); - -/*! - * jQuery UI Mouse 1.12.1 - * http://jqueryui.com - * - * Copyright jQuery Foundation and other contributors - * Released under the MIT license. - * http://jquery.org/license - */ - -//>>label: Mouse -//>>group: Widgets -//>>description: Abstracts mouse-based interactions to assist in creating certain widgets. -//>>docs: http://api.jqueryui.com/mouse/ - - - -var mouseHandled = false; -$( document ).on( "mouseup", function() { - mouseHandled = false; -} ); - -var widgetsMouse = $.widget( "ui.mouse", { - version: "1.12.1", - options: { - cancel: "input, textarea, button, select, option", - distance: 1, - delay: 0 - }, - _mouseInit: function() { - var that = this; - - this.element - .on( "mousedown." + this.widgetName, function( event ) { - return that._mouseDown( event ); - } ) - .on( "click." + this.widgetName, function( event ) { - if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) { - $.removeData( event.target, that.widgetName + ".preventClickEvent" ); - event.stopImmediatePropagation(); - return false; - } - } ); - - this.started = false; - }, - - // TODO: make sure destroying one instance of mouse doesn't mess with - // other instances of mouse - _mouseDestroy: function() { - this.element.off( "." + this.widgetName ); - if ( this._mouseMoveDelegate ) { - this.document - .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); - } - }, - - _mouseDown: function( event ) { - - // don't let more than one widget handle mouseStart - if ( mouseHandled ) { - return; - } - - this._mouseMoved = false; - - // We may have missed mouseup (out of window) - ( this._mouseStarted && this._mouseUp( event ) ); - - this._mouseDownEvent = event; - - var that = this, - btnIsLeft = ( event.which === 1 ), - - // event.target.nodeName works around a bug in IE 8 with - // disabled inputs (#7620) - elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ? - $( event.target ).closest( this.options.cancel ).length : false ); - if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) { - return true; - } - - this.mouseDelayMet = !this.options.delay; - if ( !this.mouseDelayMet ) { - this._mouseDelayTimer = setTimeout( function() { - that.mouseDelayMet = true; - }, this.options.delay ); - } - - if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { - this._mouseStarted = ( this._mouseStart( event ) !== false ); - if ( !this._mouseStarted ) { - event.preventDefault(); - return true; - } - } - - // Click event may never have fired (Gecko & Opera) - if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) { - $.removeData( event.target, this.widgetName + ".preventClickEvent" ); - } - - // These delegates are required to keep context - this._mouseMoveDelegate = function( event ) { - return that._mouseMove( event ); - }; - this._mouseUpDelegate = function( event ) { - return that._mouseUp( event ); - }; - - this.document - .on( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .on( "mouseup." + this.widgetName, this._mouseUpDelegate ); - - event.preventDefault(); - - mouseHandled = true; - return true; - }, - - _mouseMove: function( event ) { - - // Only check for mouseups outside the document if you've moved inside the document - // at least once. This prevents the firing of mouseup in the case of IE<9, which will - // fire a mousemove event if content is placed under the cursor. See #7778 - // Support: IE <9 - if ( this._mouseMoved ) { - - // IE mouseup check - mouseup happened when mouse was out of window - if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && - !event.button ) { - return this._mouseUp( event ); - - // Iframe mouseup check - mouseup occurred in another document - } else if ( !event.which ) { - - // Support: Safari <=8 - 9 - // Safari sets which to 0 if you press any of the following keys - // during a drag (#14461) - if ( event.originalEvent.altKey || event.originalEvent.ctrlKey || - event.originalEvent.metaKey || event.originalEvent.shiftKey ) { - this.ignoreMissingWhich = true; - } else if ( !this.ignoreMissingWhich ) { - return this._mouseUp( event ); - } - } - } - - if ( event.which || event.button ) { - this._mouseMoved = true; - } - - if ( this._mouseStarted ) { - this._mouseDrag( event ); - return event.preventDefault(); - } - - if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) { - this._mouseStarted = - ( this._mouseStart( this._mouseDownEvent, event ) !== false ); - ( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) ); - } - - return !this._mouseStarted; - }, - - _mouseUp: function( event ) { - this.document - .off( "mousemove." + this.widgetName, this._mouseMoveDelegate ) - .off( "mouseup." + this.widgetName, this._mouseUpDelegate ); - - if ( this._mouseStarted ) { - this._mouseStarted = false; - - if ( event.target === this._mouseDownEvent.target ) { - $.data( event.target, this.widgetName + ".preventClickEvent", true ); - } - - this._mouseStop( event ); - } - - if ( this._mouseDelayTimer ) { - clearTimeout( this._mouseDelayTimer ); - delete this._mouseDelayTimer; - } - - this.ignoreMissingWhich = false; - mouseHandled = false; - event.preventDefault(); - }, - - _mouseDistanceMet: function( event ) { - return ( Math.max( - Math.abs( this._mouseDownEvent.pageX - event.pageX ), - Math.abs( this._mouseDownEvent.pageY - event.pageY ) - ) >= this.options.distance - ); - }, - - _mouseDelayMet: function( /* event */ ) { - return this.mouseDelayMet; - }, - - // These are placeholder methods, to be overriden by extending plugin - _mouseStart: function( /* event */ ) {}, - _mouseDrag: function( /* event */ ) {}, - _mouseStop: function( /* event */ ) {}, - _mouseCapture: function( /* event */ ) { return true; } -} ); - - - - -// $.ui.plugin is deprecated. Use $.widget() extensions instead. -var plugin = $.ui.plugin = { - add: function( module, option, set ) { - var i, - proto = $.ui[ module ].prototype; - for ( i in set ) { - proto.plugins[ i ] = proto.plugins[ i ] || []; - proto.plugins[ i ].push( [ option, set[ i ] ] ); - } - }, - call: function( instance, name, args, allowDisconnected ) { - var i, - set = instance.plugins[ name ]; - - if ( !set ) { - return; - } - - if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode || - instance.element[ 0 ].parentNode.nodeType === 11 ) ) { - return; - } - - for ( i = 0; i < set.length; i++ ) { - if ( instance.options[ set[ i ][ 0 ] ] ) { - set[ i ][ 1 ].apply( instance.element, args ); - } - } - } -}; - - - -var safeActiveElement = $.ui.safeActiveElement = function( document ) { - var activeElement; - - // Support: IE 9 only - // IE9 throws an "Unspecified error" accessing document.activeElement from an