Merge branch '2.4' into debian_2_4_125

pull/5850/head
stricaud 2020-05-02 11:14:39 -07:00 committed by GitHub
commit c1ee4d8bfb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
157 changed files with 29890 additions and 12992 deletions

6
.gitmodules vendored
View File

@ -37,12 +37,6 @@
path = Plugin/DebugKit
url = https://github.com/cakephp/debug_kit.git
branch = 2.2
[submodule "INSTALL/Crypt_GPG"]
path = INSTALL/dependencies/Crypt_GPG
url = https://github.com/pear/Crypt_GPG
[submodule "INSTALL/Console_CommandLine"]
path = INSTALL/dependencies/Console_CommandLine
url = https://github.com/pear/Console_CommandLine
[submodule "app/files/misp-decaying-models"]
path = app/files/misp-decaying-models
url = https://github.com/MISP/misp-decaying-models.git

829
INSTALL/INSTALL.sh Normal file → Executable file

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +1,5 @@
; Generated by RHash v1.3.9 on 2020-03-18 at 13:56.48
; Generated by RHash v1.3.8 on 2020-05-01 at 12:24.54
; Written by Kravchenko Aleksey (Akademgorodok) - http://rhash.sf.net/
;
; 99980 13:56.48 2020-03-18 INSTALL.sh
INSTALL.sh 04A834FCD3BC9DA5282EDE8A3D2C459FBC625E46 FBCA1473FEC26AD5A6C1AE6AE3D9AF11E47E7758F30B160BC047ABE9978F4476 7281B13AA7D6B016152096D35619C2CECC7EC49F8F41CF8A3B8284335D950D35F273FA56FEA63EC5ADB3669038239C61 FA17DF3AA0CBC54D2B48AE14FB296C91C12FC6CF8E3704B8AF1B2CB2CDE9C6FAF591A2E42A38C01C299C62390868E7766EF682A6B3B556BAFC469688E8AED6E7
; 131599 12:24.54 2020-05-01 INSTALL.sh
INSTALL.sh 459A7BDAD0014A5BC5BDC4FBB2AEA8A18958D170 EE218F9A83C4E22A6DA65780FB5EB6624104C71A9B3D1743EDB4511AB790B57B B7D0734C17AC66C6B714DCF24CA6053DB31EC635E4AFD2F741601DF2413B1798A9FC9295A8DDE443F315C226DEB9E5A5 D28781C84601334CC88F1C57A869487FAB2CFD199CE83AE658798F3A75777741A49B561C72E85BB9537556E5D3785153339FBD9EEB396088BD0A2DEFE5199319

View File

@ -1 +1 @@
04a834fcd3bc9da5282ede8a3d2c459fbc625e46 INSTALL.sh
459a7bdad0014a5bc5bdc4fbb2aea8a18958d170 INSTALL.sh

View File

@ -1 +1 @@
fbca1473fec26ad5a6c1ae6ae3d9af11e47e7758f30b160bc047abe9978f4476 INSTALL.sh
ee218f9a83c4e22a6da65780fb5eb6624104c71a9b3d1743edb4511ab790b57b INSTALL.sh

View File

@ -1 +1 @@
7281b13aa7d6b016152096d35619c2cecc7ec49f8f41cf8a3b8284335d950d35f273fa56fea63ec5adb3669038239c61 INSTALL.sh
b7d0734c17ac66c6b714dcf24ca6053db31ec635e4afd2f741601df2413b1798a9fc9295a8dde443f315c226deb9e5a5 INSTALL.sh

View File

@ -1 +1 @@
fa17df3aa0cbc54d2b48ae14fb296c91c12fc6cf8e3704b8af1b2cb2cde9c6faf591a2e42a38c01c299c62390868e7766ef682a6b3b556bafc469688e8aed6e7 INSTALL.sh
d28781c84601334cc88f1c57a869487fab2cfd199ce83ae658798f3a75777741a49b561c72e85bb9537556e5d3785153339fbd9eeb396088bd0a2defe5199319 INSTALL.sh

View File

@ -16,6 +16,7 @@
# 0/ Quick MISP Instance on Debian Based Linux - Status |
#-------------------------------------------------------|
#
# 20200412: Ubuntu 18.04.4 tested and working. -- sCl
# 20190302: Ubuntu 18.04.2 tested and working. -- sCl
# 20190208: Kali Linux tested and working. -- sCl
#
@ -70,6 +71,7 @@
## 0_apt-upgrade.sh ##
## 0_sudoKeeper.sh ##
## 0_installCoreDeps.sh ##
## 0_installDepsPhp74.sh ##
## 0_installDepsPhp73.sh ##
## 0_installDepsPhp72.sh ##
## 0_installDepsPhp70.sh ##
@ -90,6 +92,21 @@
## 6_ssdeep.sh ##
## 6_viper.sh ##
## 0_RHEL_SCL.sh ##
## 0_CentOS_EPEL.sh ##
## 0_RHEL_EPEL.sh ##
## 0_yumInstallCoreDeps.sh ##
## 1_mispCoreInstall_RHEL.sh ##
## 1_installCake_RHEL.sh ##
## 1_prepareDB_RHEL.sh ##
## 1_apacheConfig_RHEL.sh ##
## 1_firewall_RHEL.sh ##
## 2_permissions_RHEL.sh ##
## 2_logRotation_RHEL.sh ##
## 2_configMISP_RHEL.sh ##
## 3_configWorkers_RHEL.sh ##
## 3_misp-modules_RHEL.sh ##
# No functions scripts:
## apt-upgrade.sh ##
## postfix.sh ##
@ -128,12 +145,12 @@ generateInstaller () {
cp ../INSTALL.tpl.sh .
# Pull code snippets out of Main Install Documents
for f in `echo INSTALL.ubuntu1804.md xINSTALL.debian9.md INSTALL.kali.md xINSTALL.debian10.md xINSTALL.tsurugi.md xINSTALL.debian9-postgresql.md xINSTALL.ubuntu1804.with.webmin.md`; do
for f in `echo INSTALL.ubuntu2004.md INSTALL.ubuntu1804.md xINSTALL.debian9.md INSTALL.kali.md xINSTALL.debian10.md xINSTALL.tsurugi.md xINSTALL.debian9-postgresql.md xINSTALL.ubuntu1804.with.webmin.md INSTALL.rhel7.md`; do
xsnippet . ../../docs/${f}
done
# Pull out code snippets from generic Install Documents
for f in `echo globalVariables.md mail_to_misp-debian.md MISP_CAKE_init.md misp-dashboard-debian.md misp-modules-debian.md gnupg.md ssdeep-debian.md sudo_etckeeper.md supportFunctions.md viper-debian.md`; do
for f in `echo globalVariables.md mail_to_misp-debian.md MISP_CAKE_init.md misp-dashboard-debian.md misp-modules-debian.md gnupg.md ssdeep-debian.md sudo_etckeeper.md supportFunctions.md viper-debian.md misp-modules-centos.md`; do
xsnippet . ../../docs/generic/${f}
done
@ -147,6 +164,7 @@ generateInstaller () {
perl -pe 's/^## 0_apt-upgrade.sh ##/`cat 0_apt-upgrade.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_sudoKeeper.sh ##/`cat 0_sudoKeeper.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_installCoreDeps.sh ##/`cat 0_installCoreDeps.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_installDepsPhp74.sh ##/`cat 0_installDepsPhp74.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_installDepsPhp73.sh ##/`cat 0_installDepsPhp73.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_installDepsPhp72.sh ##/`cat 0_installDepsPhp72.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_installDepsPhp70.sh ##/`cat 0_installDepsPhp70.sh`/ge' -i INSTALL.tpl.sh
@ -168,6 +186,21 @@ generateInstaller () {
perl -pe 's/^## 6_viper.sh ##/`cat 6_viper.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 6_ssdeep.sh ##/`cat 6_ssdeep.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_RHEL_SCL.sh ##/`cat 0_RHEL_SCL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_CentOS_EPEL.sh ##/`cat 0_CentOS_EPEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_RHEL_EPEL.sh ##/`cat 0_RHEL_EPEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 0_yumInstallCoreDeps.sh ##/`cat 0_yumInstallCoreDeps.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 1_mispCoreInstall_RHEL.sh ##/`cat 1_mispCoreInstall_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 1_installCake_RHEL.sh ##/`cat 1_installCake_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 2_permissions_RHEL.sh ##/`cat 2_permissions_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 1_prepareDB_RHEL.sh ##/`cat 1_prepareDB_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 1_apacheConfig_RHEL.sh ##/`cat 1_apacheConfig_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 1_firewall_RHEL.sh ##/`cat 1_firewall_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 2_logRotation_RHEL.sh ##/`cat 2_logRotation_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 2_configMISP_RHEL.sh ##/`cat 2_configMISP_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 3_configWorkers_RHEL.sh ##/`cat 3_configWorkers_RHEL.sh`/ge' -i INSTALL.tpl.sh
perl -pe 's/^## 3_misp-modules_RHEL.sh ##/`cat 3_misp-modules_RHEL.sh`/ge' -i INSTALL.tpl.sh
cp INSTALL.tpl.sh ../INSTALL.sh
cd ..
for ALGO in $(echo "1 256 384 512"); do
@ -245,18 +278,21 @@ installSupported () {
if [[ "$1" =~ ^PHP= ]]; then
PHP_VER=$(echo $1 |cut -f2 -d=)
if [[ "$PHP_VER" == "7.2" ]]; then
if [[ "$PHP_VER" == 7.2 ]]; then
# Install PHP 7.2 Dependencies - functionLocation('INSTALL.ubuntu1804.md')
[[ -n $CORE ]] || [[ -n $ALL ]] && installDepsPhp72
elif [[ "$PHP_VER" == "7.3" ]]; then
# Install PHP 7.3 Dependencies - functionLocation('generic/supportFunctions.md')
elif [[ "$PHP_VER" == 7.3 ]]; then
# Install PHP 7.4 Dependencies - functionLocation('INSTALL.ubuntu2004.md')
[[ -n $CORE ]] || [[ -n $ALL ]] && installDepsPhp73
elif [[ "$PHP_VER" == "7.0" ]]; then
elif [[ "$PHP_VER" == 7.4 ]]; then
# Install PHP 7.3 Dependencies - functionLocation('generic/supportFunctions.md')
[[ -n $CORE ]] || [[ -n $ALL ]] && installDepsPhp74
elif [[ "$PHP_VER" == 7.0 ]]; then
# Install PHP 7.0 Dependencies - functionLocation('generic/supportFunctions.md')
[[ -n $CORE ]] || [[ -n $ALL ]] && installDepsPhp70
fi
else
# Install PHP 7.2 Dependencies - functionLocation('INSTALL.ubuntu1804.md')
# Install PHP 7.2 Dependencies by dangerous default - functionLocation('INSTALL.ubuntu1804.md')
[[ -n $CORE ]] || [[ -n $ALL ]] && installDepsPhp72
fi
progress 4
@ -349,7 +385,7 @@ installSupported () {
theEnd
}
# Main Kalin Install function
# Main Kali Install function
installMISPonKali () {
# Kali might have a bug on installs where libc6 is not up to date, this forces bash and libc to update - functionLocation('')
kaliUpgrade 2> /dev/null > /dev/null
@ -464,7 +500,7 @@ installMISPonKali () {
$SUDO_WWW ${PATH_TO_MISP}/venv/bin/pip install git+https://github.com/kbandla/pydeep.git 2> /dev/null > /dev/null
# install lief
$SUDO_WWW ${PATH_TO_MISP}/venv/bin/pip install https://github.com/lief-project/packages/raw/lief-master-latest/pylief-0.9.0.dev.zip 2> /dev/null > /dev/null
$SUDO_WWW ${PATH_TO_MISP}/venv/bin/pip install lief 2> /dev/null > /dev/null
# install python-magic
$SUDO_WWW ${PATH_TO_MISP}/venv/bin/pip install python-magic 2> /dev/null > /dev/null
@ -620,6 +656,88 @@ installMISPonKali () {
}
# End installMISPonKali ()
# Main Install on RHEL function
installMISPRHEL () {
if [[ -n $SSDEEP ]] || [[ -n $MAIL2 ]]; then
echo "RHEL installation currently only supports Core and Modules"
echo "Ignoring other options..."
fi
if [[ -n $CORE ]] || [[ -n $ALL ]]; then
space
echo "Proceeding with MISP core installation on RHEL $dist_version"
space
id -u "$MISP_USER" > /dev/null
if [ $? -eq 1 ]; then
debug "Creating MISP user"
sudo useradd -r "$MISP_USER"
fi
debug "Enabling Extras Repos (SCL)"
if [[ $FLAVOUR == "rhel" ]]; then
sudo subscription-manager register --auto-attach
enableReposRHEL
enableEPEL
else # CentOS
centosEPEL
fi
debug "Installing System Dependencies"
yumInstallCoreDeps
debug "Enabling Haveged for additional entropy"
sudo yum install haveged -y
sudo systemctl enable --now haveged.service
debug "Installing MISP code"
installCoreRHEL
debug "Install Cake PHP"
installCake_RHEL
debug "Setting File permissions"
permissions_RHEL
debug "Preparing Database"
prepareDB_RHEL
debug "Configuring Apache"
apacheConfig_RHEL
debug "Setting up firewall"
firewall_RHEL
debug "Enabling log rotation"
logRotation_RHEL
debug "Configuring MISP"
configMISP_RHEL
debug "Setting up background workers"
configWorkersRHEL
debug "Optimizing Cake Installation"
coreCAKE
debug "Updating tables"
updateGOWNT
echo "Core Intallation finished, check on port 443 to see the Web UI"
fi
if [[ -n $MODULES ]] || [[ -n $ALL ]]; then
space
echo "Installing MISP Modules"
space
mispmodulesRHEL
echo "MISP modules installation finished."
fi
}
# End installMISPRHEL ()
## End Function Section ##
colors
@ -628,14 +746,14 @@ if [[ "$0" == "./INSTALL.tpl.sh" || "$(echo $0 |grep -o -e 'INSTALL.tpl.sh')" ==
generateInstaller
fi
debug "Checking Linux distribution and flavour..."
checkFlavour
debug "Checking if we are uptodate and checksums match"
checkInstaller
space
debug "Setting MISP variables"
MISPvars
debug "Checking Linux distribution and flavour..."
checkFlavour
debug "Checking for parameters or Unattended Kali Install"
if [[ $# == 0 && $0 != "/tmp/misp-kali.sh" ]]; then
@ -676,13 +794,13 @@ fi
# TODO: Move support map to top
SUPPORT_MAP="
x86_64-centos-8
x86_64-centos-7
x86_64-rhel-7
x86_64-rhel-8
x86_64-fedora-30
x86_64-debian-stretch
x86_64-debian-buster
x86_64-ubuntu-bionic
x86_64-ubuntu-focal
x86_64-kali-2019.1
x86_64-kali-2019.2
x86_64-kali-2019.3
@ -697,6 +815,7 @@ armv7l-debian-jessie
armv7l-debian-stretch
armv7l-debian-buster
armv7l-ubuntu-bionic
armv7l-ubuntu-focal
"
# Check if we actually support this configuration
@ -718,12 +837,18 @@ if [ "${FLAVOUR}" == "ubuntu" ]; then
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
installSupported && exit || exit
fi
if [ "${RELEASE}" == "20.04" ]; then
echo "Install on Ubuntu 20.04 LTS fully supported."
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
installSupported PHP="7.4" && exit || exit
fi
if [ "${RELEASE}" == "18.10" ]; then
echo "Install on Ubuntu 18.10 partially supported, bye."
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
installSupported && exit || exit
fi
if [ "${RELEASE}" == "19.04" ]; then
echo "Install on Ubuntu 19.04 under development."
echo "Install on Ubuntu 19.04 partially supported bye."
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
installSupported && exit || exit
exit 1
@ -781,3 +906,10 @@ if [ "${FLAVOUR}" == "kali" ]; then
echo "Installation done!"
exit
fi
# If RHEL/CentOS is detected, run appropriate script
if [ "${FLAVOUR}" == "rhel" ] || [ "${FLAVOUR}" == "centos" ]; then
installMISPRHEL
echo "Installation done !"
exit
fi

View File

@ -916,7 +916,7 @@ CREATE TABLE IF NOT EXISTS `shadow_attribute_correlations` (
-- Table structure for table `sharing_group_orgs`
--
CREATE TABLE `sharing_group_orgs` (
CREATE TABLE IF NOT EXISTS `sharing_group_orgs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sharing_group_id` int(11) NOT NULL,
`org_id` int(11) NOT NULL,
@ -932,7 +932,7 @@ CREATE TABLE `sharing_group_orgs` (
-- Table structure for table `sharing_group_servers`
--
CREATE TABLE `sharing_group_servers` (
CREATE TABLE IF NOT EXISTS `sharing_group_servers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`sharing_group_id` int(11) NOT NULL,
`server_id` int(11) NOT NULL,
@ -948,7 +948,7 @@ CREATE TABLE `sharing_group_servers` (
-- Table structure for table `sharing_groups`
--
CREATE TABLE `sharing_groups` (
CREATE TABLE IF NOT EXISTS `sharing_groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`releasability` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
@ -1355,14 +1355,14 @@ CREATE TABLE IF NOT EXISTS `whitelist` (
-- Default values for initial installation
--
INSERT INTO `admin_settings` (`id`, `setting`, `value`) VALUES
INSERT IGNORE INTO `admin_settings` (`id`, `setting`, `value`) VALUES
(1, 'db_version', '40');
INSERT INTO `feeds` (`id`, `provider`, `name`, `url`, `distribution`, `default`, `enabled`) VALUES
INSERT IGNORE INTO `feeds` (`id`, `provider`, `name`, `url`, `distribution`, `default`, `enabled`) VALUES
(1, 'CIRCL', 'CIRCL OSINT Feed', 'https://www.circl.lu/doc/misp/feed-osint', 3, 1, 0),
(2, 'Botvrij.eu', 'The Botvrij.eu Data', 'https://www.botvrij.eu/data/feed-osint', 3, 1, 0);
INSERT INTO `regexp` (`id`, `regexp`, `replacement`, `type`) VALUES
INSERT IGNORE INTO `regexp` (`id`, `regexp`, `replacement`, `type`) VALUES
(1, '/.:.ProgramData./i', '%ALLUSERSPROFILE%\\\\', 'ALL'),
(2, '/.:.Documents and Settings.All Users./i', '%ALLUSERSPROFILE%\\\\', 'ALL'),
(3, '/.:.Program Files.Common Files./i', '%COMMONPROGRAMFILES%\\\\', 'ALL'),
@ -1407,22 +1407,22 @@ INSERT INTO `feeds` (`id`, `provider`, `name`, `url`, `distribution`, `default`,
-- 7. Read Only - read
--
INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
INSERT IGNORE INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
VALUES (1, 'admin', NOW(), NOW(), 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0);
INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
INSERT IGNORE INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
VALUES (2, 'Org Admin', NOW(), NOW(), 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0);
INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
INSERT IGNORE INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
VALUES (3, 'User', NOW(), NOW(), 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 1, 0, 1, 1);
INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
INSERT IGNORE INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
VALUES (4, 'Publisher', NOW(), NOW(), 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0, 1, 0);
INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
INSERT IGNORE INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
VALUES (5, 'Sync user', NOW(), NOW(), 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 0, 1, 0);
INSERT INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
INSERT IGNORE INTO `roles` (`id`, `name`, `created`, `modified`, `perm_add`, `perm_modify`, `perm_modify_org`, `perm_publish`, `perm_publish_zmq`, `perm_publish_kafka`, `perm_sync`, `perm_admin`, `perm_audit`, `perm_full`, `perm_auth`, `perm_regexp_access`, `perm_tagger`, `perm_site_admin`, `perm_template`, `perm_sharing_group`, `perm_tag_editor`, `perm_delegate`, `perm_sighting`, `perm_object_template`, `perm_decaying`, `default_role`)
VALUES (6, 'Read Only', NOW(), NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
-- --------------------------------------------------------
@ -1431,7 +1431,7 @@ VALUES (6, 'Read Only', NOW(), NOW(), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
-- Initial threat levels
--
INSERT INTO `threat_levels` (`id`, `name`, `description`, `form_description`)
INSERT IGNORE INTO `threat_levels` (`id`, `name`, `description`, `form_description`)
VALUES
(1, 'High', '*high* means sophisticated APT malware or 0-day attack', 'Sophisticated APT malware or 0-day attack'),
(2, 'Medium', '*medium* means APT malware', 'APT malware'),
@ -1444,13 +1444,13 @@ VALUES
-- Default templates
--
INSERT INTO `templates` (`id`, `name`, `description`, `org`, `share`) VALUES
INSERT IGNORE INTO `templates` (`id`, `name`, `description`, `org`, `share`) VALUES
(1, 'Phishing E-mail', 'Create a MISP event about a Phishing E-mail.', 'MISP', 1),
(2, 'Phishing E-mail with malicious attachment', 'A MISP event based on Spear-phishing containing a malicious attachment. This event can include anything from the description of the e-mail itself, the malicious attachment and its description as well as the results of the analysis done on the malicious f', 'MISP', 1),
(3, 'Malware Report', 'This is a template for a generic malware report. ', 'MISP', 1),
(4, 'Indicator List', 'A simple template for indicator lists.', 'MISP', 1);
INSERT INTO `template_elements` (`id`, `template_id`, `position`, `element_definition`) VALUES
INSERT IGNORE INTO `template_elements` (`id`, `template_id`, `position`, `element_definition`) VALUES
(1, 1, 2, 'attribute'),
(2, 1, 3, 'attribute'),
(3, 1, 1, 'text'),
@ -1497,7 +1497,7 @@ INSERT INTO `template_elements` (`id`, `template_id`, `position`, `element_defin
(46, 4, 2, 'attribute'),
(47, 4, 3, 'attribute');
INSERT INTO `template_element_attributes` (`id`, `template_element_id`, `name`, `description`, `to_ids`, `category`, `complex`, `type`, `mandatory`, `batch`) VALUES
INSERT IGNORE INTO `template_element_attributes` (`id`, `template_element_id`, `name`, `description`, `to_ids`, `category`, `complex`, `type`, `mandatory`, `batch`) VALUES
(1, 1, 'From address', 'The source address from which the e-mail was sent.', 1, 'Payload delivery', 0, 'email-src', 1, 1),
(2, 2, 'Malicious url', 'The malicious url in the e-mail body.', 1, 'Payload delivery', 0, 'url', 1, 1),
(3, 4, 'E-mail subject', 'The subject line of the e-mail.', 0, 'Payload delivery', 0, 'email-subject', 1, 0),
@ -1529,13 +1529,13 @@ INSERT INTO `template_element_attributes` (`id`, `template_element_id`, `name`,
(29, 46, 'Network Indicators', 'Paste any combination of IP addresses, hostnames, domains or URL', 1, 'Network activity', 1, 'CnC', 0, 1),
(30, 47, 'File Indicators', 'Paste any file hashes that you have (MD5, SHA1, SHA256) or filenames below. You can also add filename and hash pairs by using the following syntax for each applicable column: filename|hash ', 1, 'Payload installation', 1, 'File', 0, 1);
INSERT INTO `template_element_files` (`id`, `template_element_id`, `name`, `description`, `category`, `malware`, `mandatory`, `batch`) VALUES
INSERT IGNORE INTO `template_element_files` (`id`, `template_element_id`, `name`, `description`, `category`, `malware`, `mandatory`, `batch`) VALUES
(1, 14, 'Malicious Attachment', 'The file (or files) that was (were) attached to the e-mail itself.', 'Payload delivery', 1, 0, 1),
(2, 21, 'Payload installation', 'Payload installation detected during the analysis', 'Payload installation', 1, 0, 1),
(3, 30, 'Malware sample', 'The sample that the report is based on', 'Payload delivery', 1, 0, 0),
(4, 40, 'Artifacts dropped (Sample)', 'Upload any files that were dropped during the analysis.', 'Artifacts dropped', 1, 0, 1);
INSERT INTO `template_element_texts` (`id`, `name`, `template_element_id`, `text`) VALUES
INSERT IGNORE INTO `template_element_texts` (`id`, `name`, `template_element_id`, `text`) VALUES
(1, 'Required fields', 3, 'The fields below are mandatory.'),
(2, 'Optional information', 5, 'All of the fields below are optional, please fill out anything that''s applicable.'),
(4, 'Required Fields', 11, 'The following fields are mandatory'),
@ -1548,6 +1548,6 @@ INSERT INTO `template_element_texts` (`id`, `name`, `template_element_id`, `text
(11, 'Persistence mechanism', 41, 'The following fields allow you to describe the persistence mechanism used by the malware'),
(12, 'Indicators', 45, 'Just paste your list of indicators based on type into the appropriate field. All of the fields are optional, so inputting a list of IP addresses into the Network indicator field for example is sufficient to complete this template.');
INSERT INTO `org_blacklists` (`org_uuid`, `created`, `org_name`, `comment`) VALUES
INSERT IGNORE INTO `org_blacklists` (`org_uuid`, `created`, `org_name`, `comment`) VALUES
('58d38339-7b24-4386-b4b4-4c0f950d210f', NOW(), 'Setec Astrononomy', 'default example'),
('58d38326-eda8-443a-9fa8-4e12950d210f', NOW(), 'Acme Finance', 'default example');

View File

@ -6,7 +6,6 @@ The text files in this folder are symlink to ../docs - Which is the actual sourc
Currently the following install guides are being tested on a regular basis:
```
INSTALL.debian9.txt
INSTALL.kali.txt
INSTALL.ubuntu1804.txt
```

2
PyMISP

@ -1 +1 @@
Subproject commit 64d7c9a24ad9d3a7ccc1b96fb643c235d2b9e02e
Subproject commit 5cc7a1ad57c2e5a90092644e61c3de1e3e449a36

11
SECURITY.md Normal file
View File

@ -0,0 +1,11 @@
## Reporting security vulnerabilities for MISP or related MISP project repositories
Reporting security vulnerabilities is of great importance for us, as MISP is used in multiple critical infrastructures.
In the case of a security vulnerability report, we ask the reporter to send it directly to [CIRCL](https://www.circl.lu/contact/), if possible encrypted with the following GnuPG key: **CA57 2205 C002 4E06 BA70 BE89 EAAD CFFC 22BD 4CD5**. We usually fix reported and confirmed security vulnerabilities in less than 48 hours, followed by a software release containing the fixes within the following days.
If you report security vulnerabilities, do not forget to **tell us if and how you want to be acknowledged** and if you already requested CVE(s). Otherwise, we will request the CVE(s) directly.
As one of the critical user-bases of MISP consists of the CSIRT community, it is our duty to clearly state which bug could be abused and have a security impact on a MISP instance. CVE assignment is performed even for minor bugs suspected of having a security impact. This allows every user with MISP instances set up in their environments to understand which bugs could impact their security.
We firmly believe that, even though unfortunately it is often not regarded as common practice in our industry, being as transparent as possible about vulnerabilities, no matter how minor, is of crucial importance. At MISP Project, we care about the security of our users and prefer to have a high number of published CVEs rather than sweeping some of them under the rug.

View File

@ -1 +1 @@
{"major":2, "minor":4, "hotfix":123}
{"major":2, "minor":4, "hotfix":125}

View File

@ -291,7 +291,7 @@ class AdminShell extends AppShell
$param = empty($this->args[0]) ? 'all' : $this->args[0];
$settings = $this->Server->serverSettingsRead();
$result = $settings;
if (!empty($param)) {
if ($param != 'all') {
$result = 'No valid setting found for ' . $param;
foreach ($settings as $setting) {
if ($setting['setting'] == $param) {

View File

@ -5,6 +5,7 @@ require_once 'AppShell.php';
class ServerShell extends AppShell
{
public $uses = array('Server', 'Task', 'Job', 'User', 'Feed');
public $tasks = array('ConfigLoad');
public function listServers()
{

View File

@ -44,10 +44,10 @@ class AppController extends Controller
public $debugMode = false;
public $helpers = array('Utility', 'OrgImg', 'FontAwesome', 'UserName');
public $helpers = array('Utility', 'OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector');
private $__queryVersion = '101';
public $pyMispVersion = '2.4.123';
private $__queryVersion = '105';
public $pyMispVersion = '2.4.125';
public $phpmin = '7.2';
public $phprec = '7.4';
public $pythonmin = '3.6';
@ -57,6 +57,8 @@ class AppController extends Controller
public $baseurl = '';
public $sql_dump = false;
private $isRest = null;
// Used for _isAutomation(), a check that returns true if the controller & action combo matches an action that is a non-xml and non-json automation method
// This is used to allow authentication via headers for methods not covered by _isRest() - as that only checks for JSON and XML formats
public $automationArray = array(
@ -120,7 +122,6 @@ class AppController extends Controller
} else {
$this->Auth->logoutRedirect = Configure::read('MISP.baseurl') . '/users/login';
}
$this->__sessionMassage();
if (Configure::read('Security.allow_cors')) {
// Add CORS headers
@ -183,6 +184,8 @@ class AppController extends Controller
if (!empty($this->params['named']['disable_background_processing'])) {
Configure::write('MISP.background_jobs', 0);
}
Configure::write('CurrentController', $this->params['controller']);
Configure::write('CurrentAction', $this->params['action']);
$versionArray = $this->{$this->modelClass}->checkMISPVersion();
$this->mispVersion = implode('.', array_values($versionArray));
$this->Security->blackHoleCallback = 'blackHole';
@ -203,7 +206,14 @@ class AppController extends Controller
$this->Security->unlockedActions = array($this->action);
}
if (!$userLoggedIn) {
if (
!$userLoggedIn &&
(
$this->params['controller'] !== 'users' ||
$this->params['action'] !== 'register' ||
empty(Configure::read('Security.allow_self_registration'))
)
) {
// REST authentication
if ($this->_isRest() || $this->_isAutomation()) {
// disable CSRF for REST access
@ -299,6 +309,7 @@ class AppController extends Controller
}
if ($this->Auth->user()) {
Configure::write('CurrentUserId', $this->Auth->user('id'));
$this->User->setMonitoring($this->Auth->user());
if (Configure::read('MISP.log_user_ips')) {
$redis = $this->{$this->modelClass}->setupRedis();
@ -352,7 +363,11 @@ class AppController extends Controller
}
}
} else {
if (!($this->params['controller'] === 'users' && $this->params['action'] === 'login')) {
$pre_auth_actions = array('login', 'register');
if (!empty(Configure::read('Security.email_otp_enabled'))) {
$pre_auth_actions[] = 'email_otp';
}
if ($this->params['controller'] !== 'users' || !in_array($this->params['action'], $pre_auth_actions)) {
if (!$this->request->is('ajax')) {
$this->Session->write('pre_login_requested_url', $this->here);
}
@ -607,7 +622,7 @@ class AppController extends Controller
ConnectionManager::create('default', $db->config);
}
$dataSource = $dataSourceConfig['datasource'];
if ($dataSource != 'Database/Mysql' && $dataSource != 'Database/Postgres') {
if (!in_array($dataSource, array('Database/Mysql', 'Database/Postgres', 'Database/MysqlObserver'))) {
throw new Exception('datasource not supported: ' . $dataSource);
}
}
@ -671,6 +686,11 @@ class AppController extends Controller
protected function _isRest()
{
// This method is surprisingly slow and called many times for one request, so it make sense to cache the result.
if ($this->isRest !== null) {
return $this->isRest;
}
$api = $this->__isApiFunction($this->request->params['controller'], $this->request->params['action']);
if (isset($this->RequestHandler) && ($api || $this->RequestHandler->isXml() || $this->_isJson() || $this->_isCsv())) {
if ($this->_isJson()) {
@ -678,8 +698,10 @@ class AppController extends Controller
throw new MethodNotAllowedException('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.');
}
}
$this->isRest = true;
return true;
} else {
$this->isRest = false;
return false;
}
}

View File

@ -3,6 +3,9 @@ App::uses('AppController', 'Controller');
App::uses('Folder', 'Utility');
App::uses('File', 'Utility');
/**
* @property Attribute $Attribute
*/
class AttributesController extends AppController
{
public $components = array('Security', 'RequestHandler', 'Cidr');
@ -148,6 +151,12 @@ class AttributesController extends AppController
if (!isset($this->request->data['Attribute'])) {
$this->request->data = array('Attribute' => $this->request->data);
}
if ($this->request->data['Attribute']['distribution'] == 4) {
$sg = $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1, $this->request->data['Attribute']['sharing_group_id']);
if (empty($sg)) {
throw new MethodNotAllowedException(__('Invalid Sharing Group or not authorised.'));
}
}
//
// multiple attributes in batch import
//
@ -410,22 +419,19 @@ class AttributesController extends AppController
public function add_attachment($eventId = null)
{
if ($this->request->is('post')) {
$hashes = array('md5' => 'malware-sample', 'sha1' => 'filename|sha1', 'sha256' => 'filename|sha256');
$this->loadModel('Event');
$this->Event->id = $this->request->data['Attribute']['event_id'];
$this->Event->recursive = -1;
$event = $this->Event->read();
$this->Attribute->Event->id = $this->request->data['Attribute']['event_id'];
$this->Attribute->Event->recursive = -1;
$event = $this->Attribute->Event->read();
if (empty($event)) {
throw new NotFoundException(__('Invalid Event.'));
}
if (!$this->_isSiteAdmin() && ($this->Event->data['Event']['orgc_id'] != $this->_checkOrg() || !$this->userRole['perm_modify'])) {
if (!$this->_isSiteAdmin() && ($this->Attribute->Event->data['Event']['orgc_id'] != $this->_checkOrg() || !$this->userRole['perm_modify'])) {
throw new UnauthorizedException(__('You do not have permission to do that.'));
}
$partialFails = array();
$fails = array();
$success = 0;
foreach ($this->request->data['Attribute']['values'] as $k => $value) {
foreach ($this->request->data['Attribute']['values'] as $value) {
// Check if there were problems with the file upload
// only keep the last part of the filename, this should prevent directory attacks
$filename = basename($value['name']);
@ -449,11 +455,6 @@ class AttributesController extends AppController
$filename,
$tmpfile
);
if ($result) {
$success++;
} else {
$fails[] = $filename;
}
} else {
$result = $this->Attribute->simpleAddMalwareSample(
$eventId,
@ -461,15 +462,16 @@ class AttributesController extends AppController
$filename,
$tmpfile
);
if ($result) {
$success++;
} else {
$fails[] = $filename;
}
}
if ($result) {
$success++;
} else {
$fails[] = $filename;
}
if (!empty($result)) {
foreach ($result['Object'] as $object) {
$this->loadModel('MispObject');
$object['distribution'] = $this->request->data['Attribute']['distribution'];
if (!empty($this->request->data['sharing_group_id'])) {
$object['sharing_group_id'] = $this->request->data['Attribute']['sharing_group_id'];
@ -477,11 +479,11 @@ class AttributesController extends AppController
foreach ($object['Attribute'] as $ka => $attribute) {
$object['Attribute'][$ka]['distribution'] = 5;
}
$this->MispObject->captureObject(array('Object' => $object), $eventId, $this->Auth->user());
$this->Attribute->Object->captureObject(array('Object' => $object), $eventId, $this->Auth->user());
}
if (!empty($result['ObjectReference'])) {
foreach ($result['ObjectReference'] as $reference) {
$this->MispObject->ObjectReference->smartSave($reference, $eventId);
$this->Attribute->Object->ObjectReference->smartSave($reference, $eventId);
}
}
}
@ -508,20 +510,17 @@ class AttributesController extends AppController
}
}
}
$message = 'The attachment(s) have been uploaded.';
if (!empty($partialFails)) {
$message .= ' Some of the attributes however could not be created.';
}
$message = __('The attachment(s) have been uploaded.');
if (!empty($fails)) {
$message = 'Some of the attachments failed to upload. The failed files were: ' . implode(', ', $fails) . ' - This can be caused by the attachments already existing in the event.';
$message = __('Some of the attachments failed to upload. The failed files were: %s - This can be caused by the attachments already existing in the event.', implode(', ', $fails));
}
if (empty($success)) {
if (empty($fails)) {
$message = 'The attachment(s) could not be saved. please contact your administrator.';
$message = __('The attachment(s) could not be saved. Please contact your administrator.');
}
} else {
$this->Event->id = $this->request->data['Attribute']['event_id'];
$this->Event->saveField('published', 0);
$this->Attribute->Event->id = $this->request->data['Attribute']['event_id'];
$this->Attribute->Event->saveField('published', 0);
}
if (empty($success) && !empty($fails)) {
$this->Flash->error($message);
@ -536,57 +535,43 @@ class AttributesController extends AppController
// set the event_id in the form
$this->request->data['Attribute']['event_id'] = $eventId;
}
$event = $this->Attribute->Event->findById($eventId);
if (empty($event)) {
throw new NotFoundException(__('Invalid Event.'));
}
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $eventId);
}
// combobox for categories
$categories = array_keys($this->Attribute->categoryDefinitions);
// just get them with attachments..
// Filter categories that contains attachment type
$selectedCategories = array();
foreach ($categories as $category) {
$types = $this->Attribute->categoryDefinitions[$category]['types'];
$alreadySet = false;
foreach ($types as $type) {
if ($this->Attribute->typeIsAttachment($type) && !$alreadySet) {
// add to the whole..
foreach ($this->Attribute->categoryDefinitions as $category => $values) {
foreach ($values['types'] as $type) {
if ($this->Attribute->typeIsAttachment($type)) {
$selectedCategories[] = $category;
$alreadySet = true;
continue;
continue 2;
}
}
}
$categories = $this->_arrayToValuesIndexArray($selectedCategories);
$this->set('categories', $categories);
$this->set('attrDescriptions', $this->Attribute->fieldDescriptions);
$this->set('typeDefinitions', $this->Attribute->typeDefinitions);
$this->set('categoryDefinitions', $this->Attribute->categoryDefinitions);
$this->set('zippedDefinitions', $this->Attribute->zippedDefinitions);
$this->set('uploadDefinitions', $this->Attribute->uploadDefinitions);
$this->set('advancedExtractionAvailable', $this->Attribute->isAdvancedExtractionAvailable());
// combobox for distribution
$this->loadModel('Event');
$this->set('distributionLevels', $this->Event->Attribute->distributionLevels);
foreach ($this->Attribute->categoryDefinitions as $key => $value) {
$info['category'][$key] = array('key' => $key, 'desc' => isset($value['formdesc'])? $value['formdesc'] : $value['desc']);
}
foreach ($this->Event->Attribute->distributionLevels as $key => $value) {
$info['distribution'][$key] = array('key' => $value, 'desc' => $this->Attribute->distributionDescriptions[$key]['formdesc']);
}
$this->set('info', $info);
$this->set('distributionLevels', $this->Attribute->distributionLevels);
$this->set('info', $this->__getInfo());
$this->loadModel('SharingGroup');
$sgs = $this->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1);
$this->set('sharingGroups', $sgs);
$events = $this->Event->findById($eventId);
if (empty($events)) {
throw new NotFoundException(__('Invalid Event.'));
}
$this->set('currentDist', $events['Event']['distribution']);
$this->set('published', $events['Event']['published']);
$this->set('currentDist', $event['Event']['distribution']);
$this->set('published', $event['Event']['published']);
}
@ -852,6 +837,12 @@ class AttributesController extends AppController
if (!isset($this->request->data['Attribute'])) {
$this->request->data = array('Attribute' => $this->request->data);
}
if ($this->request->data['Attribute']['distribution'] == 4) {
$sg = $this->Attribute->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1, $this->request->data['Attribute']['sharing_group_id']);
if (empty($sg)) {
throw new MethodNotAllowedException(__('Invalid Sharing Group or not authorised.'));
}
}
$existingAttribute = $this->Attribute->findByUuid($this->Attribute->data['Attribute']['uuid']);
// check if the attribute has a timestamp already set (from a previous instance that is trying to edit via synchronisation)
// check which attribute is newer
@ -1411,7 +1402,7 @@ class AttributesController extends AppController
$event = $this->Attribute->Event->find('first', array(
'conditions' => array('id' => $id),
'recursive' => -1,
'fields' => array('id', 'orgc_id', 'user_id', 'published', 'timestamp', 'info', 'uuid')
'fields' => array('id', 'orgc_id', 'org_id', 'user_id', 'published', 'timestamp', 'info', 'uuid')
));
if (!$this->_isSiteAdmin()) {
if ($event['Event']['orgc_id'] != $this->Auth->user('org_id') || (!$this->userRole['perm_modify_org'] && !($this->userRole['perm_modify'] && $event['user_id'] == $this->Auth->user('id')))) {
@ -1477,7 +1468,23 @@ class AttributesController extends AppController
}
if ($changeInAttribute) {
if ($this->Attribute->saveMany($attributes)) {
if ($this->request->data['Attribute']['is_proposal']) { // create ShadowAttributes instead
$shadowAttributes = array();
foreach ($attributes as $attribute) {
$shadowAttribute['ShadowAttribute'] = $attribute['Attribute'];
unset($shadowAttribute['ShadowAttribute']['id']);
$shadowAttribute['ShadowAttribute']['email'] = $this->Auth->user('email');
$shadowAttribute['ShadowAttribute']['org_id'] = $this->Auth->user('org_id');
$shadowAttribute['ShadowAttribute']['event_uuid'] = $event['Event']['uuid'];
$shadowAttribute['ShadowAttribute']['event_org_id'] = $event['Event']['org_id'];
$shadowAttribute['ShadowAttribute']['old_id'] = $attribute['Attribute']['id'];
$shadowAttributes[] = $shadowAttribute;
}
$saveSuccess = $this->Attribute->ShadowAttribute->saveMany($shadowAttributes);
} else {
$saveSuccess = $this->Attribute->saveMany($attributes);
}
if ($saveSuccess) {
if (!$this->_isRest()) {
$this->Attribute->Event->insertLock($this->Auth->user(), $id);
}
@ -1662,6 +1669,31 @@ class AttributesController extends AppController
);
$exception = false;
$filters = $this->_harvestParameters($filterData, $exception);
if (!empty($filters['uuid'])) {
if (!is_array($filters['uuid'])) {
$filters['uuid'] = array($filters['uuid']);
}
$uuid = array();
$ids = array();
foreach ($filters['uuid'] as $k => $filter) {
if ($filter[0] === '!') {
$filter = substr($filter, 1);
}
if (Validation::uuid($filter)) {
$uuid[] = $filters['uuid'][$k];
} else {
$ids[] = $filters['uuid'][$k];
}
}
if (empty($uuid)) {
unset($filters['uuid']);
} else {
$filters['uuid'] = $uuid;
}
if (!empty($ids)) {
$filters['eventid'] = $ids;
}
}
unset($filterData);
if ($filters === false) {
return $exception;
@ -3209,4 +3241,28 @@ class AttributesController extends AppController
return $this->RestResponse->viewData($final, $responseType, false, true, 'search.' . $type . '.' . $responseType);
}
}
private function __getInfo()
{
$info = array('category' => array(), 'type' => array(), 'distribution' => array());
foreach ($this->Attribute->categoryDefinitions as $key => $value) {
$info['category'][$key] = array(
'key' => $key,
'desc' => isset($value['formdesc']) ? $value['formdesc'] : $value['desc']
);
}
foreach ($this->Attribute->typeDefinitions as $key => $value) {
$info['type'][$key] = array(
'key' => $key,
'desc' => isset($value['formdesc']) ? $value['formdesc'] : $value['desc']
);
}
foreach ($this->Attribute->distributionLevels as $key => $value) {
$info['distribution'][$key] = array(
'key' => $value,
'desc' => $this->Attribute->distributionDescriptions[$key]['formdesc']
);
}
return $info;
}
}

View File

@ -217,6 +217,7 @@ class ACLComponent extends Component
'getEvent' => array(),
'importFeeds' => array(),
'index' => array('*'),
'loadDefaultFeeds' => array('perm_site_admin'),
'previewEvent' => array('*'),
'previewIndex' => array('*'),
'searchCaches' => array('*'),
@ -549,6 +550,7 @@ class ACLComponent extends Component
'viewEvent' => array('*'),
),
'users' => array(
'acceptRegistrations' => array('perm_site_admin'),
'admin_add' => array('perm_admin'),
'admin_delete' => array('perm_admin'),
'admin_edit' => array('perm_admin'),
@ -564,14 +566,18 @@ class ACLComponent extends Component
'checkIfLoggedIn' => array('*'),
'dashboard' => array('*'),
'delete' => array('perm_admin'),
'discardRegistrations' => array('perm_site_admin'),
'downloadTerms' => array('*'),
'edit' => array('*'),
'email_otp' => array('*'),
'searchGpgKey' => array('*'),
'fetchGpgKey' => array('*'),
'histogram' => array('*'),
'initiatePasswordReset' => array('perm_admin'),
'login' => array('*'),
'logout' => array('*'),
'register' => array('*'),
'registrations' => array('perm_site_admin'),
'resetAllSyncAuthKeys' => array(),
'resetauthkey' => array('*'),
'request_API' => array('*'),

View File

@ -449,7 +449,7 @@ class RestResponseComponent extends Component
}
if (Configure::read('debug') > 1 && !empty($this->Controller->sql_dump)) {
$this->Log = ClassRegistry::init('Log');
if ($this->Content->sql_dump === 2) {
if ($this->Controller->sql_dump === 2) {
$response = array('sql_dump' => $this->Log->getDataSource()->getLog(false, false));
} else {
$response['sql_dump'] = $this->Log->getDataSource()->getLog(false, false);

View File

@ -15,7 +15,7 @@ class RestSearchComponent extends Component
'Event' => array(
'returnFormat', 'value', 'type', 'category', 'org', 'tags', 'searchall', 'from', 'to', 'last', 'eventid', 'withAttachments',
'metadata', 'uuid', 'publish_timestamp', 'timestamp', 'published', 'enforceWarninglist', 'sgReferenceOnly',
'limit', 'page', 'requested_attributes', 'includeContext', 'headerless', 'includeWarninglistHits', 'attackGalaxy', 'deleted',
'limit', 'page', 'requested_attributes', 'includeContext', 'headerless', 'includeWarninglistHits', 'attackGalaxy', 'to_ids', 'deleted',
'excludeLocalTags', 'date', 'includeSightingdb', 'tag', 'object_relation'
),
'Object' => array(

View File

@ -315,11 +315,11 @@ class EventsController extends AppController
break;
case 'attribute':
$event_id_arrays = $this->__filterOnAttributeValue($v);
foreach ($event_id_arrays[0] as $event_id) {
$this->paginate['conditions']['AND']['OR'][] = array('Event.id' => $event_id);
if (!empty($event_id_arrays[0])) {
$this->paginate['conditions']['AND'][] = array('Event.id' => $event_id_arrays[0]);
}
foreach ($event_id_arrays[1] as $event_id) {
$this->paginate['conditions']['AND'][] = array('Event.id !=' => $event_id);
if (!empty($event_id_arrays[1])) {
$this->paginate['conditions']['AND'][] = array('Event.id !=' => $event_id_arrays[1]);
}
break;
case 'published':
@ -342,25 +342,38 @@ class EventsController extends AppController
if ($v == "") {
continue 2;
}
$pieces = explode('|', $v);
if (is_array($v)) {
$pieces = $v;
} else {
$pieces = explode('|', $v);
}
$temp = array();
$eventidConditions = array();
foreach ($pieces as $piece) {
$piece = trim($piece);
if ($piece[0] == '!') {
if (strlen($piece) == 37) {
$this->paginate['conditions']['AND'][] = array('Event.uuid !=' => substr($piece, 1));
$eventidConditions['NOT']['uuid'][] = substr($piece, 1);
} else {
$this->paginate['conditions']['AND'][] = array('Event.id !=' => substr($piece, 1));
$eventidConditions['NOT']['id'][] = substr($piece, 1);
}
} else {
if (strlen($piece) == 36) {
$temp['OR'][] = array('Event.uuid' => $piece);
$eventidConditions['OR']['uuid'][] = $piece;
} else {
$temp['OR'][] = array('Event.id' => $piece);
$eventidConditions['OR']['id'][] = $piece;
}
}
}
$this->paginate['conditions']['AND'][] = $temp;
foreach ($eventidConditions as $operator => $conditionForOperator) {
foreach ($conditionForOperator as $conditionKey => $conditionValue) {
$lookupKey = 'Event.' . $conditionKey;
if ($operator === 'NOT') {
$lookupKey = $lookupKey . ' !=';
}
$this->paginate['conditions']['AND'][] = array($lookupKey => $conditionValue);
}
}
break;
case 'datefrom':
if ($v == "") {
@ -415,7 +428,11 @@ class EventsController extends AppController
$orgUuidArray = $this->Event->Org->find('list', array('fields' => array('Org.uuid')));
$orgArray = array_map('strtoupper', $orgArray);
// if the first character is '!', search for NOT LIKE the rest of the string (excluding the '!' itself of course)
$pieces = explode('|', $v);
if (!is_array($v)) {
$pieces = explode('|', $v);
} else {
$pieces = $v;
}
$test = array();
foreach ($pieces as $piece) {
if ($piece[0] == '!') {
@ -723,8 +740,6 @@ class EventsController extends AppController
} else {
$rules['order'] = array('Event.' . $passedArgs['sort'] => 'ASC');
}
} else {
$rules['order'] = array('Event.id' => 'DESC');
}
$rules['contain'] = $this->paginate['contain'];
if (isset($this->paginate['conditions'])) {
@ -1388,12 +1403,8 @@ class EventsController extends AppController
$this->set($alias, $currentModel->{$variable});
}
}
$cluster_names = $this->GalaxyCluster->find('list', array('fields' => array('GalaxyCluster.tag_name'), 'group' => array('GalaxyCluster.tag_name', 'GalaxyCluster.id')));
foreach ($event['EventTag'] as $k => $eventTag) {
if (in_array($eventTag['Tag']['name'], $cluster_names)) {
unset($event['EventTag'][$k]);
}
}
$this->Event->removeGalaxyClusterTags($event);
$tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($event['EventTag']);
foreach ($tagConflicts['global'] as $tagConflict) {
@ -1415,11 +1426,9 @@ class EventsController extends AppController
}
$modDate = date("Y-m-d", $attribute['timestamp']);
$modificationMap[$modDate] = empty($modificationMap[$modDate])? 1 : $modificationMap[date("Y-m-d", $attribute['timestamp'])] + 1;
foreach ($attribute['AttributeTag'] as $k2 => $attributeTag) {
if (in_array($attributeTag['Tag']['name'], $cluster_names)) {
unset($event['Attribute'][$k]['AttributeTag'][$k2]);
}
}
$this->Event->Attribute->removeGalaxyClusterTags($event['Attribute'][$k]);
$tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($attribute['AttributeTag']);
foreach ($tagConflicts['global'] as $tagConflict) {
$warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy'];
@ -1448,11 +1457,9 @@ class EventsController extends AppController
}
$modDate = date("Y-m-d", $attribute['timestamp']);
$modificationMap[$modDate] = empty($modificationMap[$modDate])? 1 : $modificationMap[date("Y-m-d", $attribute['timestamp'])] + 1;
foreach ($attribute['AttributeTag'] as $k3 => $attributeTag) {
if (in_array($attributeTag['Tag']['name'], $cluster_names)) {
unset($event['Object'][$k]['Attribute'][$k2]['AttributeTag'][$k3]);
}
}
$this->Event->Attribute->removeGalaxyClusterTags($event['Object'][$k]['Attribute'][$k2]);
$tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($attribute['AttributeTag']);
foreach ($tagConflicts['global'] as $tagConflict) {
$warningTagConflicts[$tagConflict['taxonomy']['Taxonomy']['namespace']] = $tagConflict['taxonomy'];
@ -1644,7 +1651,7 @@ class EventsController extends AppController
if (!empty($this->params['named']['excludeGalaxy'])) {
$conditions['excludeGalaxy'] = 1;
}
if (!empty($this->params['named']['extended'])) {
if (!empty($this->params['named']['extended']) || !empty($this->request->data['extended'])) {
$conditions['extended'] = 1;
$this->set('extended', 1);
} else {
@ -2265,18 +2272,34 @@ class EventsController extends AppController
}
}
foreach ($resultArray as $key => $result) {
if ($has_pipe = strpos($result['default_type'], '|') !== false || $result['default_type'] === 'malware-sample') {
$pieces = explode('|', $result['value']);
$or = array('Attribute.value1' => $pieces,
'Attribute.value2' => $pieces);
} else {
$or = array('Attribute.value1' => $result['value'], 'Attribute.value2' => $result['value']);
}
$options = array(
'conditions' => array('OR' => array('Attribute.value1' => $result['value'], 'Attribute.value2' => $result['value'])),
'fields' => array('Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.comment'),
'order' => false
'conditions' => array('OR' => $or),
'fields' => array('Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.comment'),
'order' => false
);
$resultArray[$key]['related'] = $this->Event->Attribute->fetchAttributes($this->Auth->user(), $options);
}
// combobox for distribution
$distributions = $this->Event->Attribute->distributionLevels;
$sgs = $this->Event->SharingGroup->fetchAllAuthorised($this->Auth->user(), 'name', 1);
if (empty($sgs)) {
unset($distributions[4]);
}
$this->set('event', array('Event' => array('id' => $target_id)));
$this->set('resultArray', $resultArray);
$this->set('typeList', array_keys($this->Event->Attribute->typeDefinitions));
$this->set('defaultCategories', $this->Event->Attribute->defaultCategories);
$this->set('typeCategoryMapping', $typeCategoryMapping);
$this->set('distributions', $distributions);
$this->set('sgs', $sgs);
$this->set('title', 'Merge Results');
$this->set('importComment', 'Merged from event ' . $source_id);
$this->render('resolved_attributes');
@ -3083,7 +3106,9 @@ class EventsController extends AppController
'named_params' => $this->params['named'],
'ordered_url_params' => func_get_args(),
'injectedParams' => array(
'returnFormat' => 'csv'
'returnFormat' => 'csv',
'to_ids' => '1',
'published' => '1'
)
));
return $this->restSearch();
@ -3698,8 +3723,15 @@ class EventsController extends AppController
}
}
foreach ($resultArray as $key => $result) {
if ($has_pipe = strpos($result['default_type'], '|') !== false || $result['default_type'] === 'malware-sample') {
$pieces = explode('|', $result['value']);
$or = array('Attribute.value1' => $pieces,
'Attribute.value2' => $pieces);
} else {
$or = array('Attribute.value1' => $result['value'], 'Attribute.value2' => $result['value']);
}
$options = array(
'conditions' => array('OR' => array('Attribute.value1' => $result['value'], 'Attribute.value2' => $result['value'])),
'conditions' => array('OR' => $or),
'fields' => array('Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.comment'),
'order' => false,
'flatten' => 1
@ -3802,7 +3834,7 @@ class EventsController extends AppController
$this->Event->insertLock($this->Auth->user(), $id);
$attributes = json_decode($this->request->data['Attribute']['JsonObject'], true);
$default_comment = $this->request->data['Attribute']['default_comment'];
$force = $this->request->data['Attribute']['force'];
$force = $this->_isSiteAdmin() && $this->request->data['Attribute']['force'];
$flashMessage = $this->Event->processFreeTextDataRouter($this->Auth->user(), $attributes, $id, $default_comment, $force);
$this->Flash->info($flashMessage);
$this->redirect(array('controller' => 'events', 'action' => 'view', $id));
@ -3991,26 +4023,26 @@ class EventsController extends AppController
'checkbox_default' => true
),
'openIOC' => array(
'url' => '/events/downloadOpenIOCEvent/download/' . $id,
'url' => '/events/restSearch/openioc/to_ids:1/published:1/eventid:' . $id . '.json',
'text' => 'OpenIOC (all indicators marked to IDS)',
'requiresPublished' => false,
'checkbox' => false,
),
'csv' => array(
'url' => '/events/csv/download/' . $id,
'url' => '/events/restSearch/returnFormat:csv/to_ids:1/published:1/includeContext:0/eventid:' . $id,
'text' => 'CSV',
'requiresPublished' => false,
'checkbox' => true,
'checkbox_text' => 'Include non-IDS marked attributes',
'checkbox_set' => '/events/csv/download/' . $id . '/1'
'checkbox_set' => '/events/restSearch/returnFormat:csv/to_ids:1||0/published:1||0/includeContext:0/eventid:' . $id
),
'csv_with_context' => array(
'url' => '/events/restSearch/returnFormat:csv/eventid:' . $id,
'url' => '/events/restSearch/returnFormat:csv/to_ids:1/published:1/includeContext:1/eventid:' . $id,
'text' => 'CSV with additional context',
'requiresPublished' => false,
'checkbox' => true,
'checkbox_text' => 'Include non-IDS marked attributes',
'checkbox_set' => '/events/restSearch/returnFormat:csv/to_ids:1||0/published:1||0/eventid:' . $id
'checkbox_set' => '/events/restSearch/returnFormat:csv/to_ids:1||0/published:1||0/includeContext:1/eventid:' . $id
),
'stix_xml' => array(
'url' => '/events/restSearch/stix/eventid:' . $id,
@ -4056,6 +4088,7 @@ class EventsController extends AppController
),
'bro' => array(
'url' => '/attributes/bro/download/all/false/' . $id,
// 'url' => '/attributes/restSearch/returnFormat:bro/published:1||0/eventid:' . $id,
'text' => 'Download Bro rules',
'requiresPublished' => false,
'checkbox' => false
@ -4076,7 +4109,7 @@ class EventsController extends AppController
}
}
$exports['csv'] = array(
'url' => '/events/csv/download/' . $id . '/1',
'url' => '/events/restSearch/returnFormat:csv/includeContext:0/eventid:' . $id,
'text' => 'CSV (event not published, IDS flag ignored)',
'requiresPublished' => false,
'checkbox' => false
@ -4496,16 +4529,20 @@ class EventsController extends AppController
if (!in_array($type, $validTools)) {
throw new MethodNotAllowedException('Invalid type.');
}
App::uses('EventTimelineTool', 'Tools');
$grapher = new EventTimelineTool();
$data = $this->request->is('post') ? $this->request->data : array();
$dataFiltering = array_key_exists('filtering', $data) ? $data['filtering'] : array();
$scope = isset($data['scope']) ? $data['scope'] : 'seen';
$extended = isset($this->params['named']['extended']) ? 1 : 0;
$grapher->construct($this->Event, $this->Auth->user(), $dataFiltering, $extended);
$json = $grapher->get_timeline($id);
if ($scope == 'seen') {
$json = $grapher->get_timeline($id);
} elseif ($scope == 'sightings') {
$json = $grapher->get_sighting_timeline($id);
}
array_walk_recursive($json, function (&$item, $key) {
if (!mb_detect_encoding($item, 'utf-8', true)) {
@ -4965,10 +5002,17 @@ class EventsController extends AppController
}
}
foreach ($resultArray as $key => $result) {
if ($has_pipe = strpos($result['default_type'], '|') !== false || $result['default_type'] === 'malware-sample') {
$pieces = explode('|', $result['value']);
$or = array('Attribute.value1' => $pieces,
'Attribute.value2' => $pieces);
} else {
$or = array('Attribute.value1' => $result['value'], 'Attribute.value2' => $result['value']);
}
$options = array(
'conditions' => array('OR' => array('Attribute.value1' => $result['value'], 'Attribute.value2' => $result['value'])),
'fields' => array('Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.comment'),
'order' => false
'conditions' => array('OR' => $or),
'fields' => array('Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.comment'),
'order' => false
);
$resultArray[$key]['related'] = $this->Event->Attribute->fetchAttributes($this->Auth->user(), $options);
if (isset($result['data'])) {
@ -5148,10 +5192,17 @@ class EventsController extends AppController
}
}
foreach ($resultArray as $key => $result) {
if ($has_pipe = strpos($result['default_type'], '|') !== false || $result['default_type'] === 'malware-sample') {
$pieces = explode('|', $result['value']);
$or = array('Attribute.value1' => $pieces,
'Attribute.value2' => $pieces);
} else {
$or = array('Attribute.value1' => $result['value'], 'Attribute.value2' => $result['value']);
}
$options = array(
'conditions' => array('OR' => array('Attribute.value1' => $result['value'], 'Attribute.value2' => $result['value'])),
'fields' => array('Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.comment'),
'order' => false
'conditions' => array('OR' => $or),
'fields' => array('Attribute.type', 'Attribute.category', 'Attribute.value', 'Attribute.comment'),
'order' => false
);
$resultArray[$key]['related'] = $this->Event->Attribute->fetchAttributes($this->Auth->user(), $options);
}

View File

@ -9,7 +9,18 @@ class FeedsController extends AppController
public $paginate = array(
'limit' => 60,
'recursive' => -1,
'contain' => array('Tag', 'SharingGroup'),
'contain' => array(
'Tag',
'SharingGroup',
'Orgc' => array(
'fields' => array(
'Orgc.id',
'Orgc.uuid',
'Orgc.name',
'Orgc.local'
)
)
),
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events
'order' => array(
'Feed.default' => 'DESC',
@ -29,12 +40,25 @@ class FeedsController extends AppController
}
}
public function loadDefaultFeeds()
{
if ($this->request->is('post')) {
$this->Feed->load_default_feeds();
$message = __('Default feed metadata loaded.');
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('Feed', 'loadDefaultFeeds', false, $this->response->type(), $message);
} else {
$this->Flash->success($message);
$this->redirect(array('controller' => 'Feeds', 'action' => 'index'));
}
}
}
public function index()
{
if (!$this->_isSiteAdmin() && !$this->Auth->user('org_id') == Configure::read('MISP.host_org_id')) {
throw NotAllowedException('You don\'t have access to this feature.');
}
$this->Feed->load_default_feeds();
$scope = isset($this->passedArgs['scope']) ? $this->passedArgs['scope'] : 'all';
if ($scope !== 'all') {
if ($scope == 'enabled') {
@ -50,6 +74,15 @@ class FeedsController extends AppController
);
}
}
$passedArgs = $this->passedArgs;
if (!empty($passedArgs['value'])) {
$lookup = strtolower($passedArgs['value']);
$allSearchFields = array('name', 'url', 'provider', 'source_format');
foreach ($allSearchFields as $field) {
$this->paginate['conditions']['AND']['OR'][] = array('LOWER(Feed.' . $field . ') LIKE' => '%' . $lookup . '%');
}
}
$this->set('passedArgs', json_encode($passedArgs));
if ($this->_isRest()) {
$keepFields = array('conditions', 'contain', 'recursive', 'sort');
$searchParams = array();
@ -169,9 +202,13 @@ 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->set('tags', $tags);
if (empty($this->request->data['Feed']['fixed_event'])) {
if (!isset($this->request->data['Feed']['fixed_event'])) {
$this->request->data['Feed']['fixed_event'] = 1;
}
$this->set('orgs', $this->Event->Orgc->find('list', array(
'fields' => array('id', 'name'),
'order' => 'LOWER(name)'
)));
if ($this->request->is('post')) {
if ($this->_isRest()) {
if (empty($this->request->data['Feed'])) {
@ -198,6 +235,11 @@ class FeedsController extends AppController
if (!isset($this->request->data['Feed']['source_format'])) {
$this->request->data['Feed']['source_format'] = 'freetext';
}
if (!empty($this->request->data['Feed']['source_format']) && ($this->request->data['Feed']['source_format'] == 'misp')) {
if (!empty($this->request->data['Feed']['orgc_id'])) {
$this->request->data['Feed']['orgc_id'] = 0;
}
}
if ($this->request->data['Feed']['source_format'] == 'freetext') {
if ($this->request->data['Feed']['fixed_event'] == 1) {
if (!empty($this->request->data['Feed']['target_event']) && is_numeric($this->request->data['Feed']['target_event'])) {
@ -246,7 +288,7 @@ class FeedsController extends AppController
$this->Flash->success($message);
$this->redirect(array('controller' => 'feeds', 'action' => 'index'));
} else {
$message = __('Feed could not be added. Invalid field: %s', array_keys($this->Feed->validationErrors)[0]);
$message = __('Feed could not be added. Reason: %s', json_encode($this->Feed->validationErrors));
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Feeds', 'add', false, $message, $this->response->type());
}
@ -287,6 +329,10 @@ class FeedsController extends AppController
$tags[0] = 'None';
$this->set('feed_types', $this->Feed->getFeedTypesOptions());
$this->set('tags', $tags);
$this->set('orgs', $this->Event->Orgc->find('list', array(
'fields' => array('id', 'name'),
'order' => 'LOWER(name)'
)));
if (!empty($this->Feed->data['Feed']['settings'])) {
$this->Feed->data['Feed']['settings'] = json_decode($this->Feed->data['Feed']['settings'], true);
}
@ -303,6 +349,11 @@ class FeedsController extends AppController
$this->request->data['Feed']['sharing_group_id'] = 0;
}
$this->request->data['Feed']['id'] = $feedId;
if (!empty($this->request->data['Feed']['source_format']) && ($this->request->data['Feed']['source_format'] == 'misp')) {
if (!empty($this->request->data['Feed']['orgc_id'])) {
$this->request->data['Feed']['orgc_id'] = 0;
}
}
if (!empty($this->request->data['Feed']['source_format']) && ($this->request->data['Feed']['source_format'] == 'freetext' || $this->request->data['Feed']['source_format'] == 'csv')) {
if ($this->request->data['Feed']['fixed_event'] == 1) {
if (isset($this->request->data['Feed']['target_event']) && is_numeric($this->request->data['Feed']['target_event'])) {
@ -324,7 +375,7 @@ class FeedsController extends AppController
$this->request->data['Feed']['settings']['delimiter'] = ',';
}
$this->request->data['Feed']['settings'] = json_encode($this->request->data['Feed']['settings']);
$fields = array('id', 'name', 'provider', 'enabled', 'caching_enabled','rules', 'url', 'distribution', 'sharing_group_id', 'tag_id', 'fixed_event', 'event_id', 'publish', 'delta_merge', 'source_format', 'override_ids', 'settings', 'input_source', 'delete_local_file', 'lookup_visible', 'headers');
$fields = array('id', 'name', 'provider', 'enabled', 'caching_enabled','rules', 'url', 'distribution', 'sharing_group_id', 'tag_id', 'fixed_event', 'event_id', 'publish', 'delta_merge', 'source_format', 'override_ids', 'settings', 'input_source', 'delete_local_file', 'lookup_visible', 'headers', 'orgc_id');
$feed = array();
foreach ($fields as $field) {
if (isset($this->request->data['Feed'][$field])) {
@ -345,7 +396,7 @@ class FeedsController extends AppController
$this->Flash->success($message);
$this->redirect(array('controller' => 'feeds', 'action' => 'index'));
} else {
$message = __('Feed could not be updated. Invalid fields: %s', implode(', ', array_keys($this->Feed->validationErrors)));
$message = __('Feed could not be updated. Reason: %s', json_encode($this->Feed->validationErrors));
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Feeds', 'add', false, $message, $this->response->type());
}

View File

@ -0,0 +1,17 @@
<?php
App::uses('AppController', 'Controller');
class InboxController extends AppController
{
public $components = array('Session', 'RequestHandler');
public function beforeFilter()
{
parent::beforeFilter();
}
public $paginate = array(
'limit' => 60,
'maxLimit' => 9999
);
}

View File

@ -349,21 +349,25 @@ class LogsController extends AppController
}
$this->set('list', $list);
// and store into session
$this->Session->write('paginate_conditions_log', $this->paginate);
$this->Session->write('paginate_conditions_log_email', $filters['email']);
$this->Session->write('paginate_conditions_log_org', $filters['org']);
$this->Session->write('paginate_conditions_log_action', $filters['action']);
$this->Session->write('paginate_conditions_log_model', $filters['model']);
$this->Session->write('paginate_conditions_log_model_id', $filters['model_id']);
$this->Session->write('paginate_conditions_log_title', $filters['title']);
$this->Session->write('paginate_conditions_log_change', $filters['change']);
if (Configure::read('MISP.log_client_ip')) {
$this->Session->write('paginate_conditions_log_ip', $filters['ip']);
}
if ($this->_isRest()) {
return $this->RestResponse->viewData($list, $this->response->type());
} else {
// and store into session
$this->Session->write('paginate_conditions_log', $this->paginate);
$this->Session->write('paginate_conditions_log_email', $filters['email']);
$this->Session->write('paginate_conditions_log_org', $filters['org']);
$this->Session->write('paginate_conditions_log_action', $filters['action']);
$this->Session->write('paginate_conditions_log_model', $filters['model']);
$this->Session->write('paginate_conditions_log_model_id', $filters['model_id']);
$this->Session->write('paginate_conditions_log_title', $filters['title']);
$this->Session->write('paginate_conditions_log_change', $filters['change']);
if (Configure::read('MISP.log_client_ip')) {
$this->Session->write('paginate_conditions_log_ip', $filters['ip']);
}
// set the same view as the index page
$this->render('admin_index');
// set the same view as the index page
$this->render('admin_index');
}
} else {
// get from Session
$filters['email'] = $this->Session->read('paginate_conditions_log_email');

View File

@ -398,7 +398,7 @@ class ObjectsController extends AppController
$this->set('updateable_attribute', $templateData['updateable_attribute']);
$this->set('not_updateable_attribute', $templateData['not_updateable_attribute']);
if (isset($this->params['named']['revised_object'])) {
$revisedData = $this->MispObject->reviseObject($this->params['named']['revised_object'], $object);
$revisedData = $this->MispObject->reviseObject($this->params['named']['revised_object'], $object, $template);
$this->set('revised_object', $revisedData['revised_object_both']);
$object = $revisedData['object'];
}

View File

@ -143,6 +143,13 @@ class OrganisationsController extends AppController
} else {
if ($this->_isRest()) {
return $this->RestResponse->describe('Organisations', 'admin_add', false, $this->response->type());
} else {
if (!empty($this->params['named']['name'])) {
$this->request->data['Organisation']['name'] = $this->params['named']['name'];
}
if (!empty($this->params['named']['uuid'])) {
$this->request->data['Organisation']['uuid'] = $this->params['named']['uuid'];
}
}
}
$this->set('countries', $this->_arrayToValuesIndexArray($this->Organisation->countries));

View File

@ -992,13 +992,15 @@ class ServersController extends AppController
if ($tab == 'diagnostics' || $tab == 'download' || $this->_isRest()) {
$php_ini = php_ini_loaded_file();
$this->set('php_ini', $php_ini);
$advanced_attachments = shell_exec($this->Server->getPythonVersion() . ' ' . APP . 'files/scripts/generate_file_objects.py -c');
$malwareTool = new MalwareTool();
try {
$advanced_attachments = json_decode($advanced_attachments, true);
$advanced_attachments = $malwareTool->checkAdvancedExtractionStatus($this->Server->getPythonVersion());
} catch (Exception $e) {
$this->log($e->getMessage(), LOG_NOTICE);
$advanced_attachments = false;
}
$this->set('advanced_attachments', $advanced_attachments);
// check if the current version of MISP is outdated or not
$version = $this->__checkVersion();

View File

@ -30,7 +30,14 @@ class UsersController extends AppController
parent::beforeFilter();
// what pages are allowed for non-logged-in users
$this->Auth->allow('login', 'logout');
$allowedActions = array('login', 'logout');
if(!empty(Configure::read('Security.email_otp_enabled'))) {
$allowedActions[] = 'email_otp';
}
if (!empty(Configure::read('Security.allow_self_registration'))) {
$allowedActions[] = 'register';
}
$this->Auth->allow($allowedActions);
}
public function view($id = null)
@ -438,7 +445,7 @@ class UsersController extends AppController
$users[$key]['User']['authkey'] = __('Redacted');
}
} else if (!empty(Configure::read('Security.user_monitoring_enabled'))) {
$users[$key]['User']['monitored'] = $redis->sismember('misp:monitored_users', $id);
$users[$key]['User']['monitored'] = $redis->sismember('misp:monitored_users', $value['User']['id']);
}
unset($users[$key]['User']['password']);
}
@ -634,13 +641,14 @@ class UsersController extends AppController
if (isset($this->request->data['User']['password'])) {
$this->request->data['User']['confirm_password'] = $this->request->data['User']['password'];
}
$default_publish_alert = Configure::check('MISP.default_publish_alert') ? Configure::read('MISP.default_publish_alert') : 0;
$defaults = array(
'external_auth_required' => 0,
'external_auth_key' => '',
'server_id' => 0,
'gpgkey' => '',
'certif_public' => '',
'autoalert' => 0,
'autoalert' => $default_publish_alert,
'contactalert' => 0,
'disabled' => 0,
'newsread' => 0,
@ -1111,33 +1119,15 @@ class UsersController extends AppController
$this->Auth->constructAuthenticate();
}
}
if ($this->request->is('post') && Configure::read('Security.email_otp_enabled')) {
$user = $this->Auth->identify($this->request, $this->response);
if ($user) {
$this->Session->write('email_otp_user', $user);
return $this->redirect('email_otp');
}
}
if ($this->Auth->login()) {
$this->User->extralog($this->Auth->user(), "login");
$this->User->Behaviors->disable('SysLogLogable.SysLogLogable');
$this->User->id = $this->Auth->user('id');
$user = $this->User->find('first', array(
'conditions' => array(
'User.id' => $this->Auth->user('id')
),
'recursive' => -1
));
$lastUserLogin = $user['User']['last_login'];
unset($user['User']['password']);
$user['User']['action'] = 'login';
$user['User']['last_login'] = $this->Auth->user('current_login');
$user['User']['current_login'] = time();
$this->User->save($user['User'], true, array('id', 'last_login', 'current_login'));
if (empty($this->Auth->authenticate['Form']['passwordHasher']) && !empty($passwordToSave)) {
$this->User->saveField('password', $passwordToSave);
}
$this->User->Behaviors->enable('SysLogLogable.SysLogLogable');
if ($lastUserLogin) {
$readableDatetime = (new DateTime())->setTimestamp($lastUserLogin)->format('D, d M y H:i:s O'); // RFC822
$this->Flash->info(sprintf('Welcome! Last login was on %s', $readableDatetime));
}
// no state changes are ever done via GET requests, so it is safe to return to the original page:
$this->redirect($this->Auth->redirectUrl());
// $this->redirect(array('controller' => 'events', 'action' => 'index'));
$this->_postlogin();
} else {
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
$dataSource = $dataSourceConfig['datasource'];
@ -1219,6 +1209,35 @@ class UsersController extends AppController
}
}
private function _postlogin()
{
$this->User->extralog($this->Auth->user(), "login");
$this->User->Behaviors->disable('SysLogLogable.SysLogLogable');
$this->User->id = $this->Auth->user('id');
$user = $this->User->find('first', array(
'conditions' => array(
'User.id' => $this->Auth->user('id')
),
'recursive' => -1
));
$lastUserLogin = $user['User']['last_login'];
unset($user['User']['password']);
$user['User']['action'] = 'login';
$user['User']['last_login'] = $this->Auth->user('current_login');
$user['User']['current_login'] = time();
$this->User->save($user['User'], true, array('id', 'last_login', 'current_login'));
if (empty($this->Auth->authenticate['Form']['passwordHasher']) && !empty($passwordToSave)) {
$this->User->saveField('password', $passwordToSave);
}
$this->User->Behaviors->enable('SysLogLogable.SysLogLogable');
if ($lastUserLogin) {
$readableDatetime = (new DateTime())->setTimestamp($lastUserLogin)->format('D, d M y H:i:s O'); // RFC822
$this->Flash->info(__('Welcome! Last login was on %s', $readableDatetime));
}
// no state changes are ever done via GET requests, so it is safe to return to the original page:
$this->redirect($this->Auth->redirectUrl());
}
public function routeafterlogin()
{
// Events list
@ -1468,7 +1487,7 @@ class UsersController extends AppController
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', 'admin_quickEmail', false, $error, $this->response->type());
} else {
$this->Flash->error('Cannot send an e-mail to this user as the account is disabled.');
$this->Flash->error($error);
$this->redirect('/admin/users/view/' . $user_id);
}
}
@ -1522,17 +1541,17 @@ class UsersController extends AppController
if ($isPostOrPut) {
$recipient = $this->request->data['User']['recipient'];
} else {
$recipient = isset($this->request->query['recipient']) ? $this->request->query['recipient'] : null;
$recipient = isset($this->params['named']['recipient']) ? $this->params['named']['recipient'] : null;
}
if ($isPostOrPut) {
$recipientEmailList = $this->request->data['User']['recipientEmailList'];
} else {
$recipientEmailList = isset($this->request->query['recipientEmailList']) ? $this->request->query['recipientEmailList'] : null;
$recipientEmailList = isset($this->params['named']['recipientEmailList']) ? $this->params['named']['recipientEmailList'] : null;
}
if ($isPostOrPut) {
$orgNameList = $this->request->data['User']['orgNameList'];
} else {
$orgNameList = isset($this->request->query['orgNameList']) ? $this->request->query['orgNameList'] : null;
$orgNameList = isset($this->params['named']['orgNameList']) ? $this->params['named']['orgNameList'] : null;
}
if (!is_null($recipient) && $recipient == 0) {
@ -1651,6 +1670,90 @@ class UsersController extends AppController
}
}
public function email_otp()
{
$user = $this->Session->read('email_otp_user');
if(empty($user)) {
$this->redirect('login');
}
$redis = $this->User->setupRedis();
$user_id = $user['id'];
if ($this->request->is('post') && isset($this->request->data['User']['otp'])) {
$stored_otp = $redis->get('misp:otp:'.$user_id);
if (!empty($stored_otp) && $this->request->data['User']['otp'] == $stored_otp) {
// we invalidate the previously generated OTP
$redis->delete('misp:otp:'.$user_id);
// We login the user with CakePHP
$this->Auth->login($user);
$this->_postlogin();
} else {
$this->Flash->error(__("The OTP is incorrect or has expired"));
}
} else {
// GET Request
// We check for exceptions
$exception_list = Configure::read('Security.email_otp_exceptions');
if (!empty($exception_list)) {
$exceptions = explode(",", $exception_list);
foreach ($exceptions as &$exception) {
if ($user['email'] == trim($exception)) {
// We login the user with CakePHP
$this->Auth->login($user);
$this->_postlogin();
}
}
}
$this->loadModel('Server');
// Generating the OTP
$digits = !empty(Configure::read('Security.email_otp_length')) ? Configure::read('Security.email_otp_length') : $this->Server->serverSettings['Security']['email_otp_length']['value'];
$otp = "";
for ($i=0; $i<$digits; $i++) {
$otp.= random_int(0,9);
}
// We use Redis to cache the OTP
$redis->set('misp:otp:'.$user_id, $otp);
$validity = !empty(Configure::read('Security.email_otp_validity')) ? Configure::read('Security.email_otp_validity') : $this->Server->serverSettings['Security']['email_otp_validity']['value'];
$redis->expire('misp:otp:'.$user_id, (int) $validity * 60);
// Email construction
$body = !empty(Configure::read('Security.email_otp_text')) ? Configure::read('Security.email_otp_text') : $this->Server->serverSettings['Security']['email_otp_text']['value'];
$body = str_replace('$misp', Configure::read('MISP.baseurl'), $body);
$body = str_replace('$org', Configure::read('MISP.org'), $body);
$body = str_replace('$contact', Configure::read('MISP.contact'), $body);
$body = str_replace('$validity', $validity, $body);
$body = str_replace('$otp', $otp, $body);
$body = str_replace('$ip', $this->__getClientIP(), $body);
$body = str_replace('$username', $user['email'], $body);
$result = $this->User->sendEmail(array('User' => $user), $body, false, "[MISP] Email OTP");
if ( $result ) {
$this->Flash->success(__("An email containing a OTP has been sent."));
} else {
$this->Flash->error(__("The email couldn't be sent, please reach out to your administrator."));
}
}
}
/**
* Helper function to determine the IP of a client (proxy aware)
*/
private function __getClientIP() {
$x_forwarded = filter_input(INPUT_SERVER, 'HTTP_X_FORWARDED_FOR', FILTER_SANITIZE_STRING);
$client_ip = filter_input(INPUT_SERVER, 'HTTP_CLIENT_IP', FILTER_SANITIZE_STRING);
if (!empty($x_forwarded)) {
$x_forwarded = explode(",", $x_forwarded);
return $x_forwarded[0];
} elseif(!empty($client_ip)){
return $_client_ip;
} else {
return filter_input(INPUT_SERVER, 'REMOTE_ADDR', FILTER_SANITIZE_STRING);
}
}
// shows some statistics about the instance
public function statistics($page = 'data')
{
@ -1737,6 +1840,7 @@ class UsersController extends AppController
$stats['user_count_pgp'] = $this->User->find('count', array('recursive' => -1, 'conditions' => array('User.gpgkey !=' => '')));
$stats['org_count'] = count($orgs);
$stats['local_org_count'] = count($local_orgs);
$stats['contributing_org_count'] = $this->User->Event->find('count', array('recursive' => -1, 'group' => array('Event.orgc_id')));
$stats['average_user_per_org'] = round($stats['user_count'] / $stats['local_org_count'], 1);
$this->loadModel('Thread');
@ -2240,4 +2344,297 @@ class UsersController extends AppController
}
}
}
public function register()
{
if (empty(Configure::read('Security.allow_self_registration'))) {
throw new MethodNotAllowedException(__('Self registration is not enabled on this instance.'));
}
if ($this->request->is('post')) {
if (isset($this->request->data['User'])) {
$this->request->data = $this->request->data['User'];
}
$validKeys = array(
'email',
'org_name',
'org_uuid',
'message',
'custom_perms',
'perm_sync',
'perm_publish',
'perm_admin'
);
$requestObject = array();
foreach ($validKeys as $key) {
if (isset($this->request->data[$key])) {
$requestObject[$key] = trim($this->request->data[$key]);
}
}
if (!isset($requestObject['message'])) {
$requestObject['message'] = '';
}
if (empty($requestObject['email'])) {
throw new InvalidArgumentException(__('We require at least the email field to be filled.'));
}
$this->loadModel('Inbox');
$this->Inbox->create();
$data = array(
'Inbox' => array(
'title' => __('User registration for %s.', $requestObject['email']),
'type' => 'registration',
'comment' => $requestObject['message'],
'data' => json_encode($requestObject)
)
);
$result = $this->Inbox->save($data);
if (empty($result)) {
$message = __('Request could not be created. Make sure that the email and org name fields are filled.');
if ($this->_isRest()) {
return $this->RestResponse->saveFailResponse('Users', 'register', false, $message, $this->response->type());
} else {
$this->Flash->error($message);
}
} else {
$message = __('Request sent. The administrators of this community have been notified.');
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('User', 'register', false, $this->response->type(), $message);
} else {
$this->Flash->success($message);
$this->redirect('/');
}
}
} else {
$message = Configure::read('Security.self_registration_message');
if (empty($message)) {
$this->loadModel('Server');
$message = $this->Server->serverSettings['Security']['self_registration_message']['value'];
}
$this->set('message', $message);
}
}
public function registrations()
{
$this->loadModel('Inbox');
$params = array(
'recursive' => -1,
'conditions' => array(
'deleted' => 0,
'type' => 'registration'
),
'order' => array(
'timestamp desc'
)
);
$passedArgs = $this->passedArgs;
if (!empty($passedArgs['value'])) {
$lookup = strtolower($passedArgs['value']);
$allSearchFields = array('data', 'user_agent', 'ip');
foreach ($allSearchFields as $field) {
$params['conditions']['AND']['OR'][] = array('LOWER(Inbox.' . $field . ') LIKE' => '%' . $lookup . '%');
}
}
$this->set('passedArgs', json_encode($passedArgs));
if ($this->_isRest()) {
$data = $this->Inbox->find('all', array(
'recursive' => -1,
'conditions' => $params['conditions']
));
foreach ($data as $k => $v) {
$data[$k]['Inbox']['data'] = json_decode($data[$k]['Inbox']['data'], true);
}
return $this->RestResponse->viewData($data, $this->response->type());
} else {
$this->paginate = $params;
$data = $this->paginate('Inbox');
foreach ($data as $k => $message) {
$data[$k]['Inbox']['data'] = json_decode($data[$k]['Inbox']['data'], true);
$data[$k]['Inbox']['requested_role'] = __('default');
if (!empty($data[$k]['Inbox']['data']['custom_perms'])) {
$data[$k]['Inbox']['requested_role'] = array(
'perm_publish' => !empty($data[$k]['Inbox']['data']['perm_publish']) ? __('Yes') : __('No'),
'perm_sync' => !empty($data[$k]['Inbox']['data']['perm_sync']) ? __('Yes') : __('No'),
'perm_admin' => !empty($data[$k]['Inbox']['data']['perm_admin']) ? __('Yes') : __('No')
);
}
}
$this->set('data', $data);
}
}
public function discardRegistrations($id = false)
{
if (!$this->request->is('post') && !$this->request->is('delete')) {
$this->set('id', $id);
$this->set('type', 'discardRegistrations');
$this->render('ajax/discardRegistrations');
} else {
if (empty($id) && !empty($this->params['named']['id'])) {
$id = $this->params['named']['id'];
}
$this->loadModel('Inbox');
if (Validation::uuid($id)) {
$id = $this->Toolbox->findIdByUuid($this->Inbox, $id);
}
$registrations = $this->Inbox->find('all', array(
'recursive' => -1,
'conditions' => array(
'deleted' => 0,
'type' => 'registration',
'id' => $id
)
));
foreach ($registrations as $registration) {
$this->Inbox->delete($registration['Inbox']['id']);
}
$message = sprintf(
'%s registration(s) discarded.',
count($registrations)
);
if ($this->_isRest()) {
return $this->RestResponse->saveSuccessResponse('User', 'discardRegistrations', false, $this->response->type(), $message);
} else {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$this->Log->save(array(
'org' => $this->Auth->user('Organisation')['name'],
'model' => 'User',
'model_id' => $id,
'email' => $this->Auth->user('email'),
'action' => 'discardRegistrations',
'title' => $message,
'change' => ''
));
$this->Flash->success($message);
$this->redirect(array('controller' => 'users', 'action' => 'registrations'));
}
}
}
public function acceptRegistrations($id = false)
{
if (empty($id) && !empty($this->params['named']['id'])) {
$id = $this->params['named']['id'];
}
$this->loadModel('Inbox');
if (Validation::uuid($id)) {
$id = $this->Toolbox->findIdByUuid($this->Inbox, $id);
}
$registrations = $this->Inbox->find('all', array(
'recursive' => -1,
'conditions' => array(
'deleted' => 0,
'type' => 'registration',
'id' => $id
)
));
$suggestedOrg = null;
$suggestedRole = null;
$orgCache = array();
foreach ($registrations as $k => $v) {
$registrations[$k]['Inbox']['data'] = json_decode($registrations[$k]['Inbox']['data'], true);
$roleRequirements = array();
if ($this->request->is('get')) {
$suggestedOrg = $this->User->Organisation->checkDesiredOrg($suggestedOrg, $registrations[$k]);
$suggestedRole = $this->User->Role->checkDesiredRole($suggestedRole, $registrations[$k]);
}
}
$default_role = $this->User->Role->find('first', array(
'recursive' => -1,
'conditions' => array('Role.default_role' => 1),
'fields' => array('Role.id')
));
if ($this->request->is('get')) {
if (!is_array($id)) {
$id = array($id);
}
foreach ($id as $k => $v) {
$id[$k] = 'id[]:' . intval($v);
}
$roles_raw = $this->User->Role->find('all', array(
'recursive' => -1
));
//roles = id => name
$roles = array();
$role_perms = array();
foreach ($roles_raw as $role) {
$roles[$role['Role']['id']] = $role['Role']['name'];
$role_perms[$role['Role']['id']] = array(
'perm_publish' => $role['Role']['perm_publish'],
'perm_sync' => $role['Role']['perm_sync'],
'perm_admin' => $role['Role']['perm_admin']
);
}
if (!empty($default_role)) {
$this->request->data['User']['role_id'] = $default_role['Role']['id'];
}
$this->set('roles', $roles);
$this->set('role_perms', $role_perms);
$orgConditions = array('OR' => array('local' => 1));
if (!empty($suggestedOrg)) {
$orgConditions['OR'][] = array('Organisation.id' => $suggestedOrg[0]);
}
$this->set('orgs', $this->User->Organisation->find('list', array(
'fields' => array('id', 'name'),
'recursive' => -1,
'conditions' => $orgConditions
)));
$this->set('registration', $registrations[$k]);
$this->set('suggestedOrg', $suggestedOrg);
$this->set('suggestedRole', $suggestedRole);
$id = implode('/', $id);
$this->set('id', $id);
$this->layout = false;
} else {
$results = array('successes' => 0, 'fails' => 0);
if (!isset($this->request->data['User']['role_id'])) {
if (!empty($default_role)) {
$this->request->data['User']['role_id'] = $default_role['Role']['id'];
} else {
throw new InvalidArgumentException(__('Role ID not provided and no default role exist on the instance'));
}
}
if (!isset($this->request->data['User']['org_id'])) {
throw new InvalidArgumentException(__('No organisation selected. Supply an Organisation ID'));
} else {
if (Validation::uuid($this->request->data['User']['org_id'])) {
$id = $this->Toolbox->findIdByUuid($this->User->Organisation, $this->request->data['User']['org_id']);
$this->request->data['User']['org_id'] = $id;
}
}
foreach ($registrations as $registration) {
$result = $this->User->registerUser(
$this->Auth->user(),
$registration['Inbox'],
$this->request->data['User']['org_id'],
$this->request->data['User']['role_id']
);
$results[($result ? 'successes' : 'fails')] += 1;
}
$message = array();
if (!empty($results['successes'])) {
$message[] = __('Added %s user(s).', $results['successes']);
}
if (!empty($results['fails'])) {
$message[] = __('Could not add %s user(s), reasons for the failure have been logged.', $results['fails']);
}
if (empty($message)) {
$message[] = __('No new users added - there was nothing to add.');
}
$message = implode(' ', $message);
if ($this->_isRest()) {
if (empty($results['fails']) && !empty($results['successes'])) {
return $this->RestResponse->saveSuccessResponse('User', 'acceptRegistrations', false, $this->response->type(), $message);
} else {
return $this->RestResponse->saveFailResponse('Users', 'acceptRegistrations', false, $message, $this->response->type());
}
} else {
if (empty($results['fails']) && !empty($results['successes'])) {
return new CakeResponse(array('body'=> json_encode(array('saved' => true, 'errors' => $message)), 'status'=>200, 'type' => 'json'));
} else {
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'success' => $message)), 'status'=>200, 'type' => 'json'));
}
}
}
}
}

View File

@ -0,0 +1,305 @@
<?php
class CsseCovidMapWidget
{
public $title = 'CSSE Covid-19 map';
public $render = 'WorldMap';
public $width = 3;
public $height = 4;
public $params = array(
'event_info' => 'World map based on the countries with infections.',
'type' => 'Type of data used for the widget (confirmed, death, recovered).',
'logarithmic' => 'Use a log10 scale for the graph (set via 0/1).'
);
public $description = 'Widget mapping the countries showing confirmed cases of COVID-19.';
public $placeholder =
'{
"event_info": "%CSSE COVID-19 daily report%",
"type": "confirmed",
"logarithmic": 1
}';
public $countryCodes = array(
'Afghanistan' => 'AF',
'Albania' => 'AL',
'Algeria' => 'DZ',
'Angola' => 'AO',
'Argentina' => 'AR',
'Armenia' => 'AM',
'Australia' => 'AU',
'Austria' => 'AT',
'Azerbaijan' => 'AZ',
'Bahamas' => 'BS',
'Bangladesh' => 'BD',
'Belarus' => 'BY',
'Belgium' => 'BE',
'Belize' => 'BZ',
'Benin' => 'BJ',
'Bhutan' => 'BT',
'Bolivia' => 'BO',
'Bosnia and Herz.' => 'BA',
'Botswana' => 'BW',
'Brazil' => 'BR',
'Brunei' => 'BN',
'Bulgaria' => 'BG',
'Burkina Faso' => 'BF',
'Burundi' => 'BI',
'Cambodia' => 'KH',
'Cameroon' => 'CM',
'Canada' => 'CA',
'Central African Rep.' => 'CF',
'Chad' => 'TD',
'Chile' => 'CL',
'China' => 'CN',
'Colombia' => 'CO',
'Congo' => 'CG',
'Costa Rica' => 'CR',
'Croatia' => 'HR',
'Cuba' => 'CU',
'Cyprus' => 'CY',
'Czech Rep.' => 'CZ',
'Côte d\'Ivoire' => 'CI',
'Dem. Rep. Congo' => 'CD',
'Dem. Rep. Korea' => 'KP',
'Denmark' => 'DK',
'Djibouti' => 'DJ',
'Dominican Rep.' => 'DO',
'Ecuador' => 'EC',
'Egypt' => 'EG',
'El Salvador' => 'SV',
'Eq. Guinea' => 'GQ',
'Eritrea' => 'ER',
'Estonia' => 'EE',
'Ethiopia' => 'ET',
'Falkland Is.' => 'FK',
'Fiji' => 'FJ',
'Finland' => 'FI',
'Fr. S. Antarctic Lands' => 'TF',
'France' => 'FR',
'Gabon' => 'GA',
'Gambia' => 'GM',
'Georgia' => 'GE',
'Germany' => 'DE',
'Ghana' => 'GH',
'Greece' => 'GR',
'Greenland' => 'GL',
'Guatemala' => 'GT',
'Guinea' => 'GN',
'Guinea-Bissau' => 'GW',
'Guyana' => 'GY',
'Haiti' => 'HT',
'Honduras' => 'HN',
'Hungary' => 'HU',
'Iceland' => 'IS',
'India' => 'IN',
'Indonesia' => 'ID',
'Iran' => 'IR',
'Iraq' => 'IQ',
'Ireland' => 'IE',
'Israel' => 'IL',
'Italy' => 'IT',
'Jamaica' => 'JM',
'Japan' => 'JP',
'Jordan' => 'JO',
'Kazakhstan' => 'KZ',
'Kenya' => 'KE',
'Korea' => 'KR',
'Kuwait' => 'KW',
'Kyrgyzstan' => 'KG',
'Lao PDR' => 'LA',
'Latvia' => 'LV',
'Lebanon' => 'LB',
'Lesotho' => 'LS',
'Liberia' => 'LR',
'Libya' => 'LY',
'Lithuania' => 'LT',
'Luxembourg' => 'LU',
'Macedonia' => 'MK',
'Madagascar' => 'MG',
'Mainland China' => 'CN',
'Malawi' => 'MW',
'Malaysia' => 'MY',
'Mali' => 'ML',
'Mauritania' => 'MR',
'Mexico' => 'MX',
'Moldova' => 'MD',
'Mongolia' => 'MN',
'Montenegro' => 'ME',
'Morocco' => 'MA',
'Mozamb' => 'MZ',
'Myanmar' => 'MM',
'Namibia' => 'NA',
'Nepal' => 'NP',
'Netherlands' => 'NL',
'New Caledonia' => 'NC',
'New Zealand' => 'NZ',
'Nicaragua' => 'NI',
'Niger' => 'NE',
'Nigeria' => 'NG',
'Norway' => 'NO',
'Oman' => 'OM',
'Pakistan' => 'PK',
'Palestine' => 'PS',
'Panama' => 'PA',
'Papua New Guinea' => 'PG',
'Paraguay' => 'PY',
'Peru' => 'PE',
'Philippines' => 'PH',
'Poland' => 'PL',
'Portugal' => 'PT',
'Puerto Rico' => 'PR',
'Qatar' => 'QA',
'Romania' => 'RO',
'Russia' => 'RU',
'Rwanda' => 'RW',
'S. Sudan' => 'SS',
'Saudi Arabia' => 'SA',
'Senegal' => 'SN',
'Serbia' => 'RS',
'Sierra Leone' => 'SL',
'Slovakia' => 'SK',
'Slovenia' => 'SI',
'Solomon Is.' => 'SB',
'Somalia' => 'SO',
'South Africa' => 'ZA',
'Spain' => 'ES',
'Sri Lanka' => 'LK',
'Sudan' => 'SD',
'Suriname' => 'SR',
'Swaziland' => 'SZ',
'Sweden' => 'SE',
'Switzerland' => 'CH',
'Syria' => 'SY',
'Taiwan' => 'TW',
'Tajikistan' => 'TJ',
'Tanzania' => 'TZ',
'Thailand' => 'TH',
'Timor-Leste' => 'TL',
'Togo' => 'TG',
'Trinidad and Tobago' => 'TT',
'Tunisia' => 'TN',
'Turkey' => 'TR',
'Turkmenistan' => 'TM',
'Uganda' => 'UG',
'Ukraine' => 'UA',
'United Arab Emirates' => 'AE',
'United Kingdom' => 'GB',
'United States' => 'US',
'Uruguay' => 'UY',
'Uzbekistan' => 'UZ',
'Vanuatu' => 'VU',
'Venezuela' => 'VE',
'Vietnam' => 'VN',
'W. Sahara' => 'EH',
'Yemen' => 'YE',
'Zambia' => 'ZM',
'Zimbabwe' => 'ZW'
);
public $countryCodesReversed = array();
public function handler($user, $options = array())
{
$this->countryCodesReversed = array_flip($this->countryCodes);
$this->Event = ClassRegistry::init('Event');
$event_info_condition = empty($options['event_info']) ? '%CSSE COVID-19 daily report%' : $options['event_info'];
$params = array(
'eventinfo' => $event_info_condition,
'order' => 'date desc',
'limit' => 1,
'page' => 1
);
$eventIds = $this->Event->filterEventIds($user, $params);
$params['eventid'] = $eventIds;
$data = array();
if (empty($options['type'])) {
$options['type'] = 'confirmed';
}
if (!empty($eventIds)) {
$events = $this->Event->fetchEvent($user, $params);
$data = $this->__handleEvents($events, $options);
arsort($data);
}
$data = array('data' => $data);
if (!empty($options['type']) && $options['type'] === 'mortality') {
$data['output_decorator'] = 'percentage';
}
if (!empty($options['logarithmic'])) {
$data['logarithmic'] = array();
foreach ($data['data'] as $k => $v) {
if ($v == 0) {
$value = 0;
} else if ($v <= 1) {
$value = 0.2;
} else {
$value = log10($v);
}
$data['logarithmic'][$k] = $value;
}
}
$data['scope'] = Inflector::humanize($options['type']);
$data['colour_scale'] = json_encode(array('#F08080', '#8B0000'), true);
return $data;
}
private function __handleEvents($events, $options)
{
$data = array();
if (!empty($events)) {
foreach ($events as $event) {
if (!empty($event['Object'])) {
$data = $this->__handleObjects($data, $event['Object'], $options);
}
}
}
return $data;
}
private function __handleObjects($data, $objects, $options)
{
foreach ($objects as $object) {
if ($object['name'] === 'covid19-csse-daily-report') {
$temp = $this->__interpretObject($object);
$data = $this->__rearrangeResults($data, $temp, $options);
}
}
if ($options['type'] === 'mortality') {
foreach ($data as $k => $v) {
$data[$k] = round(100 * (empty($v['death']) ? 0 : $v['death']) / $v['confirmed'], 2);
}
}
return $data;
}
private function __rearrangeResults($data, $temp, $options)
{
$country = $temp['country-region'];
$type = $options['type'];
if (!empty($temp[$type])) {
$data[$country] = (empty($data[$country]) ? $temp[$type] : ($data[$country] + $temp[$type]));
}
return $data;
}
private function __interpretObject($object)
{
$temp = array();
$validFields = array('country-region', 'confirmed', 'death', 'recovered');
foreach ($object['Attribute'] as $attribute) {
if (in_array($attribute['object_relation'], $validFields)) {
if ($attribute['object_relation'] === 'country-region') {
if (!empty($this->countryCodes[$attribute['value']])) {
$temp[$attribute['object_relation']] = $this->countryCodes[$attribute['value']];
} elseif (isset($this->countryCodesReversed[$attribute['value']])) {
$temp[$attribute['object_relation']] = $attribute['value'];
} else {
$temp[$attribute['object_relation']] = 'XX';
}
} else {
$attribute['value'] = intval($attribute['value']);
$temp[$attribute['object_relation']] = $attribute['value'];
}
}
}
return $temp;
}
}

View File

@ -0,0 +1,223 @@
<?php
class CsseCovidTrendsWidget
{
public $title = 'CSSE Covid-19 trends';
public $render = 'MultiLineChart';
public $width = 4;
public $height = 5;
public $params = array(
'event_info' => 'Substring included in the info field of relevant CSSE COVID-19 events.',
'type' => 'Type of data used for the widget - confirmed (default), death, recovered, mortality, active.',
'insight' => 'Insight type - raw (default), growth, percent.',
'countries' => 'List of countries to be included (using the names used by the reports, such as Belgium, US, Germany).',
'timeframe' => 'Timeframe for events taken into account in days (going back from now, using the date field, default 10).'
);
public $description = 'Widget showing line charts for the evolution of the various case types.';
public $placeholder =
'{
"event_info": "%CSSE COVID-19 daily report%",
"type": "confirmed",
"insight": "raw",
"countries": ["Luxembourg", "Germany", "Belgium", "France"],
"timeframe": 20
}';
private $__countryAliases = array(
'Mainland China' => 'China',
'Korea, South' => 'South Korea'
);
public $cacheLifetime = 600;
public $autoRefreshDelay = false;
private $__countries = array();
public function handler($user, $options = array())
{
$this->Event = ClassRegistry::init('Event');
if (!isset($options['insight']) || !isset($this->__insightFunctions[$options['insight']])) {
$options['Insight'] = 'calculate_growth_rate';
}
if (empty($options['timeframe'])) {
$options['timeframe'] = 10;
}
if (empty($options['countries'])) {
$options['countries'] = array("Luxembourg", "Germany", "Belgium", "France");
}
if (empty($options['insight'])) {
$options['insight'] = 'raw';
}
$event_info_condition = empty($options['event_info']) ? '%CSSE COVID-19 daily report%' : $options['event_info'];
$params = array(
'eventinfo' => $event_info_condition,
'order' => 'date desc',
'date' => (empty($options['timeframe']) ? 10 : $options['timeframe']) . 'd'
);
$eventIds = $this->Event->filterEventIds($user, $params);
$eventIds = array_reverse(array_values($eventIds));
$data = array();
if (empty($options['type'])) {
$options['type'] = 'confirmed';
}
if (!empty($eventIds)) {
$previous = false;
foreach ($eventIds as $eventId) {
$params = array('eventid' => $eventId);
$event = $this->Event->fetchEvent($user, $params);
if (!empty($event)) {
$data[$event[0]['Event']['date']] = $this->__handleEvent($event[0], $options, $previous);
}
$previous = $data[$event[0]['Event']['date']];
}
}
$startDate = date('Y-m-d', strtotime('-' . intval($options['timeframe']) . ' days'));
//$data = call_user_func_array((array($this, $this->__insightFunctions[$options['Insight']]), array($startDate));
$data = array('data' => $data);
$data['insight'] = empty($options['insight']) ? 'raw' : $options['insight'];
foreach ($data['data'] as $date => $day) {
$data['data'][$date]['date'] = $date;
foreach ($this->__countries as $country => $temp) {
if (empty($data['data'][$date][$country][$data['insight']])) {
$data['data'][$date][$country][$data['insight']] = 0;
}
}
}
$data['data'] = array_values($data['data']);
$formulaData = array(
'insight' => array(
'raw' => '',
'growth' => 'daily increase in ',
'percent' => 'percentage wise daily increase in '
),
'type' => array(
'confirmed' => 'confirmed cases',
'death' => 'mortalities',
'recovered' => 'recoveries',
'mortality' => 'mortality rate',
'active' => 'active cases'
)
);
$data['formula'] = sprintf(
'%s%s',
(isset($options['insight']) && !empty($formulaData[$options['insight']])) ?
$formulaData['insight'][$options['insight']] :
$formulaData['insight']['raw'],
(isset($options['type']) && !empty($formulaData['type'][$options['type']])) ?
$formulaData['type'][$options['type']] :
$formulaData['type']['confirmed']
);
$data['formula'] = ucfirst($data['formula']);
foreach ($data['data'] as &$day) {
foreach ($day as $key => &$countryData) {
if ($key !== 'date') {
$countryData = $countryData[$options['insight']];
}
}
}
return $data;
}
private function __handleEvent($event, $options, $previous)
{
$data = array();
if (!empty($event['Object'])) {
$data = $this->__handleObjects($data, $event['Object'], $options, $previous);
}
$data['date'] = $event['Event']['date'];
return $data;
}
private function __handleObjects($data, $objects, $options, $previous)
{
foreach ($objects as $object) {
if ($object['name'] === 'covid19-csse-daily-report') {
$temp = $this->__interpretObject($object, $previous);
$data = $this->__rearrangeResults($data, $temp, $options, $previous);
}
}
if ($options['type'] === 'mortality') {
foreach ($data as $k => $v) {
$data[$k]['mortality'] = round(100 * (empty($v['death']) ? 0 : $v['death']) / $v['confirmed'], 2);
}
}
if (!empty($options['insight']) && $options['insight'] !== 'raw') {
if ($options['insight'] == 'growth') {
foreach ($data as $k => $countryData) {
foreach ($countryData as $type => &$value) {
if (!isset($previous[$k][$type])) {
$previous[$k][$type] = $data[$k][$type];
}
$data[$k]['growth'] = $data[$k][$type] - $previous[$k][$type];
}
}
} else if ($options['insight'] == 'percent') {
foreach ($data as $k => $countryData) {
foreach ($countryData as $type => &$value) {
if (empty($previous[$k][$type])) {
$previous[$k][$type] = $data[$k][$type];
}
if (!empty($previous[$k][$type])) {
$data[$k]['percent'] = 100 * ($data[$k][$type] - $previous[$k][$type]) / $previous[$k][$type];
}
}
}
}
} else {
foreach ($data as $k => &$countryData) {
$data[$k]['raw'] = $data[$k][$options['type']];
}
}
return $data;
}
private function __rearrangeResults($data, $temp, $options, $previous)
{
$country = $temp['country-region'];
if (!in_array($country, $options['countries'])) {
return $data;
}
$this->__countries[$country] = 1;
if ($options['type'] === 'mortality') {
foreach (array('confirmed', 'death') as $type) {
if (!empty($temp[$type])) {
$data[$country][$type] = (empty($data[$country][$type]) ? $temp[$type] : ($data[$country][$type] + $temp[$type]));
}
}
} else if ($options['type'] === 'active') {
if (empty($data[$country]['active'])) {
$data[$country]['active'] = 0;
}
$data[$country]['active'] =
$data[$country]['active'] +
(empty($temp['confirmed']) ? 0 : $temp['confirmed']) -
(empty($temp['death']) ? 0 : $temp['death']) -
(empty($temp['recovered']) ? 0 : $temp['recovered']);
} else {
$type = $options['type'];
if (!empty($temp[$type])) {
$data[$country][$type] = (empty($data[$country][$type]) ? $temp[$type] : ($data[$country][$type] + $temp[$type]));
}
}
return $data;
}
private function __interpretObject($object, $previous)
{
$temp = array();
$validFields = array('country-region', 'confirmed', 'death', 'recovered');
foreach ($object['Attribute'] as $attribute) {
if (in_array($attribute['object_relation'], $validFields)) {
if ($attribute['object_relation'] !== 'country-region') {
$attribute['value'] = intval($attribute['value']);
} else {
if (isset($this->__countryAliases[$attribute['value']])) {
$attribute['value'] = $this->__countryAliases[$attribute['value']];
}
}
$temp[$attribute['object_relation']] = $attribute['value'];
}
}
return $temp;
}
}

View File

@ -0,0 +1,191 @@
<?php
class CsseCovidWidget
{
public $title = 'CSSE Covid-19 data';
public $render = 'BarChart';
public $width = 3;
public $height = 4;
public $params = array(
'event_info' => 'Substring included in the info field of relevant CSSE COVID-19 events.',
'type' => 'Type of data used for the widget (confirmed, death, recovered, mortality, active).',
'logarithmic' => 'Use a log10 scale for the graph (set via 0/1).',
'relative' => 'Take the country\'s population size into account (count / 10M)'
);
public $description = 'Widget visualising the countries ranked by highest count in the chosen category.';
public $placeholder =
'{
"event_info": "%CSSE COVID-19 daily report%",
"type": "confirmed",
"logarithmic": 1,
"relative": 0
}';
public $__nameReplacements = array(
'US' => 'United States',
'Cote d\'Ivoire' => 'Ivory Coast',
'Holy See' => 'Vatican',
'Congo (Kinshasa)' => 'Democratic Republic of Congo',
'Taiwan*' => 'Taiwan',
'Korea, South' => 'South Korea',
'Mainland China' => 'China'
);
private $__populationData = array();
public function handler($user, $options = array())
{
$this->Event = ClassRegistry::init('Event');
$event_info_condition = empty($options['event_info']) ? '%CSSE COVID-19 daily report%' : $options['event_info'];
$params = array(
'eventinfo' => $event_info_condition,
'order' => 'date desc',
'limit' => 1,
'page' => 1
);
$eventIds = $this->Event->filterEventIds($user, $params);
$params['eventid'] = $eventIds;
$data = array();
if (empty($options['type'])) {
$options['type'] = 'confirmed';
}
if (!empty($eventIds)) {
$events = $this->Event->fetchEvent($user, $params);
$data = $this->__handleEvents($events, $options);
arsort($data);
}
$data = array('data' => $data);
if (!empty($options['type']) && $options['type'] === 'mortality') {
$data['output_decorator'] = 'percentage';
}
if ($options['type'] !== 'mortality' && !empty($options['relative'])) {
$this->__getPopulationData();
if (!empty($this->__populationData)) {
foreach ($data['data'] as $country => $value) {
if (isset($this->__nameReplacements[$country])) {
$alias = $this->__nameReplacements[$country];
} else {
$alias = $country;
}
if (empty($this->__populationData[$alias])) {
unset($data['data'][$country]);
} else {
$pre = $data['data'][$country];
$data['data'][$country] = round(10000000 * $data['data'][$country] / $this->__populationData[$alias]);
}
}
}
arsort($data['data']);
}
if (!empty($options['logarithmic'])) {
$data['logarithmic'] = array();
foreach ($data['data'] as $k => $v) {
if ($v == 0) {
$value = 0;
} else if ($v <= 1) {
$value = 0.2;
} else {
$value = log10($v);
}
$data['logarithmic'][$k] = $value;
}
}
return $data;
}
private function __getPopulationData()
{
$this->Galaxy = ClassRegistry::init('Galaxy');
$galaxy = $this->Galaxy->find('first', array(
'recursive' => -1,
'contain' => array('GalaxyCluster' => array('GalaxyElement')),
'conditions' => array('Galaxy.name' => 'Country')
));
if (empty($galaxy)) {
return false;
}
foreach ($galaxy['GalaxyCluster'] as $cluster) {
foreach ($cluster['GalaxyElement'] as $element) {
if ($element['key'] === 'Population') {
$this->__populationData[$cluster['description']] = $element['value'];
}
}
}
return true;
}
private function __handleEvents($events, $options)
{
$data = array();
if (!empty($events)) {
foreach ($events as $event) {
if (!empty($event['Object'])) {
$data = $this->__handleObjects($data, $event['Object'], $options);
}
}
}
return $data;
}
private function __handleObjects($data, $objects, $options)
{
foreach ($objects as $object) {
if ($object['name'] === 'covid19-csse-daily-report') {
$temp = $this->__interpretObject($object);
$data = $this->__rearrangeResults($data, $temp, $options);
}
}
if ($options['type'] === 'mortality') {
foreach ($data as $k => $v) {
if (!isset($v['death']) || empty($v['confirmed'])) {
unset($data[$k]);
continue;
}
$data[$k] = round(100 * (empty($v['death']) ? 0 : $v['death']) / $v['confirmed'], 2);
}
}
return $data;
}
private function __rearrangeResults($data, $temp, $options)
{
$country = $temp['country-region'];
if ($options['type'] === 'mortality') {
foreach (array('confirmed', 'death') as $type) {
if (!empty($temp[$type])) {
$data[$country][$type] = (empty($data[$country][$type]) ? $temp[$type] : ($data[$country][$type] + $temp[$type]));
}
}
} else if ($options['type'] === 'active') {
if (empty($data[$country]['active'])) {
$data[$country]['active'] = 0;
}
$data[$country]['active'] =
$data[$country]['active'] +
(empty($temp['confirmed']) ? 0 : $temp['confirmed']) -
(empty($temp['death']) ? 0 : $temp['death']) -
(empty($temp['recovered']) ? 0 : $temp['recovered']);
} else {
$type = $options['type'];
if (!empty($temp[$type])) {
$data[$country] = (empty($data[$country]) ? $temp[$type] : ($data[$country] + $temp[$type]));
}
}
return $data;
}
private function __interpretObject($object)
{
$temp = array();
$validFields = array('country-region', 'confirmed', 'death', 'recovered');
foreach ($object['Attribute'] as $attribute) {
if (in_array($attribute['object_relation'], $validFields)) {
if ($attribute['object_relation'] !== 'country-region') {
$attribute['value'] = intval($attribute['value']);
}
$temp[$attribute['object_relation']] = $attribute['value'];
}
}
return $temp;
}
}

View File

@ -9,11 +9,13 @@ class TrendingTagsWidget
public $params = array(
'time_window' => 'The time window, going back in seconds, that should be included.',
'exclude' => 'List of substrings to exclude tags by - for example "sofacy" would exclude any tag containing sofacy.',
'include' => 'List of substrings to include tags by - for example "sofacy" would include any tag containing sofacy.'
'include' => 'List of substrings to include tags by - for example "sofacy" would include any tag containing sofacy.',
'threshold' => 'Limits the number of displayed tags. Default: 10'
);
public $placeholder =
'{
"time_window": "86400",
"threshold": 15,
"exclude": ["tlp:", "pap:"],
"include": ["misp-galaxy:", "my-internal-taxonomy"]
}';
@ -26,6 +28,7 @@ class TrendingTagsWidget
'metadata' => 1,
'timestamp' => time() - (empty($options['time_window']) ? 8640000 : $options['time_window'])
);
$threshold = empty($options['threshold']) ? 10 : $options['threshold'];
$eventIds = $this->Event->filterEventIds($user, $params);
$params['eventid'] = $eventIds;
$events = array();
@ -34,8 +37,6 @@ class TrendingTagsWidget
}
$tags = array();
$tagColours = array();
$rules['exclusions'] = empty($options['exclude']) ? array() : $options['exclude'];
$rules['inclusions'] = empty($options['exclude']) ? array() : $options['exclude'];
foreach ($events as $event) {
foreach ($event['EventTag'] as $et) {
if ($this->checkTag($options, $et['Tag']['name'])) {
@ -49,7 +50,7 @@ class TrendingTagsWidget
}
}
arsort($tags);
$data['data'] = array_slice($tags, 0, 10);
$data['data'] = array_slice($tags, 0, $threshold);
$data['colours'] = $tagColours;
return $data;
}

View File

@ -131,12 +131,12 @@ class NidsSuricataExport extends NidsExport
$data['host'] = NidsExport::replaceIllegalChars($data['host']);
$tag = 'tag:session,600,seconds;';
# IP: classic IP rule for HTTPS
$suricata_protocol = 'tcp';
$suricata_protocol = 'tls';
$suricata_src_ip = '$HOME_NET';
$suricata_src_port = 'any';
$suricata_dst_ip = $data['host'];
$suricata_dst_ip = '$EXTERNAL_NET';
$suricata_dst_port = NidsExport::getProtocolPort($scheme, $data['port']);
$content = 'flow:to_server; app-layer-protocol:tls;';
$content = 'tls_sni; content:"' . $data['host'] . '";';
break;
case "ssh":

View File

@ -19,6 +19,8 @@ class Stix2Export extends StixExport
$scriptFile = $this->__scripts_dir . $this->__script_name;
$filename = $this->__scripts_dir . 'tmp/' . $filename;
$my_server = ClassRegistry::init('Server');
return shell_exec($my_server->getPythonVersion() . ' ' . $scriptFile . ' ' . $filename . $this->__end_of_cmd);
$result = shell_exec($my_server->getPythonVersion() . ' ' . $scriptFile . ' ' . $filename . $this->__end_of_cmd);
$result = preg_split("/\r\n|\n|\r/", trim($result));
return end($result);
}
}

View File

@ -135,4 +135,145 @@
return $this->__json;
}
/*
* Extrapolation strategy:
* - If only positive sightings: Will be from first to last sighting
* - If both positive and false positive: False positive get priority. It will be marked as false positive until next positive sighting
*/
public function get_sighting_timeline($id)
{
$event = $this->__eventModel->fetchEvent($this->__user, array(
'eventid' => $id,
'flatten' => 1,
'includeTagRelations' => 1,
'extended' => $this->__extended_view
));
$this->__json['items'] = array();
if (empty($event)) {
return $this->__json;
} else {
$event = $event[0];
}
$lookupAttribute = array();
foreach ($event['Attribute'] as $k => $attribute) {
$lookupAttribute[$attribute['id']] = &$event['Attribute'][$k];
}
// regroup sightings per attribute
$regroupedSightings = array();
foreach ($event['Sighting'] as $k => $sighting) {
$event['Sighting'][$k]['date_sighting'] *= 1000; // adapt to use micro
$regroupedSightings[$sighting['attribute_id']][] = &$event['Sighting'][$k];
}
// make sure sightings are ordered
uksort($regroupedSightings, function ($a, $b) {
return $a['date_sighting'] > $b['date_sighting'];
});
// generate extrapolation
$now = time()*1000;
foreach ($regroupedSightings as $attributeId => $sightings) {
$i = 0;
while ($i < count($sightings)) {
$sighting = $sightings[$i];
$attribute = $lookupAttribute[$attributeId];
$fpSightingIndex = $this->getNextFalsePositiveSightingIndex($sightings, $i+1);
if ($fpSightingIndex === false) { // No next FP, extrapolate to now
$this->__json['items'][] = array(
'attribute_id' => $attributeId,
'id' => sprintf('%s-%s', $attributeId, $sighting['id']),
'uuid' => $sighting['uuid'],
'content' => $attribute['value'],
'event_id' => $attribute['event_id'],
'group' => 'sighting_positive',
'timestamp' => $attribute['timestamp'],
'first_seen' => $sighting['date_sighting'],
'last_seen' => $now,
);
break;
} else {
// set up until last positive
$pSightingIndex = $fpSightingIndex - 1;
$halfTime = 0;
if ($pSightingIndex == $i) {
// we have only one positive sighting, thus the UP time should be take from a pooling frequence
// for now, consider it UP only for half the time until the next FP
$halfTime = ($sightings[$i+1]['date_sighting'] - $sighting['date_sighting'])/2;
}
$pSighting = $sightings[$pSightingIndex];
$this->__json['items'][] = array(
'attribute_id' => $attributeId,
'id' => sprintf('%s-%s', $attributeId, $sighting['id']),
'uuid' => $sighting['uuid'],
'content' => $attribute['value'],
'event_id' => $attribute['event_id'],
'group' => 'sighting_positive',
'timestamp' => $attribute['timestamp'],
'first_seen' => $sighting['date_sighting'],
'last_seen' => $pSighting['date_sighting'] + $halfTime,
);
// No next FP, extrapolate to now
$fpSighting = $sightings[$fpSightingIndex];
$secondNextPSightingIndex = $this->getNextPositiveSightingIndex($sightings, $fpSightingIndex+1);
if ($secondNextPSightingIndex === false) { // No next P, extrapolate to now
$this->__json['items'][] = array(
'attribute_id' => $attributeId,
'id' => sprintf('%s-%s', $attributeId, $sighting['id']),
'uuid' => $sighting['uuid'],
'content' => $attribute['value'],
'event_id' => $attribute['event_id'],
'group' => 'sighting_negative',
'timestamp' => $attribute['timestamp'],
'first_seen' => $pSighting['date_sighting'] - $halfTime,
'last_seen' => $now,
);
break;
} else {
if ($halfTime > 0) { // We need to fake a previous P
$pSightingIndex = $pSightingIndex+1;
$pSighting = $sightings[$pSightingIndex];
}
// set down until next postive
$secondNextPSighting = $sightings[$secondNextPSightingIndex];
$this->__json['items'][] = array(
'attribute_id' => $attributeId,
'id' => sprintf('%s-%s', $attributeId, $sighting['id']),
'uuid' => $pSighting['uuid'],
'content' => $attribute['value'],
'event_id' => $attribute['event_id'],
'group' => 'sighting_negative',
'timestamp' => $attribute['timestamp'],
'first_seen' => $pSighting['date_sighting'] - $halfTime,
'last_seen' => $secondNextPSighting['date_sighting'],
);
$i = $secondNextPSightingIndex;
}
}
}
}
return $this->__json;
}
private function getNextFalsePositiveSightingIndex($sightings, $startIndex)
{
for ($i=$startIndex; $i < count($sightings) ; $i++) {
$sighting = $sightings[$i];
if ($sighting['type'] == 1) { // is false positive
return $i;
}
}
return false;
}
private function getNextPositiveSightingIndex($sightings, $startIndex)
{
for ($i=$startIndex; $i < count($sightings) ; $i++) {
$sighting = $sightings[$i];
if ($sighting['type'] == 0) { // is false positive
return $i;
}
}
return false;
}
}

View File

@ -0,0 +1,227 @@
<?php
class MalwareTool
{
const ZIP_PASSWORD = 'infected';
const ADVANCED_EXTRACTION_SCRIPT_PATH = APP . 'files/scripts/generate_file_objects.py';
/**
* @param string $originalFilename
* @param string $content
* @param string $md5
* @return string Content of zipped file
* @throws Exception
*/
public function encrypt($originalFilename, $content, $md5)
{
if (method_exists("ZipArchive", "setEncryptionName")) {
// When PHP zip extension is installed and supports creating encrypted archives.
return $this->encryptByExtension($originalFilename, $content, $md5);
} else {
return $this->encryptByCommand($originalFilename, $content, $md5);
}
}
/**
* @param string $originalFilename
* @param string $content
* @param string $md5
* @return string Content of zipped file
* @throws Exception
*/
private function encryptByCommand($originalFilename, $content, $md5)
{
$tempDir = $this->tempDir();
$contentsFile = new File($tempDir . DS . $md5, true);
if (!$contentsFile->write($content)) {
throw new Exception("Could not write content to file '{$contentsFile->path}'.");
}
$contentsFile->close();
$fileNameFile = new File($tempDir . DS . $md5 . '.filename.txt', true);
if (!$fileNameFile->write($originalFilename)) {
throw new Exception("Could not write original file name to file '{$fileNameFile->path}'.");
}
$fileNameFile->close();
$zipFile = new File($tempDir . DS . $md5 . '.zip');
$exec = [
'zip',
'-j', // junk (don't record) directory names
'-P', // use standard encryption
self::ZIP_PASSWORD,
escapeshellarg($zipFile->path),
escapeshellarg($contentsFile->path),
escapeshellarg($fileNameFile->path),
];
try {
$this->execute($exec);
$zipContent = $zipFile->read();
if ($zipContent === false) {
throw new Exception("Could not read content of newly created ZIP file.");
}
return $zipContent;
} catch (Exception $e) {
throw new Exception("Could not create encrypted ZIP file '{$zipFile->path}'.", 0, $e);
} finally {
$fileNameFile->delete();
$contentsFile->delete();
$zipFile->delete();
}
}
/**
* @param string $originalFilename
* @param string $content
* @param string $md5
* @return string Content of zipped file
* @throws Exception
*/
private function encryptByExtension($originalFilename, $content, $md5)
{
$zipFilePath = $this->tempFileName();
$zip = new ZipArchive();
$result = $zip->open($zipFilePath, ZipArchive::CREATE);
if ($result === true) {
$zip->setPassword(self::ZIP_PASSWORD);
$zip->addFromString($md5, $content);
$zip->setEncryptionName($md5, ZipArchive::EM_AES_128);
$zip->addFromString("$md5.filename.txt", $originalFilename);
$zip->setEncryptionName("$md5.filename.txt", ZipArchive::EM_AES_128);
$zip->close();
} else {
throw new Exception("Could not create encrypted ZIP file '$zipFilePath'. Error code: $result");
}
$zipFile = new File($zipFilePath);
$zipContent = $zipFile->read();
if ($zipContent === false) {
throw new Exception("Could not read content of newly created ZIP file.");
}
$zipFile->delete();
return $zipContent;
}
/**
* @param string $content
* @param array $hashTypes
* @return array
* @throws InvalidArgumentException
*/
public function computeHashes($content, array $hashTypes = array())
{
$validHashes = array('md5', 'sha1', 'sha256');
$hashes = [];
foreach ($hashTypes as $hashType) {
if (!in_array($hashType, $validHashes)) {
throw new InvalidArgumentException("Hash type '$hashType' is not valid hash type.");
}
$hashes[$hashType] = hash($hashType, $content);
}
return $hashes;
}
/**
* @param string $pythonBin
* @param string $filePath
* @return array
* @throws Exception
*/
public function advancedExtraction($pythonBin, $filePath)
{
return $this->executeAndParseJsonOutput([
$pythonBin,
self::ADVANCED_EXTRACTION_SCRIPT_PATH,
'-p',
escapeshellarg($filePath),
]);
}
/**
* @param string $pythonBin
* @return array
* @throws Exception
*/
public function checkAdvancedExtractionStatus($pythonBin)
{
return $this->executeAndParseJsonOutput([$pythonBin, self::ADVANCED_EXTRACTION_SCRIPT_PATH, '-c']);
}
private function tempFileName()
{
$randomName = (new RandomTool())->random_str(false, 12);
return $this->tempDir() . DS . $randomName;
}
/**
* @return string
*/
private function tempDir()
{
return Configure::read('MISP.tmpdir') ?: sys_get_temp_dir();
}
/**
* @param array $command
* @return array
* @throws Exception
*/
private function executeAndParseJsonOutput(array $command)
{
$output = $this->execute($command);
$json = json_decode($output, true);
if ($json === null) {
throw new Exception("Command output is not valid JSON: " . json_last_error_msg());
}
return $json;
}
/**
* This method is much more complicated than just `exec`, but it also provide stderr output, so Exceptions
* can be much more specific.
*
* @param array $command
* @return string
* @throws Exception
*/
private function execute(array $command)
{
$descriptorspec = [
1 => ["pipe", "w"], // stdout
2 => ["pipe", "w"], // stderr
];
$command = implode(' ', $command);
$process = proc_open($command, $descriptorspec, $pipes);
if (!$process) {
throw new Exception("Command '$command' could be started.");
}
$stdout = stream_get_contents($pipes[1]);
if ($stdout === false) {
throw new Exception("Could not get STDOUT of command.");
}
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$returnCode = proc_close($process);
if ($returnCode !== 0) {
throw new Exception("Command '$command' return error code $returnCode. STDERR: '$stderr', STDOUT: '$stdout'");
}
return $stdout;
}
}

View File

@ -14,7 +14,7 @@ class QueryTool
{
$db = $model->getDataSource();
$connection = $db->getConnection();
if ($db->config['datasource'] == 'Database/Mysql' ) {
if ($db->config['datasource'] == 'Database/Mysql' || $db->config['datasource'] == 'Database/MysqlObserver') {
$query = $connection->prepare('DELETE FROM ' . $table . ' WHERE ' . $field . ' = :value');
} elseif ($db->config['datasource'] == 'Database/Postgres' ) {
$query = $connection->prepare('DELETE FROM "' . $table . '" WHERE "' . $field . '" = :value');

@ -1 +1 @@
Subproject commit d2e1681eb8ec75e6c2819fa113834843fed6995a
Subproject commit 5ccb12354dfc08ca1b3e0a430e8668bf1610b5d3

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: misp\n"
"PO-Revision-Date: 2020-02-27 02:21\n"
"PO-Revision-Date: 2020-04-24 01:13\n"
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
"Language-Team: German\n"
"MIME-Version: 1.0\n"

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: misp\n"
"PO-Revision-Date: 2020-02-27 02:23\n"
"PO-Revision-Date: 2020-04-24 01:14\n"
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
"Language-Team: Italian\n"
"MIME-Version: 1.0\n"
@ -82,11 +82,12 @@ msgstr ""
#: Console/Command/AdminShell.php:563
msgid "\n"
"Error: %s\n"
msgstr ""
msgstr "\n"
"Errore: %s\n"
#: Console/Command/AdminShell.php:565
msgid "%s events purged.\n"
msgstr ""
msgstr "%s eventi eliminati.\n"
#: Console/Command/AdminShell.php:587
msgid "> Database schema dumped on disk"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: misp\n"
"PO-Revision-Date: 2020-02-27 02:23\n"
"PO-Revision-Date: 2020-04-24 01:12\n"
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
"Language-Team: Russian\n"
"MIME-Version: 1.0\n"

View File

@ -1,7 +1,7 @@
msgid ""
msgstr ""
"Project-Id-Version: misp\n"
"PO-Revision-Date: 2020-02-27 02:20\n"
"PO-Revision-Date: 2020-04-24 01:14\n"
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese Simplified\n"
"MIME-Version: 1.0\n"
@ -73,11 +73,11 @@ msgstr ""
#: Console/Command/AdminShell.php:536
#: Controller/UsersController.php:1234
msgid "Invalid user."
msgstr ""
msgstr "无效用户"
#: Console/Command/AdminShell.php:539
msgid "User has to be a site admin."
msgstr ""
msgstr "用户必须是站点管理员。"
#: Console/Command/AdminShell.php:563
msgid "\n"
@ -181,7 +181,7 @@ msgstr "未设置事件ID。"
#: Controller/AttributesController.php:112
msgid "You do not have permissions to create attributes"
msgstr ""
msgstr "你没有权限创建属性."
#: Controller/AttributesController.php:132;422;602;832;3127;3142
#: Controller/EventGraphController.php:88
@ -350,7 +350,7 @@ msgstr "此功能只能通过AJAX访问。"
#: Controller/AttributesController.php:2241
msgid "You do not have permission to do that"
msgstr ""
msgstr "您无权执行此操作。"
#: Controller/AttributesController.php:2271;2279
#: Controller/EventsController.php:3643;3796;4999
@ -395,7 +395,7 @@ msgstr "无效请求类型。"
#: Controller/AttributesController.php:2823;3030
#: Controller/TagCollectionsController.php:254
msgid "Invalid tag"
msgstr ""
msgstr "无效标签"
#: Controller/AttributesController.php:3120
#: Controller/EventsController.php:5197
@ -409,11 +409,11 @@ msgstr "无效属性。"
#: Controller/CommunitiesController.php:150
msgid "Request sent."
msgstr ""
msgstr "请求已发送"
#: Controller/CommunitiesController.php:150
msgid "Something went wrong and the request could not be sent."
msgstr ""
msgstr "出错了,请求无法发送。"
#: Controller/CommunitiesController.php:166
msgid "The message could not be sent (either because e-mailing is disabled or because encryption is misconfigured), however, you can view the e-mail that would have been sent below. Feel free to send it manually."

View File

@ -77,7 +77,8 @@ class AppModel extends Model
27 => false, 28 => false, 29 => false, 30 => false, 31 => false, 32 => false,
33 => false, 34 => false, 35 => false, 36 => false, 37 => false, 38 => false,
39 => false, 40 => false, 41 => false, 42 => false, 43 => false, 44 => false,
45 => false, 46 => false, 47 => false, 48 => false, 49 => false
45 => false, 46 => false, 47 => false, 48 => false, 49 => false, 50 => false,
51 => false, 52 => false, 53 => false
);
public $advanced_updates_description = array(
@ -123,7 +124,7 @@ class AppModel extends Model
public function isAcceptedDatabaseError($errorMessage, $dataSource)
{
$isAccepted = false;
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$errorDuplicateColumn = 'SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name';
$errorDuplicateIndex = 'SQLSTATE[42000]: Syntax error or access violation: 1061 Duplicate key name';
$errorDropIndex = "/SQLSTATE\[42000\]: Syntax error or access violation: 1091 Can't DROP '[\w]+'; check that column\/key exists/";
@ -722,7 +723,7 @@ class AppModel extends Model
$sqlArray[] = "ALTER TABLE taxonomy_predicates ADD colour varchar(7) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '';";
break;
case '2.4.60':
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$sqlArray[] = 'CREATE TABLE IF NOT EXISTS `attribute_tags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`attribute_id` int(11) NOT NULL,
@ -1267,7 +1268,7 @@ class AppModel extends Model
case 39:
$sqlArray[] = "CREATE TABLE IF NOT EXISTS user_settings (
`id` int(11) NOT NULL AUTO_INCREMENT,
`key` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL,
`setting` varchar(255) COLLATE utf8_bin NOT NULL,
`value` text,
`user_id` int(11) NOT NULL,
`timestamp` int(11) NOT NULL,
@ -1349,6 +1350,45 @@ class AppModel extends Model
INDEX `restrict_to_permission_flag` (`restrict_to_permission_flag`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
break;
case 50:
$sqlArray[] = "CREATE TABLE IF NOT EXISTS inbox (
`id` int(11) NOT NULL AUTO_INCREMENT,
`uuid` varchar(40) COLLATE utf8_bin NOT NULL,
`title` varchar(191) NOT NULL,
`type` varchar(191) NOT NULL,
`ip` varchar(191) NOT NULL,
`user_agent` text,
`user_agent_sha256` varchar(64) NOT NULL,
`comment` text,
`deleted` tinyint(1) NOT NULL DEFAULT 0,
`timestamp` int(11) NOT NULL,
`store_as_file` tinyint(1) NOT NULL DEFAULT 0,
`data` longtext,
PRIMARY KEY (id),
INDEX `title` (`title`),
INDEX `type` (`type`),
INDEX `uuid` (`uuid`),
INDEX `user_agent_sha256` (`user_agent_sha256`),
INDEX `ip` (`ip`),
INDEX `timestamp` (`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
break;
case 51:
$sqlArray[] = "ALTER TABLE `feeds` ADD `orgc_id` int(11) NOT NULL DEFAULT 0";
$this->__addIndex('feeds', 'orgc_id');
break;
case 52:
if (!empty($this->query("SHOW COLUMNS FROM `admin_settings` LIKE 'key';"))) {
$sqlArray[] = "ALTER TABLE admin_settings CHANGE `key` `setting` varchar(255) COLLATE utf8_bin NOT NULL;";
$this->__addIndex('admin_settings', 'setting');
}
break;
case 53:
if (!empty($this->query("SHOW COLUMNS FROM `user_settings` LIKE 'key';"))) {
$sqlArray[] = "ALTER TABLE user_settings CHANGE `key` `setting` varchar(255) COLLATE utf8_bin NOT NULL;";
$this->__addIndex('user_settings', 'setting');
}
break;
case 'fixNonEmptySharingGroupID':
$sqlArray[] = 'UPDATE `events` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
$sqlArray[] = 'UPDATE `attributes` SET `sharing_group_id` = 0 WHERE `distribution` != 4;';
@ -1572,7 +1612,7 @@ class AppModel extends Model
$dataSource = $dataSourceConfig['datasource'];
$this->Log = ClassRegistry::init('Log');
$indexCheckResult = array();
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$indexCheck = "SELECT INDEX_NAME FROM INFORMATION_SCHEMA.STATISTICS WHERE table_schema=DATABASE() AND table_name='" . $table . "' AND index_name LIKE '" . $field . "%';";
$indexCheckResult = $this->query($indexCheck);
} elseif ($dataSource == 'Database/Postgres') {
@ -1580,7 +1620,7 @@ class AppModel extends Model
$indexCheckResult[] = array('STATISTICS' => array('INDEX_NAME' => $pgIndexName));
}
foreach ($indexCheckResult as $icr) {
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$dropIndex = 'ALTER TABLE ' . $table . ' DROP INDEX ' . $icr['STATISTICS']['INDEX_NAME'] . ';';
} elseif ($dataSource == 'Database/Postgres') {
$dropIndex = 'DROP INDEX IF EXISTS ' . $icr['STATISTICS']['INDEX_NAME'] . ';';
@ -2811,12 +2851,34 @@ class AppModel extends Model
*/
protected function logException($message, Exception $exception, $type = LOG_ERR)
{
$message = sprintf("%s\n[%s] %s",
$message,
get_class($exception),
$exception->getMessage()
);
$message .= "\nStack Trace:\n" . $exception->getTraceAsString();
$message .= "\n";
do {
$message .= sprintf("[%s] %s",
get_class($exception),
$exception->getMessage()
);
$message .= "\nStack Trace:\n" . $exception->getTraceAsString();
$exception = $exception->getPrevious();
} while ($exception !== null);
return $this->log($message, $type);
}
/**
* Generates random file name in tmp dir.
* @return string
*/
protected function tempFileName()
{
return $this->tempDir() . DS . $this->generateRandomFileName();
}
/**
* @return string
*/
protected function tempDir()
{
return Configure::read('MISP.tmpdir') ?: sys_get_temp_dir();
}
}

View File

@ -5,6 +5,7 @@ App::uses('Folder', 'Utility');
App::uses('File', 'Utility');
App::uses('FinancialTool', 'Tools');
App::uses('RandomTool', 'Tools');
App::uses('MalwareTool', 'Tools');
class Attribute extends AppModel
{
@ -698,7 +699,7 @@ class Attribute extends AppModel
* Only recorrelate if:
* - We are dealing with a new attribute OR
* - The existing attribute's previous state is known AND
* value, type or disable correlation have changed
* value, type, disable correlation or distribution have changed
* This will avoid recorrelations when it's not really needed, such as adding a tag
*/
if (!$created) {
@ -706,7 +707,9 @@ class Attribute extends AppModel
empty($this->old) ||
$this->data['Attribute']['value'] != $this->old['Attribute']['value'] ||
$this->data['Attribute']['disable_correlation'] != $this->old['Attribute']['disable_correlation'] ||
$this->data['Attribute']['type'] != $this->old['Attribute']['type']
$this->data['Attribute']['type'] != $this->old['Attribute']['type'] ||
$this->data['Attribute']['distribution'] != $this->old['Attribute']['distribution'] ||
$this->data['Attribute']['sharing_group_id'] != $this->old['Attribute']['sharing_group_id']
) {
$this->__beforeSaveCorrelation($this->data['Attribute']);
$this->__afterSaveCorrelation($this->data['Attribute'], false, $passedEvent);
@ -3348,6 +3351,9 @@ class Attribute extends AppModel
} else {
$options['includeDecayScore'] = true;
}
if ($options['includeDecayScore']) {
$options['includeEventTags'] = true;
}
if (!$user['Role']['perm_sync'] || !isset($options['deleted']) || !$options['deleted']) {
$params['conditions']['AND']['(Attribute.deleted + 0)'] = 0;
} else {
@ -3477,7 +3483,13 @@ class Attribute extends AppModel
if ($options['includeDecayScore']) {
$this->DecayingModel = ClassRegistry::init('DecayingModel');
$include_full_model = isset($options['includeFullModel']) && $options['includeFullModel'] ? 1 : 0;
if (empty($results[$key]['Attribute']['AttributeTag'])) {
$results[$key]['Attribute']['AttributeTag'] = $results[$key]['AttributeTag'];
$results[$key]['Attribute']['EventTag'] = $results[$key]['EventTag'];
}
$results[$key]['Attribute'] = $this->DecayingModel->attachScoresToAttribute($user, $results[$key]['Attribute'], $options['decayingModel'], $options['modelOverrides'], $include_full_model);
unset($results[$key]['Attribute']['AttributeTag']);
unset($results[$key]['Attribute']['EventTag']);
if ($options['excludeDecayed'] && !empty($results[$key]['Attribute']['decay_score'])) { // filter out decayed attribute
$decayed_flag = true;
foreach ($results[$key]['Attribute']['decay_score'] as $decayResult) { // remove attribute if ALL score results in a decay
@ -3558,63 +3570,40 @@ class Attribute extends AppModel
if (!is_numeric($event_id)) {
throw new Exception(__('Something went wrong. Received a non-numeric event ID while trying to create a zip archive of an uploaded malware sample.'));
}
$attachments_dir = Configure::read('MISP.attachments_dir');
if (empty($attachments_dir)) {
$attachments_dir = $this->getDefaultAttachments_dir();
$content = base64_decode($base64);
$malwareTool = new MalwareTool();
$hashes = $malwareTool->computeHashes($content, $hash_types);
try {
$encrypted = $malwareTool->encrypt($original_filename, $content, $hashes['md5']);
} catch (Exception $e) {
$this->logException("Could not create encrypted malware sample.", $e);
return array('success' => false);
}
// If we've set attachments to S3, we can't write there
if ($this->attachmentDirIsS3()) {
$attachments_dir = Configure::read('MISP.tmpdir');
// Sometimes it's not set?
if (empty($attachments_dir)) {
// Get a default tmpdir
$attachments_dir = $this->getDefaultTmp_dir();
}
}
if ($proposal) {
$dir = new Folder($attachments_dir . DS . $event_id . DS . 'shadow', true);
} else {
$dir = new Folder($attachments_dir . DS . $event_id, true);
}
$tmpFile = new File($dir->path . DS . $this->generateRandomFileName(), true, 0600);
$tmpFile->write(base64_decode($base64));
$hashes = array();
foreach ($hash_types as $hash) {
$hashes[$hash] = $this->__hashRouter($hash, $tmpFile->path);
}
$contentsFile = new File($dir->path . DS . $hashes['md5']);
rename($tmpFile->path, $contentsFile->path);
$fileNameFile = new File($dir->path . DS . $hashes['md5'] . '.filename.txt');
$fileNameFile->write($original_filename);
$fileNameFile->close();
$zipFile = new File($dir->path . DS . $hashes['md5'] . '.zip');
exec('zip -j -P infected ' . escapeshellarg($zipFile->path) . ' ' . escapeshellarg($contentsFile->path) . ' ' . escapeshellarg($fileNameFile->path), $execOutput, $execRetval);
if ($execRetval != 0) {
$result = array('success' => false);
} else {
$result = array_merge(array('data' => base64_encode($zipFile->read()), 'success' => true), $hashes);
}
$fileNameFile->delete();
$zipFile->delete();
$contentsFile->delete();
$result = array_merge(array('data' => base64_encode($encrypted), 'success' => true), $hashes);
return $result;
}
private function __hashRouter($hashType, $file)
/**
* @return bool Return true if at least one advanced extraction tool is available
*/
public function isAdvancedExtractionAvailable()
{
$validHashes = array('md5', 'sha1', 'sha256');
if (!in_array($hashType, $validHashes)) {
$malwareTool = new MalwareTool();
try {
$types = $malwareTool->checkAdvancedExtractionStatus($this->getPythonVersion());
} catch (Exception $e) {
return false;
}
switch ($hashType) {
case 'md5':
case 'sha1':
case 'sha256':
return hash_file($hashType, $file);
break;
foreach ($types as $type => $missing) {
if ($missing === false) {
return true;
}
}
return false;
}
@ -3961,7 +3950,7 @@ class Attribute extends AppModel
'event_id' => $event_id,
'comment' => !empty($attribute_settings['comment']) ? $attribute_settings['comment'] : ''
);
$result = $this->Event->Attribute->handleMaliciousBase64($event_id, $filename, base64_encode($tmpfile->read()), $hashes);
$result = $this->handleMaliciousBase64($event_id, $filename, base64_encode($tmpfile->read()), $hashes);
foreach ($attributes as $k => $v) {
$attribute = array(
'distribution' => 5,
@ -3992,33 +3981,34 @@ class Attribute extends AppModel
public function advancedAddMalwareSample($event_id, $attribute_settings, $filename, $tmpfile)
{
$execRetval = '';
$execOutput = array();
$result = shell_exec($this->getPythonVersion() . ' ' . APP . 'files/scripts/generate_file_objects.py -p ' . $tmpfile->path);
if (!empty($result)) {
$result = json_decode($result, true);
if (isset($result['objects'])) {
$result['Object'] = $result['objects'];
unset($result['objects']);
}
if (isset($result['references'])) {
$result['ObjectReference'] = $result['references'];
unset($result['references']);
}
foreach ($result['Object'] as $k => $object) {
$result['Object'][$k]['distribution'] = $attribute_settings['distribution'];
$result['Object'][$k]['sharing_group_id'] = isset($attribute_settings['distribution']) ? $attribute_settings['distribution'] : 0;
if (!empty($result['Object'][$k]['Attribute'])) {
foreach ($result['Object'][$k]['Attribute'] as $k2 => $attribute) {
if ($attribute['value'] == $tmpfile->name) {
$result['Object'][$k]['Attribute'][$k2]['value'] = $filename;
}
$malwareTool = new MalwareTool();
try {
$result = $malwareTool->advancedExtraction($this->getPythonVersion(), $tmpfile->path);
} catch (Exception $e) {
$this->logException("Could not finish advanced extraction", $e);
return $this->simpleAddMalwareSample($event_id, $attribute_settings, $filename, $tmpfile);
}
if (isset($result['objects'])) {
$result['Object'] = $result['objects'];
unset($result['objects']);
}
if (isset($result['references'])) {
$result['ObjectReference'] = $result['references'];
unset($result['references']);
}
foreach ($result['Object'] as $k => $object) {
$result['Object'][$k]['distribution'] = $attribute_settings['distribution'];
$result['Object'][$k]['sharing_group_id'] = isset($attribute_settings['distribution']) ? $attribute_settings['distribution'] : 0;
if (!empty($result['Object'][$k]['Attribute'])) {
foreach ($result['Object'][$k]['Attribute'] as $k2 => $attribute) {
if ($attribute['value'] == $tmpfile->name) {
$result['Object'][$k]['Attribute'][$k2]['value'] = $filename;
}
}
}
} else {
$result = $this->simpleAddMalwareSample($event_id, $attribute_settings, $filename, $tmpfile);
}
return $result;
}
@ -4426,7 +4416,6 @@ class Attribute extends AppModel
'event_timestamp' => array('function' => 'set_filter_timestamp', 'pop' => true),
'publish_timestamp' => array('function' => 'set_filter_timestamp'),
'org' => array('function' => 'set_filter_org'),
'uuid' => array('function' => 'set_filter_uuid'),
'published' => array('function' => 'set_filter_published')
),
'Object' => array(
@ -4487,7 +4476,6 @@ class Attribute extends AppModel
$subqueryElements = $this->Event->harvestSubqueryElements($filters);
$filters = $this->Event->addFiltersFromSubqueryElements($filters, $subqueryElements);
$conditions = $this->buildFilterConditions($user, $filters);
$params = array(
'conditions' => $conditions,
@ -4617,4 +4605,52 @@ class Attribute extends AppModel
}
return true;
}
public function set_filter_uuid(&$params, $conditions, $options)
{
if (!empty($params['uuid'])) {
$params['uuid'] = $this->convert_filters($params['uuid']);
if (!empty($params['uuid']['OR'])) {
$conditions['AND'][] = array(
'OR' => array(
'Event.uuid' => $params['uuid']['OR'],
'Attribute.uuid' => $params['uuid']['OR']
)
);
}
if (!empty($params['uuid']['NOT'])) {
$conditions['AND'][] = array(
'NOT' => array(
'Event.uuid' => $params['uuid']['NOT'],
'Attribute.uuid' => $params['uuid']['NOT']
)
);
}
}
return $conditions;
}
/**
* @param array $attribute
*/
public function removeGalaxyClusterTags(array &$attribute)
{
$galaxyTagIds = array();
foreach ($attribute['Galaxy'] as $galaxy) {
foreach ($galaxy['GalaxyCluster'] as $galaxyCluster) {
$galaxyTagIds[$galaxyCluster['tag_id']] = true;
}
}
if (empty($galaxyTagIds)) {
return;
}
foreach ($attribute['AttributeTag'] as $k => $attributeTag) {
$tagId = $attributeTag['Tag']['id'];
if (isset($galaxyTagIds[$tagId])) {
unset($attribute['AttributeTag'][$k]);
}
}
}
}

View File

@ -39,10 +39,10 @@ class Bruteforce extends AppModel
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
$dataSource = $dataSourceConfig['datasource'];
$expire = date('Y-m-d H:i:s', time());
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$sql = 'DELETE FROM bruteforces WHERE `expire` <= "' . $expire . '";';
} elseif ($dataSource == 'Database/Postgres') {
$sql = 'DELETE FROM bruteforces WHERE expire <= "' . $expire . '";';
$sql = 'DELETE FROM bruteforces WHERE expire <= \'' . $expire . '\';';
}
$this->query($sql);
}

View File

@ -0,0 +1,23 @@
<?php
App::uses('Mysql', 'Model/Datasource/Database');
/*
* Overrides the default MySQL database implementation to prepend all queries with debug comments
* - Lightweight and doesn't affect default operations, like a protoss observer it remains cloaked
* whilst trying to help detect potential bugs burrowed in our queries
*/
class MysqlObserver extends Mysql {
public function execute($sql, $options = array(), $params = array()) {
$comment = sprintf(
'%s%s%s',
empty(Configure::read('CurrentUserId')) ? '' : sprintf(
'[User: %s] ',
intval(Configure::read('CurrentUserId'))
),
empty(Configure::read('CurrentController')) ? '' : preg_replace('/[^a-zA-Z0-9_]/', '', Configure::read('CurrentController')) . ' :: ',
empty(Configure::read('CurrentAction')) ? '' : preg_replace('/[^a-zA-Z0-9_]/', '', Configure::read('CurrentAction'))
);
$sql = '/* ' . $comment . ' */ ' . $sql;
return parent::execute($sql, $options, $params);
}
}

View File

@ -536,7 +536,7 @@ class DecayingModel extends AppModel
'sightings' => $sightings,
'base_score_config' => $base_score_config,
'last_sighting' => $sightings[count($sightings)-1],
'current_score' => $this->Computation->computeCurrentScore($user, $model, $attribute['Attribute'], $base_score, $last_sighting_timestamp),
'current_score' => $this->Computation->computeCurrentScore($user, $model, $attribute['Attribute'], $base_score, $last_sighting_timestamp)['score'],
'Model' => $model['DecayingModel']
);
}
@ -587,9 +587,10 @@ class DecayingModel extends AppModel
$model = $this->overrideModelParameters($model, $model_overrides);
}
$score = $this->getScore($attribute, $model, $user);
$decayed = $this->isDecayed($attribute, $model, $score);
$decayed = $this->isDecayed($attribute, $model, $score['score']);
$to_attach = array(
'score' => $score,
'score' => $score['score'],
'base_score' => $score['base_score'],
'decayed' => $decayed,
'DecayingModel' => array(
'id' => $model['DecayingModel']['id'],
@ -626,7 +627,7 @@ class DecayingModel extends AppModel
public function isDecayed($attribute, $model, $score=false, $user=false)
{
if ($score === false) {
$score = $this->getScore($attribute, $model, $user);
$score = $this->getScore($attribute, $model, $user)['score'];
}
$this->Computation = $this->getModelClass($model);
return $this->Computation->isDecayed($model, $attribute, $score);

View File

@ -24,6 +24,12 @@ abstract class DecayingModelBase
$pieces['predicate'] = $pieces[1];
$pieces['2tag'] = sprintf('%s:%s', $pieces[0], $pieces[1]);
$pieces['base'] = $pieces[0];
} else {
$pieces['complete'] = $tagName;
$pieces['namespace'] = '';
$pieces['predicate'] = '';
$pieces['2tag'] = '';
$pieces['base'] = $tagName;
}
return $pieces;
}
@ -138,7 +144,11 @@ abstract class DecayingModelBase
$last_sighting_timestamp = $attribute['timestamp'];
}
$timestamp = time();
return $this->computeScore($model, $attribute, $base_score, $timestamp - $last_sighting_timestamp);
$scores = array(
'score' => $this->computeScore($model, $attribute, $base_score, $timestamp - $last_sighting_timestamp),
'base_score' => $base_score
);
return $scores;
}
// Compute the score for the provided attribute according to the elapsed time with the provided model

View File

@ -641,6 +641,12 @@ class Event extends AppModel
if (isset($this->data['Event']['info'])) {
$this->Correlation->updateAll(array('Correlation.info' => $db->value($this->data['Event']['info'])), array('Correlation.event_id' => intval($this->data['Event']['id'])));
}
if (isset($this->data['Event']['distribution'])) {
$this->Correlation->updateAll(array('Correlation.distribution' => $db->value($this->data['Event']['distribution'])), array('Correlation.event_id' => intval($this->data['Event']['id'])));
}
if (isset($this->data['Event']['sharing_group_id'])) {
$this->Correlation->updateAll(array('Correlation.sharing_group_id' => $db->value($this->data['Event']['sharing_group_id'])), array('Correlation.event_id' => intval($this->data['Event']['id'])));
}
}
if (empty($this->data['Event']['unpublishAction']) && empty($this->data['Event']['skip_zmq']) && Configure::read('Plugin.ZeroMQ_enable') && Configure::read('Plugin.ZeroMQ_event_notifications_enable')) {
$pubSubTool = $this->getPubSubTool();
@ -1667,7 +1673,6 @@ class Event extends AppModel
'object_relation' => array('function' => 'set_filter_simple_attribute'),
'tags' => array('function' => 'set_filter_tags', 'pop' => true),
'ignore' => array('function' => 'set_filter_ignore'),
'uuid' => array('function' => 'set_filter_uuid'),
'deleted' => array('function' => 'set_filter_deleted'),
'to_ids' => array('function' => 'set_filter_to_ids'),
'comment' => array('function' => 'set_filter_comment')
@ -1707,7 +1712,6 @@ class Event extends AppModel
}
}
}
$fields = array('Event.id');
if (!empty($params['include_attribute_count'])) {
$fields[] = 'Event.attribute_count';
@ -1875,6 +1879,9 @@ class Event extends AppModel
if (!empty($options['includeDecayScore'])) {
$this->DecayingModel = ClassRegistry::init('DecayingModel');
}
if (!isset($options['includeEventCorrelations'])) {
$options['includeEventCorrelations'] = true;
}
foreach ($possibleOptions as &$opt) {
if (!isset($options[$opt])) {
$options[$opt] = false;
@ -2149,6 +2156,22 @@ class Event extends AppModel
'Object' => array('name', 'meta-category')
);
foreach ($results as $eventKey => &$event) {
if ($event['Event']['distribution'] == 4 && !in_array($event['Event']['sharing_group_id'], $sgids)) {
$this->Log = ClassRegistry::init('Log');
$this->Log->create();
$this->Log->save(array(
'org' => $user['Organisation']['name'],
'model' => 'Event',
'model_id' => $event['Event']['id'],
'email' => $user['email'],
'action' => 'fetchEvent',
'user_id' => $user['id'],
'title' => 'User was able to fetch the event but not the sharing_group it belongs to',
'change' => ''
));
unset($results[$eventKey]); // Current user cannot access sharing_group associated to this event
continue;
}
$this->__attachReferences($user, $event, $sgids, $fields);
$event = $this->Orgc->attachOrgsToEvent($event, $fieldsOrg);
if (!$options['sgReferenceOnly'] && $event['Event']['sharing_group_id']) {
@ -2163,7 +2186,9 @@ class Event extends AppModel
}
$event = $this->massageTags($event, 'Event', $options['excludeGalaxy']);
// Let's find all the related events and attach it to the event itself
$results[$eventKey]['RelatedEvent'] = $this->getRelatedEvents($user, $event['Event']['id'], $sgids);
if (!empty($options['includeEventCorrelations'])) {
$results[$eventKey]['RelatedEvent'] = $this->getRelatedEvents($user, $event['Event']['id'], $sgids);
}
// Let's also find all the relations for the attributes - this won't be in the xml export though
if (!empty($options['includeGranularCorrelations'])) {
$results[$eventKey]['RelatedAttribute'] = $this->getRelatedAttributes($user, $event['Event']['id'], $sgids);
@ -2445,7 +2470,11 @@ class Event extends AppModel
}
foreach ($data as $k => $v) {
if ($v['distribution'] == 4) {
$data[$k]['SharingGroup'] = $sharingGroupData[$v['sharing_group_id']]['SharingGroup'];
if (isset($sharingGroupData[$v['sharing_group_id']])) {
$data[$k]['SharingGroup'] = $sharingGroupData[$v['sharing_group_id']]['SharingGroup'];
} else {
unset($data[$k]); // current user could not fetch the sharing_group
}
}
}
return $data;
@ -2585,14 +2614,38 @@ class Event extends AppModel
public function set_filter_uuid(&$params, $conditions, $options)
{
if (!empty($params['uuid'])) {
$params['uuid'] = $this->convert_filters($params['uuid']);
if (!empty($options['scope']) && $options['scope'] === 'Event') {
$conditions = $this->generic_add_filter($conditions, $params['uuid'], 'Event.uuid');
}
if (!empty($options['scope']) && $options['scope'] === 'Attribute') {
$conditions = $this->generic_add_filter($conditions, $params['uuid'], 'Attribute.uuid');
if ($options['scope'] === 'Event') {
if (!empty($params['uuid'])) {
$params['uuid'] = $this->convert_filters($params['uuid']);
if (!empty($params['uuid']['OR'])) {
$subQueryOptions = array(
'conditions' => array('Attribute.uuid' => $params['uuid']['OR']),
'fields' => array('event_id')
);
$attributeSubquery = $this->subQueryGenerator($this->Attribute, $subQueryOptions, 'Event.id');
$conditions['AND'][] = array(
'OR' => array(
'Event.uuid' => $params['uuid']['OR'],
$attributeSubquery
)
);
}
if (!empty($params['uuid']['NOT'])) {
$subQueryOptions = array(
'conditions' => array('Attribute.uuid' => $params['uuid']['NOT']),
'fields' => array('event_id')
);
$attributeSubquery = $this->subQueryGenerator($this->Attribute, $subQueryOptions, 'Event.id');
$conditions['AND'][] = array(
'NOT' => array(
'Event.uuid' => $params['uuid']['NOT'],
$attributeSubquery
)
);
}
}
} else {
$conditions = $this->{$options['scope']}->set_filter_uuid($params, $conditions, $options);
}
return $conditions;
}
@ -2674,6 +2727,11 @@ class Event extends AppModel
{
if (!empty($params[$options['filter']])) {
$params[$options['filter']] = $this->convert_filters($params[$options['filter']]);
if (!empty(Configure::read('MISP.attribute_filters_block_only'))) {
if ($options['context'] === 'Event' && !empty($params[$options['filter']]['OR'])) {
unset($params[$options['filter']]['OR']);
}
}
$conditions = $this->generic_add_filter($conditions, $params[$options['filter']], 'Attribute.' . $options['filter']);
}
return $conditions;
@ -3078,6 +3136,15 @@ class Event extends AppModel
}
$body .= $bodyTempOther; // append the 'other' attribute types to the bottom.
$body .= '==============================================' . "\n";
$body .= sprintf(
"You receive this e-mail because the e-mail address %s is set to receive publish alerts on the MISP instance at %s.%s%s",
$user['email'],
(empty(Configure::read('MISP.external_baseurl')) ? Configure::read('MISP.baseurl') : Configure::read('MISP.external_baseurl')),
PHP_EOL,
PHP_EOL
);
$body .= "If you would like to unsubscribe from receiving such alert e-mails, simply\ndisable publish alerts via " . $this->__getAnnounceBaseurl() . '/users/edit' . PHP_EOL;
$body .= '==============================================' . "\n";
return $body;
}
@ -3196,10 +3263,10 @@ class Event extends AppModel
return array($bodyevent, $body);
}
private function __captureSGForElement($element, $user)
private function __captureSGForElement($element, $user, $syncLocal=false)
{
if (isset($element['SharingGroup'])) {
$sg = $this->SharingGroup->captureSG($element['SharingGroup'], $user);
$sg = $this->SharingGroup->captureSG($element['SharingGroup'], $user, $syncLocal);
unset($element['SharingGroup']);
} elseif (isset($element['sharing_group_id'])) {
$sg = $this->SharingGroup->checkIfAuthorised($user, $element['sharing_group_id']) ? $element['sharing_group_id'] : false;
@ -3216,17 +3283,17 @@ class Event extends AppModel
// When we receive an event via REST, we might end up with organisations, sharing groups, tags that we do not know
// or which we need to update. All of that is controlled in this method.
private function __captureObjects($data, $user)
private function __captureObjects($data, $user, $syncLocal=false)
{
// First we need to check whether the event or any attributes are tied to a sharing group and whether the user is even allowed to create the sharing group / is part of it
if (isset($data['Event']['distribution']) && $data['Event']['distribution'] == 4) {
$data['Event'] = $this->__captureSGForElement($data['Event'], $user);
$data['Event'] = $this->__captureSGForElement($data['Event'], $user, $syncLocal);
}
if (!empty($data['Event']['Attribute'])) {
foreach ($data['Event']['Attribute'] as $k => $a) {
unset($data['Event']['Attribute']['id']);
if (isset($a['distribution']) && $a['distribution'] == 4) {
$data['Event']['Attribute'][$k] = $this->__captureSGForElement($a, $user);
$data['Event']['Attribute'][$k] = $this->__captureSGForElement($a, $user, $syncLocal);
if ($data['Event']['Attribute'][$k] === false) {
unset($data['Event']['Attribute']);
}
@ -3236,7 +3303,7 @@ class Event extends AppModel
if (!empty($data['Event']['Object'])) {
foreach ($data['Event']['Object'] as $k => $o) {
if (isset($o['distribution']) && $o['distribution'] == 4) {
$data['Event']['Object'][$k] = $this->__captureSGForElement($o, $user);
$data['Event']['Object'][$k] = $this->__captureSGForElement($o, $user, $syncLocal);
if ($data['Event']['Object'][$k] === false) {
unset($data['Event']['Object'][$k]);
continue;
@ -3244,7 +3311,7 @@ class Event extends AppModel
}
foreach ($o['Attribute'] as $k2 => $a) {
if (isset($a['distribution']) && $a['distribution'] == 4) {
$data['Event']['Object'][$k]['Attribute'][$k2] = $this->__captureSGForElement($a, $user);
$data['Event']['Object'][$k]['Attribute'][$k2] = $this->__captureSGForElement($a, $user, $syncLocal);
if ($data['Event']['Object'][$k]['Attribute'][$k2] === false) {
unset($data['Event']['Object'][$k]['Attribute'][$k2]);
}
@ -3412,6 +3479,24 @@ class Event extends AppModel
return 'blocked';
}
}
if ($passAlong) {
$this->Server = ClassRegistry::init('Server');
$server = $this->Server->find('first', array(
'conditions' => array(
'Server.id' => $passAlong
),
'recursive' => -1,
'fields' => array(
'Server.name',
'Server.id',
'Server.unpublish_event',
'Server.publish_without_email',
'Server.internal'
)
));
} else {
$server['Server']['internal'] = false;
}
if ($fromXml) {
// Workaround for different structure in XML/array than what CakePHP expects
$data = $this->cleanupEventArrayFromXML($data);
@ -3438,7 +3523,7 @@ class Event extends AppModel
return $existingEvent['Event']['id'];
} else {
if ($fromXml) {
$data = $this->__captureObjects($data, $user);
$data = $this->__captureObjects($data, $user, $server['Server']['internal']);
}
if ($data === false) {
$failedCapture = true;
@ -3446,7 +3531,7 @@ class Event extends AppModel
}
} else {
if ($fromXml) {
$data = $this->__captureObjects($data, $user);
$data = $this->__captureObjects($data, $user, $server['Server']['internal']);
}
if ($data === false) {
$failedCapture = true;
@ -3507,19 +3592,6 @@ class Event extends AppModel
$this->Log = ClassRegistry::init('Log');
if ($saveResult) {
if ($passAlong) {
$this->Server = ClassRegistry::init('Server');
$server = $this->Server->find('first', array(
'conditions' => array(
'Server.id' => $passAlong
),
'recursive' => -1,
'fields' => array(
'Server.name',
'Server.id',
'Server.unpublish_event',
'Server.publish_without_email'
)
));
if ($server['Server']['publish_without_email'] == 0) {
$st = "enabled";
} else {
@ -3662,6 +3734,23 @@ class Event extends AppModel
} else {
$existingEvent = $this->findById($id);
}
if ($passAlong) {
$this->Server = ClassRegistry::init('Server');
$server = $this->Server->find('first', array(
'conditions' => array(
'Server.id' => $passAlong
),
'recursive' => -1,
'fields' => array(
'Server.name',
'Server.id',
'Server.unpublish_event',
'Server.publish_without_email'
)
));
} else {
$server['Server']['internal'] = false;
}
// If the event exists...
$dateObj = new DateTime();
$date = $dateObj->getTimestamp();
@ -3684,7 +3773,7 @@ class Event extends AppModel
return(array('error' => 'Event could not be saved: Invalid sharing group or you don\'t have access to that sharing group.'));
}
} else {
$data['Event']['sharing_group_id'] = $this->SharingGroup->captureSG($data['Event']['SharingGroup'], $user);
$data['Event']['sharing_group_id'] = $this->SharingGroup->captureSG($data['Event']['SharingGroup'], $user, $server['Server']['internal']);
unset($data['Event']['SharingGroup']);
if ($data['Event']['sharing_group_id'] === false) {
return (array('error' => 'Event could not be saved: User not authorised to create the associated sharing group.'));
@ -3805,19 +3894,6 @@ class Event extends AppModel
if ((!empty($data['Event']['published']) && 1 == $data['Event']['published'])) {
// The edited event is from a remote server ?
if ($passAlong) {
$this->Server = ClassRegistry::init('Server');
$server = $this->Server->find('first', array(
'conditions' => array(
'Server.id' => $passAlong
),
'recursive' => -1,
'fields' => array(
'Server.name',
'Server.id',
'Server.unpublish_event',
'Server.publish_without_email'
)
));
if ($server['Server']['publish_without_email'] == 0) {
$st = "enabled";
} else {
@ -5729,7 +5805,8 @@ class Event extends AppModel
}
$shell_command .= ' ' . escapeshellarg(Configure::read('MISP.default_event_distribution')) . ' ' . escapeshellarg(Configure::read('MISP.default_attribute_distribution')) . ' 2>' . APP . 'tmp/logs/exec-errors.log';
$result = shell_exec($shell_command);
$result = end(preg_split("/\r\n|\n|\r/", trim($result)));
$result = preg_split("/\r\n|\n|\r/", trim($result));
$result = end($result);
$tempFile = file_get_contents($tempFilePath);
unlink($tempFilePath);
if (trim($result) == '1') {
@ -6906,4 +6983,27 @@ class Event extends AppModel
}
return $filters;
}
/**
* @param array $event
*/
public function removeGalaxyClusterTags(array &$event)
{
$galaxyTagIds = array();
foreach ($event['Galaxy'] as $galaxy) {
foreach ($galaxy['GalaxyCluster'] as $galaxyCluster) {
$galaxyTagIds[$galaxyCluster['tag_id']] = true;
}
}
if (empty($galaxyTagIds)) {
return;
}
foreach ($event['EventTag'] as $k => $eventTag) {
if (isset($galaxyTagIds[$eventTag['tag_id']])) {
unset($event['EventTag'][$k]);
}
}
}
}

View File

@ -19,6 +19,10 @@ class Feed extends AppModel
'Tag' => array(
'className' => 'Tag',
'foreignKey' => 'tag_id',
),
'Orgc' => array(
'className' => 'Organisation',
'foreignKey' => 'orgc_id'
)
);
@ -31,6 +35,10 @@ class Feed extends AppModel
'event_id' => array(
'rule' => array('numeric'),
'message' => 'Please enter a numeric event ID or leave this field blank.',
),
'input_source' => array(
'rule' => 'validateInputSource',
'message' => ''
)
);
@ -47,6 +55,46 @@ class Feed extends AppModel
)
);
/*
* Cleanup of empty belongsto relationships
*/
public function afterFind($results, $primary = false)
{
foreach ($results as $k => $result) {
if (isset($result['SharingGroup']) && empty($result['SharingGroup']['id'])) {
unset($results[$k]['SharingGroup']);
}
if (isset($result['Tag']) && empty($result['Tag']['id'])) {
unset($results[$k]['Tag']);
}
if (isset($result['Orgc']) && empty($result['Orgc']['id'])) {
unset($results[$k]['Orgc']);
}
}
return $results;
}
public function validateInputSource($fields)
{
if (!empty($this->data['Feed']['input_source'])) {
$localAllowed = empty(Configure::read('Security.disable_local_feed_access'));
$validOptions = array('network');
if ($localAllowed) {
$validOptions[] = 'local';
}
if (!in_array($this->data['Feed']['input_source'], $validOptions)) {
return __(
'Invalid input source. The only valid options are %s. %s',
implode(', ', $validOptions),
(!$localAllowed && $this->data['Feed']['input_source'] === 'local') ?
__('Security.disable_local_feed_access is currently enabled, local feeds are thereby not allowed.') :
''
);
}
}
return true;
}
public function urlOrExistingFilepath($fields)
{
if ($this->isFeedLocal($this->data)) {
@ -199,10 +247,7 @@ class Feed extends AppModel
$data = $this->feedGetUri($feed, $feedUrl, $HttpSocket, true);
if (!$isLocal) {
$redis = $this->setupRedis();
if ($redis === false) {
throw new Exception('Could not reach Redis.');
}
$redis = $this->setupRedisWithException();
$redis->del('misp:feed_cache:' . $feed['Feed']['id']);
file_put_contents($feedCache, $data);
}
@ -454,12 +499,17 @@ class Feed extends AppModel
$result = array(
'header' => array(
'Accept' => array('application/json', 'text/plain'),
'Content-Type' => 'application/json',
'MISP-version' => $version,
'MISP-uuid' => Configure::read('MISP.uuid')
'Accept' => array('application/json', 'text/plain'),
'MISP-version' => $version,
'MISP-uuid' => Configure::read('MISP.uuid'),
)
);
// Enable gzipped responses if PHP has 'gzdecode' method
if (function_exists('gzdecode')) {
$result['header']['Accept-Encoding'] = 'gzip';
}
if ($commit) {
$result['header']['commit'] = $commit;
}
@ -854,11 +904,15 @@ class Feed extends AppModel
}
} else {
$this->Event->create();
$orgc_id = $user['org_id'];
if (!empty($feed['Feed']['orgc_id'])) {
$orgc_id = $feed['Feed']['orgc_id'];
}
$event = array(
'info' => $feed['Feed']['name'] . ' feed',
'analysis' => 2,
'threat_level_id' => 4,
'orgc_id' => $user['org_id'],
'orgc_id' => $orgc_id,
'org_id' => $user['org_id'],
'date' => date('Y-m-d'),
'distribution' => $feed['Feed']['distribution'],
@ -977,14 +1031,14 @@ class Feed extends AppModel
} elseif ($scope == 'freetext' || $scope == 'csv') {
$params['conditions']['source_format'] = array('csv', 'freetext');
} elseif ($scope == 'misp') {
$redis->del('misp:feed_cache:event_uuid_lookup:');
$redis->del($redis->keys('misp:feed_cache:event_uuid_lookup:*'));
$params['conditions']['source_format'] = 'misp';
} else {
throw new InvalidArgumentException("Invalid value for scope, it must be integer or 'freetext', 'csv', 'misp' or 'all' string.");
}
} else {
$redis->del('misp:feed_cache:combined');
$redis->del('misp:feed_cache:event_uuid_lookup:');
$redis->del($redis->keys('misp:feed_cache:event_uuid_lookup:*'));
}
$feeds = $this->find('all', $params);
$atLeastOneSuccess = false;
@ -1162,7 +1216,7 @@ class Feed extends AppModel
'recursive' => -1,
'fields' => array('id', 'url', 'name'),
'contain' => array('RemoteOrg' => array('fields' => array('RemoteOrg.id', 'RemoteOrg.name'))),
'conditions' => array('Server.caching_enabled')
'conditions' => array('Server.caching_enabled' => 1)
));
foreach ($servers as $k => $server) {
if (!$redis->exists('misp:server_cache:' . $server['Server']['id'])) {
@ -1538,24 +1592,51 @@ class Feed extends AppModel
if ($data === false) {
throw new Exception("Could not read local file '$uri'.");
}
return $data;
} else {
throw new Exception("Local file '$uri' doesn't exists.");
}
}
$request = $this->__createFeedRequest($feed['Feed']['headers']);
if ($followRedirect) {
$response = $this->getFollowRedirect($HttpSocket, $uri, $request);
} else {
$request = $this->__createFeedRequest($feed['Feed']['headers']);
$response = $HttpSocket->get($uri, array(), $request);
}
if ($followRedirect) {
$response = $this->getFollowRedirect($HttpSocket, $uri, $request);
} else {
$response = $HttpSocket->get($uri, array(), $request);
}
if ($response === false) {
throw new Exception("Could not reach '$uri'.");
} else if ($response->code != 200) { // intentionally !=
throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}");
}
if ($response === false) {
throw new Exception("Could not reach '$uri'.");
} else if ($response->code != 200) { // intentionally !=
throw new Exception("Fetching the '$uri' failed with HTTP error {$response->code}: {$response->reasonPhrase}");
$data = $response->body;
$contentEncoding = $response->getHeader('Content-Encoding');
if ($contentEncoding === 'gzip') {
$data = gzdecode($data);
if ($data === false) {
throw new Exception("Fetching the '$uri' failed, response should be gzip encoded, but gzip decoding failed.");
}
} else if ($contentEncoding) {
throw new Exception("Fetching the '$uri' failed, because remote server returns unsupported content encoding '$contentEncoding'");
}
$contentType = $response->getHeader('Content-Type');
if ($contentType === 'application/zip') {
$zipFile = new File($this->tempFileName());
$zipFile->write($data);
$zipFile->close();
try {
$data = $this->unzipFirstFile($zipFile);
} catch (Exception $e) {
throw new Exception("Fetching the '$uri' failed: {$e->getMessage()}");
} finally {
$zipFile->delete();
}
$data = $response->body;
}
return $data;
@ -1668,4 +1749,47 @@ class Feed extends AppModel
$this->save($feed);
return $count;
}
/**
* @param File $zipFile
* @return string Uncompressed data
* @throws Exception
*/
private function unzipFirstFile(File $zipFile)
{
if (!class_exists('ZipArchive')) {
throw new Exception("ZIP archive decompressing is not supported.");
}
$zip = new ZipArchive();
$result = $zip->open($zipFile->pwd());
if ($result !== true) {
throw new Exception("Remote server returns ZIP file, that cannot be open (error $result)");
}
if ($zip->numFiles !== 1) {
throw new Exception("Remote server returns ZIP file, that contains multiple files.");
}
$filename = $zip->getNameIndex(0);
if ($filename === false) {
throw new Exception("Remote server returns ZIP file, but there is a problem with reading filename.");
}
$zip->close();
$destinationFile = $this->tempFileName();
$result = copy("zip://{$zipFile->pwd()}#$filename", $destinationFile);
if ($result === false) {
throw new Exception("Remote server returns ZIP file, that contains '$filename' file, that cannot be extracted.");
}
$unzipped = new File($destinationFile);
$data = $unzipped->read();
if ($data === false) {
throw new Exception("Couldn't read extracted file content.");
}
$unzipped->delete();
return $data;
}
}

28
app/Model/Inbox.php Normal file
View File

@ -0,0 +1,28 @@
<?php
App::uses('AppModel', 'Model');
class Inbox extends AppModel
{
public $useTable = 'inbox';
public $recursive = -1;
public $actsAs = array(
'Containable',
);
public $validate = array(
);
public function beforeValidate($options = array())
{
parent::beforeValidate();
$this->data['Inbox']['uuid'] = CakeText::uuid();
$this->data['Inbox']['timestamp'] = time();
$this->data['Inbox']['ip'] = $_SERVER['REMOTE_ADDR'];
$this->data['Inbox']['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$this->data['Inbox']['user_agent_sha256'] = hash('sha256', $_SERVER['HTTP_USER_AGENT']);
return true;
}
}

View File

@ -21,6 +21,7 @@ class Log extends AppModel
array( // ensure that the length of the rules is < 20 in length
'accept',
'accept_delegation',
'acceptRegistrations',
'add',
'admin_email',
'auth',
@ -30,11 +31,13 @@ class Log extends AppModel
'delete',
'disable',
'discard',
'discardRegistrations',
'edit',
'email',
'enable',
'error',
'export',
'failed_registration',
'file_upload',
'galaxy',
'include_formula',
@ -55,6 +58,7 @@ class Log extends AppModel
'reset_auth_key',
'security',
'serverSettingsEdit',
'succeeded_registration',
'tag',
'undelete',
'update',
@ -92,18 +96,6 @@ class Log extends AppModel
'email' => array('values' => array('admin_email'))
);
public function beforeValidete()
{
parent::beforeValidate();
if (!isset($this->data['Log']['org']) || empty($this->data['Log']['org'])) {
$this->data['Log']['org'] = 'SYSTEM';
}
// truncate the description if it would exceed the allowed size in mysql
if (!empty($this->data['Log']['description'] && strlen($this->data['Log']['description']) > 65536)) {
$this->data['Log']['description'] = substr($this->data['Log']['description'], 0, 65535);
}
}
public function beforeSave($options = array())
{
if (!empty(Configure::read('MISP.log_skip_db_logs_completely'))) {
@ -121,7 +113,7 @@ class Log extends AppModel
if (!isset($this->data['Log']['created'])) {
$this->data['Log']['created'] = date('Y-m-d H:i:s');
}
if (!isset($this->data['Log']['org'])) {
if (!isset($this->data['Log']['org']) || empty($this->data['Log']['org'])) {
$this->data['Log']['org'] = 'SYSTEM';
}
$truncate_fields = array('title', 'change', 'description');
@ -151,7 +143,7 @@ class Log extends AppModel
$conditions['org'] = $org['Organisation']['name'];
}
$conditions['AND']['NOT'] = array('action' => array('login', 'logout', 'changepw'));
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$validDates = $this->find('all', array(
'fields' => array('DISTINCT UNIX_TIMESTAMP(DATE(created)) AS Date', 'count(id) AS count'),
'conditions' => $conditions,
@ -323,7 +315,6 @@ class Log extends AppModel
$elasticSearchClient = $this->getElasticSearchTool();
$elasticSearchClient->pushDocument($logIndex, "log", $data);
}
if (Configure::read('Security.syslog')) {
// write to syslogd as well
$syslog = new SysLog();
@ -338,8 +329,17 @@ class Log extends AppModel
}
$entry = $data['Log']['action'];
if (!empty($data['Log']['title'])) {
$entry .= sprintf(
' -- %s',
$data['Log']['title']
);
}
if (!empty($data['Log']['description'])) {
$entry .= sprintf(' -- %s', $data['Log']['description']);
$entry .= sprintf(
' -- %s',
$data['Log']['description']
);
}
$syslog->write($action, $entry);
}

View File

@ -1234,7 +1234,7 @@ class MispObject extends AppModel
return $toReturn;
}
public function reviseObject($revised_object, $object) {
public function reviseObject($revised_object, $object, $template) {
$revised_object = json_decode(base64_decode($revised_object), true);
$revised_object_both = array('mergeable' => array(), 'notMergeable' => array());

View File

@ -292,7 +292,7 @@ class Organisation extends AppModel
$success = true;
foreach ($this->organisationAssociations as $model => $data) {
foreach ($data['fields'] as $field) {
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$sql = 'SELECT `id` FROM `' . $data['table'] . '` WHERE `' . $field . '` = "' . $currentOrg['Organisation']['id'] . '"';
} elseif ($dataSource == 'Database/Postgres') {
$sql = 'SELECT "id" FROM "' . $data['table'] . '" WHERE "' . $field . '" = "' . $currentOrg['Organisation']['id'] . '"';
@ -303,13 +303,13 @@ class Organisation extends AppModel
if (!empty($dataMoved['values_changed'][$model][$field])) {
$this->Log->create();
try {
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$sql = 'UPDATE `' . $data['table'] . '` SET `' . $field . '` = ' . $targetOrg['Organisation']['id'] . ' WHERE `' . $field . '` = ' . $currentOrg['Organisation']['id'] . ';';
} elseif ($dataSource == 'Database/Postgres') {
$sql = 'UPDATE "' . $data['table'] . '" SET "' . $field . '" = ' . $targetOrg['Organisation']['id'] . ' WHERE "' . $field . '" = ' . $currentOrg['Organisation']['id'] . ';';
}
$result = $this->query($sql);
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$sql = 'UPDATE `' . $data['table'] . '` SET `' . $field . '` = ' . $currentOrg['Organisation']['id'] . ' WHERE `id` IN (' . implode(',', $dataMoved['values_changed'][$model][$field]) . ');';
} elseif ($dataSource == 'Database/Postgres') {
$sql = 'UPDATE "' . $data['table'] . '" SET "' . $field . '" = ' . $currentOrg['Organisation']['id'] . ' WHERE "id" IN (' . implode(',', $dataMoved['values_changed'][$model][$field]) . ');';
@ -428,4 +428,32 @@ class Organisation extends AppModel
}
return array_values($orgIds);
}
public function checkDesiredOrg($suggestedOrg, $registration)
{
if ($suggestedOrg !== false && $suggestedOrg !== -1) {
$conditions = array();
if (!empty($registration['Inbox']['data']['org_uuid'])) {
$conditions = array('Organisation.uuid' => $registration['Inbox']['data']['org_uuid']);
} else if (!empty($registration['Inbox']['data']['org_name'])) {
$conditions = array('Organisation.name' => $registration['Inbox']['data']['org_name']);
} else {
$domain = explode('@', $registration['Inbox']['data']['email'])[1];
$conditions = array('LOWER(Organisation.name)' => strtolower($domain));
}
$identifiedOrg = $this->User->Organisation->find('first', array(
'recursive' => -1,
'fields' => array('id', 'name', 'local'),
'conditions' => $conditions
));
if (empty($identifiedOrg)) {
$suggestedOrg = -1;
} else if (!empty($suggestedOrg) && $suggestedOrg[0] !== $identifiedOrg['Organisation']['id']) {
$suggestedOrg = false;
} else {
$suggestedOrg = array($identifiedOrg['Organisation']['id'], $identifiedOrg['Organisation']['name'], $identifiedOrg['Organisation']['local']);
}
}
return $suggestedOrg;
}
}

View File

@ -254,4 +254,34 @@ class Role extends AppModel
}
return true;
}
/*
* Helper function to find out if a list of registrations has the same role requirements
* If no role requirements have been passed yet, null is assumed for $suggestedRole
* Returns an array with the permission flags required
*/
public function checkDesiredRole($suggestedRole, $registration)
{
if ($suggestedRole !== false) {
$currentRole = array();
$roleFlags = array('perm_publish', 'perm_admin', 'perm_sync');
foreach ($roleFlags as $roleFlag) {
if (isset($registration['Inbox']['data'][$roleFlag])) {
$currentRole[$roleFlag] = $registration['Inbox']['data'][$roleFlag];
}
}
if ($suggestedRole !== null) {
if (count($suggestedRole) != count($currentRole) || !empty(array_diff_key($suggestedRole, $currentRole))) {
return false;
}
foreach (array_keys($currentRole) as $perm) {
if ($currentRole[$perm] !== $suggestedRole[$perm]) {
return false;
}
}
}
return $currentRole;
}
return $suggestedRole;
}
}

View File

@ -512,7 +512,7 @@ class Server extends AppModel
'description' => __('Enables the use of MISP\'s background processing.'),
'value' => '',
'errorMessage' => '',
'test' => 'testBool',
'test' => 'testBoolTrue',
'type' => 'boolean',
),
'attachments_dir' => array(
@ -642,6 +642,15 @@ class Server extends AppModel
'type' => 'numeric',
'optionsSource' => 'TagCollections',
),
'default_publish_alert' => array(
'level' => 0,
'description' => __('The default setting for publish alerts when creating users.'),
'value' => true,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean',
'null' => true
),
'tagging' => array(
'level' => 1,
'description' => __('Enable the tagging feature of MISP. This is highly recommended.'),
@ -1063,6 +1072,15 @@ class Server extends AppModel
'test' => 'testForNumeric',
'type' => 'numeric',
'null' => true
),
'attribute_filters_block_only' => array(
'level' => 1,
'description' => __('This is a performance tweak to change the behaviour of restSearch to use attribute filters solely for blocking. This means that a lookup on the event scope with for example the type field set will be ignored unless it\'s used to strip unwanted attributes from the results. If left disabled, passing [ip-src, ip-dst] for example will return any event with at least one ip-src or ip-dst attribute. This is generally not considered to be too useful and is a heavy burden on the database.'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean',
'null' => true
)
),
'GnuPG' => array(
@ -1243,6 +1261,72 @@ class Server extends AppModel
'type' => 'boolean',
'null' => true
),
'email_otp_enabled' => array(
'level'=> 2,
'description' => __('Enable two step authentication with a OTP sent by email. Requires e-mailing to be enabled. Warning: You cannot use it in combination with external authentication plugins.'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'beforeHook' => 'otpBeforeHook',
'type' => 'boolean',
'null' => true
),
'email_otp_length' => array (
'level' => 2,
'description' => __('Define the length of the OTP code sent by email'),
'value' => '6',
'errorMessage' => '',
'type' => 'numeric',
'test' => 'testForNumeric',
'null' => true,
),
'email_otp_validity' => array (
'level' => 2,
'description' => __('Define the validity (in minutes) of the OTP code sent by email'),
'value' => '5',
'errorMessage' => '',
'type' => 'numeric',
'test' => 'testForNumeric',
'null' => true,
),
'email_otp_text' => array(
'level' => 2,
'bigField' => true,
'description' => __('The message sent to the user when a new OTP is requested. Use \\n for line-breaks. The following variables will be automatically replaced in the text: $otp = the new OTP generated by MISP, $username = the user\'s e-mail address, $org the Organisation managing the instance, $misp = the url of this instance, $contact = the e-mail address used to contact the support team (as set in MISP.contact), $ip the IP used to complete the first step of the login and $validity the validity time in minutes.'),
'value' => 'Dear MISP user,\n\nYou have attempted to login to MISP ($misp) from $ip with username $username.\n\n Use the following OTP to log into MISP: $otp\n This code is valid for the next $validity minutes.\n\nIf you have any questions, don\'t hesitate to contact us at: $contact.\n\nBest regards,\nYour $org MISP support team',
'errorMessage' => '',
'test' => 'testForEmpty',
'type' => 'string',
'null' => true,
),
'email_otp_exceptions' => array(
'level' => 2,
'bigField' => true,
'description' => __('A comma separated list of emails for which the OTP is disabled. Note that if you remove someone from this list, the OTP will only be asked at next login.'),
'value' => '',
'errorMessage' => '',
'test' => 'testForEmpty',
'type' => 'string',
'null' => true,
),
'allow_self_registration' => array(
'level' => 1,
'description' => __('Enabling this setting will allow users to have access to the pre-auth registration form. This will create an inbox entry for administrators to review.'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean',
'null' => true
),
'self_registration_message' => array(
'level' => 1,
'bigField' => true,
'description' => __('The message sent shown to anyone trying to self-register.'),
'value' => 'If you would like to send us a registration request, please fill out the form below. Make sure you fill out as much information as possible in order to ease the task of the administrators.',
'errorMessage' => '',
'test' => false,
'type' => 'string'
),
'password_policy_length' => array(
'level' => 2,
'description' => __('Password length requirement. If it is not set or it is set to 0, then the default value is assumed (12).'),
@ -1286,6 +1370,16 @@ class Server extends AppModel
'type' => 'boolean',
'null' => true
),
'disable_local_feed_access' => array(
'level' => 0,
'description' => __('Disabling this setting will allow the creation/modification of local feeds (as opposed to network feeds). Enabling this setting will restrict feed sources to be network based only. When disabled, keep in mind that a malicious site administrator could get access to any arbitrary file on the system that the apache user has access to. Make sure that proper safe-guards are in place. This setting can only be modified via the CLI.'),
'value' => false,
'errorMessage' => '',
'test' => 'testBool',
'type' => 'boolean',
'null' => true,
'cli_only' => 1
),
'allow_unsafe_apikey_named_param' => array(
'level' => 0,
'description' => __('Allows passing the API key via the named url parameter "apikey" - highly recommended not to enable this, but if you have some dodgy legacy tools that cannot pass the authorization header it can work as a workaround. Again, only use this as a last resort.'),
@ -2617,7 +2711,6 @@ class Server extends AppModel
}
}
if (!empty($temp)) {
$temp = implode('|', $temp);
$final[substr($field, 0, strlen($field) -1)] = $temp;
}
}
@ -2628,6 +2721,23 @@ class Server extends AppModel
return $final;
}
private function __orgRuleDowngrade($HttpSocket, $request, $server, $filter_rules)
{
$uri = $server['Server']['url'] . '/servers/getVersion';
try {
$version_response = $HttpSocket->get($uri, false, $request);
$body = $version_response->body;
$version_response = json_decode($body, true);
$version = $version_response['version'];
} catch (Exception $e) {
return $e->getMessage();
}
$version = explode('.', $version);
if ($version[0] <= 2 && $version[1] <= 4 && $version[0] <= 123) {
$filter_rules['org'] = implode('|', $filter_rules['org']);
}
return $filter_rules;
}
// Get an array of event_ids that are present on the remote server
public function getEventIdsFromServer($server, $all = false, $HttpSocket=null, $force_uuid=false, $ignoreFilterRules = false, $scope = 'events')
@ -2640,6 +2750,9 @@ class Server extends AppModel
}
$HttpSocket = $this->setupHttpSocket($server, $HttpSocket);
$request = $this->setupSyncRequest($server);
if (!empty($filter_rules['org'])) {
$filter_rules = $this->__orgRuleDowngrade($HttpSocket, $request, $server, $filter_rules);
}
$uri = $url . '/events/index';
$filter_rules['minimal'] = 1;
$filter_rules['published'] = 1;
@ -2730,6 +2843,7 @@ class Server extends AppModel
} catch (SocketException $e) {
return $e->getMessage();
}
// error, so return error message, since that is handled and everything is expecting an array
return "Error: got response code " . $response->code;
}
@ -3047,15 +3161,6 @@ class Server extends AppModel
private function readModuleSettings($serverSettings, $moduleTypes)
{
$this->Module = ClassRegistry::init('Module');
$orgs = $this->Organisation->find('list', array(
'conditions' => array(
'Organisation.local' => 1
),
'fields' => array(
'Organisation.id', 'Organisation.name'
)
));
$orgs = array_merge(array('Unrestricted'), $orgs);
foreach ($moduleTypes as $moduleType) {
if (Configure::read('Plugin.' . $moduleType . '_services_enable')) {
$results = $this->Module->getModuleSettings($moduleType);
@ -3484,11 +3589,26 @@ class Server extends AppModel
if ($errorMessage) {
return $errorMessage;
}
return 'Value is not a boolean, make sure that you convert \'true\' to true for example.';
return __('Value is not a boolean, make sure that you convert \'true\' to true for example.');
}
return true;
}
public function testBoolTrue($value, $errorMessage = false)
{
if ($this->testBool($value, $errorMessage) !== true) {
return $this->testBool($value, $errorMessage);
}
if ($value === false) {
if ($errorMessage) {
return $errorMessage;
}
return 'It is highly recommended that this setting is enabled. Make sure you understand the impact of having this setting turned off.';
} else {
return true;
}
}
public function testBoolFalse($value, $errorMessage = false)
{
if ($this->testBool($value, $errorMessage) !== true) {
@ -3659,6 +3779,14 @@ class Server extends AppModel
return true;
}
public function otpBeforeHook($setting, $value)
{
if ($value && !empty(Configure::read('MISP.disable_emailing'))) {
return __('Emailing is currently disabled. Enabling OTP without e-mailing being configured would lock all users out.');
}
return true;
}
public function testForRPZSerial($value)
{
if ($this->testForEmpty($value) !== true) {
@ -3820,7 +3948,6 @@ class Server extends AppModel
} else {
$serverSettings = $this->serverSettings;
}
$relevantSettings = (array_intersect_key(Configure::read(), $serverSettings));
$setting = false;
foreach ($serverSettings as $k => $s) {
if (isset($s['branch'])) {
@ -4339,7 +4466,7 @@ class Server extends AppModel
public function dbSpaceUsage()
{
$dataSource = $this->getDataSource()->config['datasource'];
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$sql = sprintf(
'select TABLE_NAME, sum((DATA_LENGTH+INDEX_LENGTH)/1024/1024) AS used, sum(DATA_FREE)/1024/1024 AS reclaimable from information_schema.tables where table_schema = %s group by TABLE_NAME;',
"'" . $this->getDataSource()->config['database'] . "'"
@ -4415,7 +4542,7 @@ class Server extends AppModel
'update_fail_number_reached' => $this->UpdateFailNumberReached(),
'indexes' => array()
);
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$dbActualSchema = $this->getActualDBSchema();
$dbExpectedSchema = $this->getExpectedDBSchema();
if ($dbExpectedSchema !== false) {
@ -4457,7 +4584,10 @@ class Server extends AppModel
if (isset($field['error_type'])) {
$length = false;
if (in_array($field['error_type'], array('missing_column', 'column_different'))) {
if ($field['expected']['data_type'] === 'int') {
preg_match('/([a-z]+)(?:\((?<dw>[0-9,]+)\))?\s*([a-z]+)?/i', $field['expected']['column_type'], $displayWidthMatches);
if (isset($displayWidthMatches['dw'])) {
$length = $displayWidthMatches[2];
} elseif ($field['expected']['data_type'] === 'int') {
$length = 11;
} elseif ($field['expected']['data_type'] === 'tinyint') {
$length = 1;
@ -4476,7 +4606,7 @@ class Server extends AppModel
$field['column_name'],
$field['expected']['data_type'],
$length !== null ? sprintf('(%d)', $length) : '',
isset($field['expected']['column_default']) ? $field['expected']['column_default'] . '"' : '',
isset($field['expected']['column_default']) ? 'DEFAULT "' . $field['expected']['column_default'] . '"' : '',
$field['expected']['is_nullable'] === 'NO' ? 'NOT NULL' : 'NULL',
empty($field['expected']['collation_name']) ? '' : 'COLLATE ' . $field['expected']['collation_name']
);
@ -4563,13 +4693,14 @@ class Server extends AppModel
'numeric_precision',
// 'datetime_precision', -- Only available on MySQL 5.6+
'collation_name',
'column_type',
'column_default'
)
){
$dbActualSchema = array();
$dbActualIndexes = array();
$dataSource = $this->getDataSource()->config['datasource'];
if ($dataSource == 'Database/Mysql') {
if ($dataSource == 'Database/Mysql' || $dataSource == 'Database/MysqlObserver') {
$sqlGetTable = sprintf('SELECT TABLE_NAME FROM information_schema.tables WHERE table_schema = %s;', "'" . $this->getDataSource()->config['database'] . "'");
$sqlResult = $this->query($sqlGetTable);
$tables = HASH::extract($sqlResult, '{n}.tables.TABLE_NAME');
@ -4719,7 +4850,7 @@ class Server extends AppModel
} else {
$keyLength = '';
}
$sql = sprintf('CREATE INDEX `%s` ON `%s` (%s%s);',
$sql = sprintf('CREATE INDEX `%s` ON `%s` (`%s`%s);',
$columnDiff,
$tableName,
$columnDiff,
@ -5667,7 +5798,7 @@ class Server extends AppModel
$params['conditions']['Server.id'] = $id;
} else {
$redis->del('misp:server_cache:combined');
$redis->del('misp:server_cache:event_uuid_lookup:');
$redis->del($redis->keys('misp:server_cache:event_uuid_lookup:*'));
}
$servers = $this->find('all', $params);
if ($jobId) {

View File

@ -54,6 +54,10 @@ class SharingGroup extends AppModel
);
private $__sgoCache = array();
private $__sgAuthorisationCache = array(
'save' => array(),
'access' => array()
);
public function beforeValidate($options = array())
@ -353,6 +357,9 @@ class SharingGroup extends AppModel
// returns true if the SG exists and the user is allowed to see it
public function checkIfAuthorised($user, $id, $adminCheck = true)
{
if (isset($this->__sgAuthorisationCache['access'][boolval($adminCheck)][$id])) {
return $this->__sgAuthorisationCache['access'][boolval($adminCheck)][$id];
}
if (Validation::uuid($id)) {
$sgid = $this->SharingGroup->find('first', array(
'conditions' => array('SharingGroup.uuid' => $id),
@ -372,8 +379,10 @@ class SharingGroup extends AppModel
return false;
}
if (($adminCheck && $user['Role']['perm_site_admin']) || $this->SharingGroupServer->checkIfAuthorised($id) || $this->SharingGroupOrg->checkIfAuthorised($id, $user['org_id'])) {
$this->__sgAuthorisationCache['access'][boolval($adminCheck)][$id] = true;
return true;
}
$this->__sgAuthorisationCache['access'][boolval($adminCheck)][$id] = false;
return false;
}
@ -485,7 +494,7 @@ class SharingGroup extends AppModel
return $results;
}
public function captureSG($sg, $user)
public function captureSG($sg, $user, $syncLocal=false)
{
$existingSG = !isset($sg['uuid']) ? null : $this->find('first', array(
'recursive' => -1,
@ -501,6 +510,34 @@ class SharingGroup extends AppModel
if (!$user['Role']['perm_sharing_group']) {
return false;
}
// check if current user is contained in the SG and we are in a local sync setup
if (!empty($sg['uuid'])) {
if (isset($this->__sgAuthorisationCache['save'][boolval($syncLocal)][$sg['uuid']])) {
$authorisedToSave = $this->__sgAuthorisationCache['save'][boolval($syncLocal)][$sg['uuid']];
} else {
$authorisedToSave = $this->checkIfAuthorisedToSave($user, $sg);
$this->__sgAuthorisationCache['save'][boolval($syncLocal)][$sg['uuid']] = $authorisedToSave;
}
} else {
$authorisedToSave = $this->checkIfAuthorisedToSave($user, $sg);
}
if (!$user['Role']['perm_site_admin'] &&
!($user['Role']['perm_sync'] && $syncLocal ) &&
!$authorisedToSave
) {
$this->Log->create();
$entry = array(
'org' => $user['Organisation']['name'],
'model' => 'SharingGroup',
'model_id' => $sg['SharingGroup']['uuid'],
'email' => $user['email'],
'action' => 'error',
'user_id' => $user['id'],
'title' => 'Tried to save a sharing group but the user does not belong to it.'
);
$this->Log->save($entry);
return false;
}
$this->create();
$newSG = array();
$attributes = array(

View File

@ -1502,4 +1502,68 @@ class User extends AppModel
}
Configure::write('Security.monitored', 0);
}
public function registerUser($added_by, $registration, $org_id, $role_id) {
$user = array(
'email' => $registration['data']['email'],
'gpgkey' => empty($registration['data']['pgp']) ? '' : $registration['data']['pgp'],
'disabled' => 0,
'newsread' => 0,
'change_pw' => 1,
'authkey' => $this->generateAuthKey(),
'termsaccepted' => 0,
'org_id' => $org_id,
'role_id' => $role_id,
'invited_by' => $added_by['id'],
'contactalert' => 1,
'autoalert' => Configure::check('MISP.default_publish_alert') ? Configure::read('MISP.default_publish_alert') : 1
);
$this->create();
$this->Log = ClassRegistry::init('Log');
$result = $this->save(array('User' => $user));
$currentOrg = $this->Organisation->find('first', array(
'recursive' => -1,
'conditions' => array('Organisation.id' => $org_id)
));
if (!empty($currentOrg) && empty($currentOrg['Organisation']['local'])) {
$currentOrg['Organisation']['local'] = 1;
$this->Organisation->save($currentOrg);
}
if (empty($result)) {
$error = array();
foreach ($this->validationErrors as $key => $errors) {
$error[$key] = $key . ': ' . implode(', ', $errors);
}
$error = implode(PHP_EOL, $error);
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => $added_by['id'],
'email' => $added_by['email'],
'action' => 'failed_registration',
'title' => 'User registration failed for ' . $user['email'] . '. Reason(s): ' . $error,
'change' => null,
));
return false;
} else {
$user = $this->find('first', array(
'recursive' => -1,
'conditions' => array('id' => $this->id)
));
$this->Log->save(array(
'org' => 'SYSTEM',
'model' => 'User',
'model_id' => $added_by['id'],
'email' => $added_by['email'],
'action' => 'succeeded_registration',
'title' => sprintf('User registration success for %s (id=%s)', $user['User']['email'], $user['User']['id']),
'change' => null,
));
$this->initiatePasswordReset($user, true, true, false);
$this->Inbox = ClassRegistry::init('Inbox');
$this->Inbox->delete($registration['id']);
return true;
}
}
}

View File

@ -68,10 +68,10 @@ class SysLog {
} else if (in_array($type, $debugTypes)) {
$priority = LOG_DEBUG;
}
$output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message . "\n";
if (!openlog($this->_ident, LOG_PID | LOG_PERROR, $this->_facility)) {
return false;
}
$output = date('Y-m-d H:i:s') . ' ' . ucfirst($type) . ': ' . $message;
$result = syslog($priority, $output);
closelog();
return $result;

View File

@ -122,6 +122,7 @@
$('#AttributeCategory').change(function() {
formCategoryChanged('Attribute');
$('#AttributeType').chosen('destroy').chosen();
if ($(this).val() === 'Internal reference') {
$("#AttributeDistribution").val('0');
checkSharingGroup('Attribute');
@ -166,6 +167,16 @@
}
}
});
<?php if (!$ajax): ?>
$('#AttributeType').chosen();
$('#AttributeCategory').chosen();
<?php else: ?>
$('#genericModal').on('shown', function() {
$('#AttributeType').chosen();
$('#AttributeCategory').chosen();
})
<?php endif; ?>
});
</script>
<?php echo $this->element('form_seen_input'); ?>

View File

@ -67,9 +67,11 @@
<?php
echo $this->Form->input('advanced', array(
'type' => 'checkbox',
'checked' => false,
'checked' => true,
'disabled' => !$advancedExtractionAvailable,
'data-disabled-reason' => !$advancedExtractionAvailable ? __('Advanced extraction is not installed') : '',
'div' => array('id' => 'advanced_input', 'style' => 'display:none'),
'label' => __('Advanced extraction (if installed)'),
'label' => __('Advanced extraction'),
));
?>
</fieldset>
@ -131,7 +133,7 @@ $(document).ready(function() {
$("#AttributeCategory, #AttributeDistribution").change(function() {
initPopoverContent('Attribute');
});
$("#AttributeMalware").change(function () {
if (this.checked) {
$('#advanced_input').show();

View File

@ -33,6 +33,11 @@
'label' => __('For Intrusion Detection System'),
'selected' => 2,
));
echo $this->Form->input('is_proposal', array(
'type' => 'checkbox',
'label' => __('Create proposals'),
'checked' => true
));
?>
<div class="input clear"></div>

View File

@ -58,6 +58,14 @@
array(
'name' => __('Description'),
'data_path' => 'description',
),
array(
'name' => __('Self-reg'),
'element' => 'self_registration',
'class' => 'short',
'title' => __('This community allows for self-registration'),
'data_path' => 'url',
'data_path_requirement' => 'self_registration'
)
),
'title' => __('Communities index'),

View File

@ -14,7 +14,7 @@
$tr_class .= ' row_' . h($k);
}
?>
<tr id = "Object_<?php echo $object['id']; ?>_tr" class="<?php echo $tr_class; ?>" tabindex="0">
<tr id="Object_<?php echo $object['id']; ?>_tr" class="<?php echo $tr_class; ?>" tabindex="0">
<?php
if ($mayModify || $extended):
?>
@ -22,7 +22,7 @@
<?php
if ($mayModify):
?>
<input id = "select_object_<?php echo $object['id']; ?>" class="select_object row_checkbox" type="checkbox" data-id="<?php echo $object['id'];?>" />
<input id="select_object_<?php echo $object['id']; ?>" class="select_object row_checkbox" type="checkbox" data-id="<?php echo $object['id'];?>" />
<?php
endif;
?>
@ -64,7 +64,7 @@
?>
&nbsp;
</td>
<td colspan="5">
<td colspan="<?= $includeRelatedTags ? 6 : 5 ?>">
<span class="bold"><?php echo __('Name: ');?></span><?php echo h($object['name']);?>
<span class="fa fa-expand useCursorPointer" title="<?php echo __('Expand or Collapse');?>" role="button" tabindex="0" aria-label="<?php echo __('Expand or Collapse');?>" data-toggle="collapse" data-target="#Object_<?php echo h($object['id']); ?>_collapsible"></span>
<br />
@ -89,8 +89,8 @@
?>
</td>
<td class="showspaces bitwider" onmouseenter="quickEditHover(this, 'Object', '<?php echo $object['id']; ?>', 'comment', <?php echo $event['Event']['id'];?>);">
<div id = "Object_<?php echo $object['id']; ?>_comment_placeholder" class = "inline-field-placeholder"></div>
<div id = "Object_<?php echo $object['id']; ?>_comment_solid" class="inline-field-solid">
<div id="Object_<?php echo $object['id']; ?>_comment_placeholder" class="inline-field-placeholder"></div>
<div id="Object_<?php echo $object['id']; ?>_comment_solid" class="inline-field-solid">
<?php echo nl2br(h($object['comment'])); ?>&nbsp;
</div>
</td>
@ -101,8 +101,8 @@
$turnRed = '';
if ($object['objectType'] == 0 && $object['distribution'] == 0) $turnRed = 'style="color:red"';
?>
<div id = "<?php echo $currentType . '_' . $object['id'] . '_distribution_placeholder'; ?>" class = "inline-field-placeholder"></div>
<div id = "<?php echo $currentType . '_' . $object['id'] . '_distribution_solid'; ?>" <?php echo $turnRed; ?> class="inline-field-solid">
<div id="<?php echo $currentType . '_' . $object['id'] . '_distribution_placeholder'; ?>" class="inline-field-placeholder"></div>
<div id="<?php echo $currentType . '_' . $object['id'] . '_distribution_solid'; ?>" <?php echo $turnRed; ?> class="inline-field-solid">
<?php
if ($object['objectType'] == 0) {
if ($object['distribution'] == 4):
@ -163,4 +163,4 @@
}
echo '<tr class="objectAddFieldTr"><td><span class="fa fa-plus-circle objectAddField" title="' . __('Add an Object Attribute') .'" onclick="popoverPopup(this, ' . h($object['id']) . ', \'objects\', \'quickFetchTemplateWithValidObjectAttributes\')"></span></td></tr>';
}
?>

View File

@ -2,9 +2,9 @@
if (empty($scope)) {
$scope = 'event';
}
$searchUrl = '/events/index/searchtag:';
switch ($scope) {
case 'event':
$searchUrl = '/events/index/searchtag:';
$id = h($event['Event']['id']);
if (!empty($required_taxonomies)) {
foreach ($required_taxonomies as $k => $v) {

View File

@ -10,16 +10,26 @@
<div id="chartContainer-<?= $seed ?>" style="flex-grow: 1; position:relative;"></div>
<script>
if (typeof d3 === "undefined") { // load d3.js once. This is necessary as d3.js is using global variables for its event listeners (d3.mouse & d3.event)
d3 = 'loading';
$.getScript("/js/d3.js", function() {
init();
init<?= $seed ?>();
})
} else { // d3.js is already loaded
init();
} else { // d3.js is already loaded or is loading
runInitWhenReady<?= $seed ?>()
}
function init() { // variables and functions have their own scope (no override)
'use strict';
function runInitWhenReady<?= $seed ?>() {
if (d3.version === undefined) { // d3.js not loaded yet
setTimeout(function() {
runInitWhenReady<?= $seed ?>();
}, 50);
} else {
init<?= $seed ?>();
}
}
function init<?= $seed ?>() { // variables and functions have their own scope (no override)
'use strict';
/**
*
* Data expected format: Array({
@ -59,10 +69,13 @@ function init() { // variables and functions have their own scope (no override)
var svg;
var width, height, svg_width, svg_height;
var xAxis, yAxis, cursorX, cursorY;
var x, y, xGrid, yGrid, value_line
var series, line_guides, points, pointsGroup, labels
var x, y, xGrid, yGrid, value_line;
var overlayLeft, overlayRight, tooltipPickedNodes;
var series, line_guides, points, pointsGroup, labels;
var colors = d3.scale.category10();
var pickedNodes = {start: null, end: null};
var options = <?= json_encode(isset($config['widget_config']) ? $config['widget_config'] : array()) ?>;
var options = $.extend(true, {}, default_options, options);
options = _validateOptions(options);
@ -106,6 +119,10 @@ function init() { // variables and functions have their own scope (no override)
})
}
function getX(datum) {
return options.abscissa_linear ? datum.index : datum.date;
}
function _init() {
$loadingContainer = $('<div id="loadingChartContainer" style="background: #ffffff9f"><span class="fa fa-spinner fa-spin" style="font-size: xx-large;"></span></div>').css({
position: 'absolute',
@ -126,6 +143,16 @@ function init() { // variables and functions have their own scope (no override)
.style('color', 'white')
.style('border-radius', '5px')
.style('display', 'none');
tooltipPickedNodes = d3.select('body').append('div')
.attr('class', 'tooltip tooltipPickedNodes')
.style('opacity', 0)
.style('min-width', '120px')
.style('padding', '3px')
.style('background-color', '#fff')
.style('color', 'black')
.style('border', '1px solid black')
.style('border-radius', '5px')
.style('display', 'none');
$container.append($loadingContainer);
timeFormatter = d3.time.format(options.time_format).parse;
}
@ -139,8 +166,8 @@ function init() { // variables and functions have their own scope (no override)
if (options.abscissa_linear) {
x = d3.scale.linear()
.domain(d3.extent(data, function(d) { return d.index; }))
.range([ 0, width ]);
.domain(d3.extent(data, function(d) { return d.index; }))
.range([ 0, width ]);
} else {
x = d3.time.scale()
.domain(d3.extent(data, function(d) { return d.date; }))
@ -159,7 +186,7 @@ function init() { // variables and functions have their own scope (no override)
.tickFormat("");
value_line = d3.svg.line()
.x(function(d) { return x(options.abscissa_linear ? d.index : d.date); })
.x(function(d) { return x(getX(d)); })
.y(function(d) { return y(d.count); });
svg = d3.select(container_id)
@ -262,6 +289,23 @@ function init() { // variables and functions have their own scope (no override)
svg.append('g')
.classed('point-group', true);
overlayLeft = svg.append('rect')
.attr('fill', 'black')
.attr('opacity', 0.6)
.attr('class', 'overlay-left')
.attr('width', 0)
.attr('height', height)
.attr('x', 0)
.on('click', clearPickedNodes);
overlayRight = svg.append('rect')
.attr('fill', 'black')
.attr('opacity', 0.6)
.attr('class', 'overlay-right')
.attr('width', 0)
.attr('height', height)
.attr('x', 0)
.on('click', clearPickedNodes);
window.addEventListener("resize", function() {
if (resize_timeout !== undefined) {
clearTimeout(resize_timeout);
@ -350,7 +394,7 @@ function init() { // variables and functions have their own scope (no override)
data_nodes_active = data_nodes.filter(function(d) {
return !d.disabled;
})
x.domain(d3.extent(chart_data, function(d) { return options.abscissa_linear ? d.index : d.date; }))
x.domain(d3.extent(chart_data, function(d) { return getX(d); }))
y.domain([
d3.min(data_nodes_active, function(c) { return d3.min(c.values, function(v) { return v.count; }); }),
d3.max(data_nodes_active, function(c) { return d3.max(c.values, function(v) { return v.count; }); })
@ -401,17 +445,17 @@ function init() { // variables and functions have their own scope (no override)
points
.enter()
.append('circle')
.attr('class', 'datapoint d3-line-circle')
.attr('class', 'datapoint d3-line-circle useCursorPointer')
.attr('r', 5)
points // Update
.attr('cx', function (d) { return x(options.abscissa_linear ? d.index : d.date); })
.attr('cx', function (d) { return x(getX(d)); })
.attr('cy', function (d) { return y(d.count); })
.style("fill", function(d) { return colors(d.name); })
.on('mouseover', function(d) {
tooltipDate(true, this, d);
})
.on('mouseout', function() {
tooltipDate(false);
.on('click', function(d) {
handleMarkerClick(d);
})
pointsGroup.exit().remove();
}
@ -456,60 +500,184 @@ function init() { // variables and functions have their own scope (no override)
return 'translate(' + xpos + ',' + ypos + ')';
})
.on('click', function(d, i) {
d.disabled = !d.disabled;
var label_text = d.text;
var label_disabled = d.disabled;
data_nodes.filter(function(d) { return d.name === label_text; }).forEach(function(data) {
data.disabled = label_disabled
})
_draw()
if (d3.event.ctrlKey) { // hide all others
data_nodes.filter(function(fd) { return fd.name === label_text; }).forEach(function(data) {
data.disabled = false;
})
data_nodes.filter(function(fd) { return fd.name !== label_text; }).forEach(function(data) {
data.disabled = true;
})
d.disabled = false;
legend_labels.filter(function(fd) { return fd.text !== label_text}).forEach(function(label_data) {
label_data.disabled = true;
})
} else { // hide it
d.disabled = !d.disabled;
data_nodes.filter(function(fd) { return fd.name === label_text; }).forEach(function(data) {
data.disabled = d.disabled;
})
}
_draw();
});
}
}
function tooltipDate(show, d3Element, datum) {
var tooltip = _toggleTooltip(show, d3Element);
if (show) {
tooltip.html(_generate_tooltip(datum));
var tooltipBR = tooltip.node().getBoundingClientRect();
var tooltipHeight = tooltipBR.height;
var tooltipWidth = tooltipBR.width;
var tooltipcx = parseInt(d3.select(d3Element).attr('cx'));
var dcx = 17;
// Flip tooltip position if necessary
if (width < options.margin.right + tooltipcx - dcx + tooltipWidth) {
var tooltipLeft = parseInt(tooltip.style('left').split('px')[0]);
tooltip.style('left', (tooltipLeft - (dcx + tooltipWidth + 15)) + 'px')
}
var tooltipTop = parseInt(tooltip.style('top').split('px')[0]);
tooltip.style('top', (tooltipTop - tooltipHeight/2) + 'px')
}
}
function _toggleTooltip(show, d3Element) {
if (show) {
tooltip_container
.style('display', 'block')
.style('left', (d3.event.pageX + 17) + 'px')
.style('top', (d3.event.pageY) + 'px')
.transition()
.duration(options.animation_short_duration)
.delay(options.animation_short_duration/2)
.style('opacity', '0.7');
} else {
tooltip_container.transition()
.duration(options.animation_short_duration)
.style('opacity', 0)
.delay(options.animation_short_duration)
.style('display', 'none');
}
return tooltip_container;
var $d3Element = $(d3Element);
$d3Element.tooltip({
html: true,
container: 'body',
title: _generate_tooltip(datum)
}).tooltip('show')
}
function _generate_tooltip(datum) {
var formated_date = d3.time.format(options.time_format)(datum.date);
var html = $('<p></p>').text(datum.name).html() + ' (' + formated_date + ', <strong>' + $('<p></p>').text(datum.count).html() + '</strong>) ';
return html;
var formated_x = options.abscissa_linear ? datum.index : d3.time.format(options.time_format)(datum.date);
return $('<div></div>').append(
$('<h6></h6>').text(formated_x).css({'margin': 0}),
$('<h6></h6>').append(
$('<span></span>').text(datum.name).css({'margin-right': '1em'}).prepend(
$('<svg height="10px" width="15px"></svg>').append($('<circle></circle>')
.attr('cx', 5)
.attr('cy', 5)
.attr('r', 5)
.css('fill', colors(datum.name))
)
),
$('<span></span>').text(datum.count)
).css({'margin': 0})
)[0].outerHTML
}
function handleMarkerClick(datum) {
var xVal = getX(datum);
if (pickedNodes.start === null) {
pickedNodes.start = datum;
} else {
if (getX(pickedNodes.start) < xVal) {
pickedNodes.end = datum;
} else {
pickedNodes.end = pickedNodes.start;
pickedNodes.start = datum;
}
}
updatePickedNodesOverlays();
}
function clearPickedNodes() {
pickedNodes.start = null;
pickedNodes.end = null;
updatePickedNodesOverlays();
}
function updatePickedNodesOverlays() {
if (pickedNodes.start === null) {
overlayLeft.attr('width', 0);
overlayRight.attr('x', 0)
.attr('width', 0);
togglePickedNodeTooltip(false);
} else {
overlayLeft.attr('width', x(getX(pickedNodes.start)));
if (pickedNodes.end !== null) {
overlayRight.attr('x', x(getX(pickedNodes.end)))
.attr('width', width - x(getX(pickedNodes.end)));
togglePickedNodeTooltip(true);
}
}
}
function togglePickedNodeTooltip(show) {
if (show) {
tooltipPickedNodes.html(genTooltipPickedNodeHtml());
tooltipPickedNodes
.style('display', 'block')
.style('opacity', '0.8');
var overlayLeftBCR = overlayLeft.node().getBoundingClientRect();
var overlayRightBCR = overlayRight.node().getBoundingClientRect();
var tooltipBCR = tooltipPickedNodes.node().getBoundingClientRect();
var left = (overlayLeftBCR.width - overlayRightBCR.width > 0 ?
overlayLeftBCR.left + overlayLeftBCR.width/2 :
overlayRightBCR.left + overlayRightBCR.width/2) - tooltipBCR.width / 2;
var top = overlayLeftBCR.top + window.scrollY + 30;
tooltipPickedNodes
.style('left', left + 'px')
.style('top', top + 'px')
} else {
tooltipPickedNodes.style('display', 'none');
}
return tooltipPickedNodes;
}
function genTooltipPickedNodeHtml() {
var xValueStart = getX(pickedNodes.start)
var xValueEnd = getX(pickedNodes.end)
var yValues = []
data_nodes_active.forEach(function(serie) {
var startPoint = serie.values.find(function(point) {
return getX(point) == xValueStart;
})
var endPoint = serie.values.find(function(point) {
return getX(point) == xValueEnd;
})
if (startPoint !== undefined && endPoint !== undefined)
var deltaY = endPoint.count - startPoint.count;
var deltaYPerc = startPoint.count != 0 ? Math.abs(100*deltaY / startPoint.count).toFixed(2) : '-';
yValues.push({
name: serie.name,
nameColor: colors(serie.name),
deltaY: deltaY,
deltaYPerc: deltaYPerc + '%',
yColor: deltaY == 0 ? '' : (deltaY > 0 ? 'success' : 'error')
})
})
if (!options.abscissa_linear) {
xValueStart = d3.time.format(options.time_format)(xValueStart);
xValueEnd = d3.time.format(options.time_format)(xValueEnd);
}
var $content = $('<div></div>').append(
$('<div style="display: flex; justify-content: space-between;"></div>').append(
$('<span class="bold"></span>').text(xValueStart),
$('<i class="fas fa-arrow-right"></i>'),
$('<span class="bold"></span>').text(xValueEnd)
)
);
var $table = $('<table class="table table-condensed" style="margin-bottom: 0;"></table>').append(
$('<thead></thead>').append($('<tr></tr>').append(
$('<th></th>').text('Name'),
$('<th></th>').text('Delta'),
$('<th></th>').text('Delta %')
)
)
);
yValues.forEach(function(serie) {
$table.append(
$('<tbody></tbody>').append($('<tr></tr>').append(
$('<td></td>').append(
$('<svg height="10px" width="15px"></svg>').append($('<circle></circle>')
.attr('cx', 5)
.attr('cy', 5)
.attr('r', 5)
.css('fill', serie.nameColor)
),
$('<span></span>').text(serie.name)
),
$('<td></td>')
.addClass('text-'+serie.yColor)
.text(serie.deltaY)
.append($('<i></i>').addClass(serie.deltaY > 0 ? 'fas fa-caret-up' : 'fas fa-caret-down')),
$('<td></td>')
.addClass('text-'+serie.yColor)
.text(serie.deltaYPerc)
.append($('<i></i>').addClass(serie.deltaY > 0 ? 'fas fa-caret-up' : 'fas fa-caret-down')),
)
)
);
});
$content.append($table);
return $content[0].outerHTML;
}
};
</script>
@ -578,4 +746,8 @@ function init() { // variables and functions have their own scope (no override)
.axis.grid path {
stroke-width: 0;
}
.overlay-right, .overlay-left {
cursor: pointer;
}
</style>

View File

@ -17,7 +17,7 @@
h($data['model']);
$fieldsString = '';
$simpleFieldWhitelist = array(
'default', 'type', 'options', 'placeholder', 'label', 'empty', 'rows', 'div'
'default', 'type', 'options', 'placeholder', 'label', 'empty', 'rows', 'div', 'required'
);
$fieldsArrayForPersistence = array();
$formCreate = $this->Form->create($modelForForm);
@ -116,10 +116,12 @@
);
} else {
echo sprintf(
'<div class="form">%s<fieldset><legend>%s</legend>%s%s</fieldset>%s%s%s</div>',
'<div class="%s">%s<fieldset><legend>%s</legend>%s<div class="clear" style="padding-bottom:10px;">%s</div>%s</fieldset>%s%s%s</div>',
empty($data['skip_side_menu']) ? 'form' : 'menuless-form',
$formCreate,
empty($data['title']) ? h(Inflector::humanize($this->request->params['action'])) . ' ' . $modelForForm : h($data['title']),
$ajaxFlashMessage,
empty($data['description']) ? '' : $data['description'],
$fieldsString,
$formEnd,
$metaFieldString,

View File

@ -19,10 +19,13 @@
*/
echo '<td class="short action-links">';
foreach ($actions as $action) {
if (isset($action['requirement']) && !$action['requirement']) {
continue;
}
if (isset($action['complex_requirement'])) {
if (isset($action['complex_requirement']['options']['datapath'])) {
foreach ($action['complex_requirement']['options']['datapath'] as $name => $path) {
$action['complex_requirement']['options']['datapath'][$name] = Hash::extract($row, $path)[0];
$action['complex_requirement']['options']['datapath'][$name] = empty(Hash::extract($row, $path)[0]) ? null : Hash::extract($row, $path)[0];
}
}
$options = isset($action['complex_requirement']['options']) ? $action['complex_requirement']['options'] : array();
@ -55,6 +58,9 @@
}
$url .= '/' . $url_param_data_paths;
}
if (!empty($action['url_extension'])) {
$url .= '.' . $action['url_extension'];
}
if (isset($action['postLink'])) {
echo $this->Form->postLink(
'',

View File

@ -1,6 +1,57 @@
<?php
$rules_raw = array();
$typeOptions = array(
'OR' => array(
'colour' => 'green',
'text' => 'allowed'
),
'NOT' => array(
'colour' => 'red',
'text' => 'blocked'
)
);
if (
!empty(Hash::extract($row, $field['data_path'])[0]) &&
!empty($field['rule_path'][0]) &&
!empty(Hash::extract($row, $field['rule_path'])[0])
) {
$rules = Hash::extract($row, $field['rule_path'])[0];
$rules = json_decode($rules, true);
foreach ($rules as $rule => $rule_data) {
if (is_array($rule_data)) {
foreach ($rule_data as $boolean => $values) {
if (!empty($values)) {
if (is_array($values)) {
$values = implode(', ', $values);
}
$rules_raw[] = sprintf(
'<span class=\'bold\'>%s %s</span>: <span class=\'%s\'>%s</span>',
h(Inflector::humanize($rule)),
$typeOptions[$boolean]['text'],
$typeOptions[$boolean]['colour'],
h($values)
);
}
}
} else if (!empty($rule_data)){
$rules_raw[] = sprintf(
'<span class=\'bold\'>%s</span>: <span class=\'green\'>%s</span>',
h(Inflector::humanize($rule)),
h($rule_data)
);
}
}
$rules_raw = implode('<br />', $rules_raw);
}
echo sprintf(
'<i class="black fa fa-%s"></i>',
(!empty(Hash::extract($row, $field['data_path'])[0])) ? 'check' : 'times'
'<i class="black fa fa-%s"></i>%s',
(!empty(Hash::extract($row, $field['data_path'])[0])) ? 'check' : 'times',
empty($rules_raw) ? '' :
sprintf(
' <span data-toggle="popover" title="%s" data-content="%s">(%s)</span>',
__('Filter rules'),
$rules_raw,
__('Rules')
)
);
?>

View File

@ -0,0 +1,32 @@
<?php
$timestamp = Hash::extract($row, $field['data_path'])[0];
$enabled = isset($field['enabled_path']) ? Hash::extract($row, $field['enabled_path'])[0] : true;
if (!empty($timestamp)):
$units = array('m', 'h', 'd');
$intervals = array(60, 60, 24);
$unit = 's';
$last = time() - $timestamp;
foreach ($units as $k => $v) {
if ($last > $intervals[$k]) {
$unit = $v;
$last = floor($last / $intervals[$k]);
} else {
break;
}
}
$ageString = __('Age: ') . $last . $unit;
else:
$ageString = __('Not cached');
endif;
echo sprintf(
'<span class="%s">%s</span>%s',
empty($timestamp) ? 'red bold' : '',
h($ageString),
(!$enabled || !$isSiteAdmin) ? '' : sprintf(
' <a href="%s" aria-label="%s" title="%s"><span class="black fa fa-memory"></span></a>',
$baseurl . '/feeds/cacheFeeds/' . h($primary),
__('Cache feed'),
__('Cache feed')
)
);
?>

View File

@ -19,6 +19,12 @@
$data = '';
} else {
$data = h($data);
if (!empty($field['privacy'])) {
$data = sprintf(
'<span class="privacy-value" data-hidden-value="%s">****************************************</span> <i class="privacy-toggle fas fa-eye useCursorPointer"></i>',
$data
);
}
}
if (!empty($field['onClick'])) {
$data = sprintf(

View File

@ -1,7 +1,15 @@
<?php
$data = Hash::extract($row, $field['data_path']);
foreach ($data as &$element) {
$element = h($element);
foreach ($data as $key => $element) {
if (!is_numeric($key)) {
$data[$key] = sprintf(
'<span>%s</span>: %s',
h($key),
h($element)
);
} else {
$data[$key] = h($element);
}
}
$data = implode('<br />', $data);
echo $data;

View File

@ -14,8 +14,8 @@
$i = 0;
foreach ($orgs as $org) {
$i++;
if (!empty($org['id'])) {
if ($field['fields']['allow_picture']) {
if (!empty($org['id']) || !empty($org['name'])) {
if ($field['fields']['allow_picture'] && !empty($org['id'])) {
echo $this->OrgImg->getOrgImg(array('name' => $org['name'], 'id' => $org['id'], 'size' => 24));
} else {
echo sprintf(

View File

@ -1,6 +1,20 @@
<?php
$data = array();
if (!empty($field['data'])) {
foreach ($field['data'] as $dataField => $dataValue) {
$value = '';
if (!empty($dataValue['value'])) {
$value = $dataValue['value'];
}
if (!empty($dataValue['value_path']) && !empty(Hash::extract($row, $dataValue['value_path'])[0])) {
$value = Hash::extract($row, $dataValue['value_path'])[0];
}
$data[] = 'data-' . h($dataField) . '="' . h($value) . '"';
}
}
echo sprintf(
'<input class="select_attribute" type="checkbox" data-rowid="%s">',
h($k)
'<input class="select_attribute select" type="checkbox" data-rowid="%s" %s>',
h($k),
empty($data) ? '' : implode(' ', $data)
);
?>

View File

@ -0,0 +1,17 @@
<?php
$self_registration_flag = Hash::extract($row, $field['data_path_requirement']);
if (empty($self_registration_flag[0])) {
echo '<i class="black fa fa-times"></i>';
} else {
$url = Hash::extract($row, $field['data_path'])[0];
echo sprintf(
'<i class="black fa fa-%s"></i>%s',
(!empty($self_registration_flag[0])) ? 'check' : 'times',
(empty($self_registration_flag[0])) ? '' :
sprintf(
' (<a href="%s/users/register">' . __('click here') . '</a>)',
h($url)
)
);
}
?>

View File

@ -1,4 +1,17 @@
<?php
$tags = Hash::extract($row, $field['data_path']);
echo $this->element('ajaxTags', array('attributeId' => 0, 'tags' => $tags, 'tagAccess' => false));
if (!empty($tags)) {
if (empty($tags[0])) {
$tags = array($tags);
}
echo $this->element(
'ajaxTags',
array(
'attributeId' => 0,
'tags' => $tags,
'tagAccess' => false,
'static_tags_only' => 1
)
);
}
?>

View File

@ -0,0 +1,33 @@
<?php
$data = $this->DataPathCollector->extract($row, $field['data_path']);
if ($data['Feed.enabled']) {
if (in_array($data['Feed.source_format'], array('freetext', 'csv'))) {
if ($data['Feed.fixed_event']) {
if (!empty($data['Feed.event_error'])) {
echo sprintf(
'<span class="red bold">%s</span>',
__('Error: Invalid event!')
);
} else {
if ($data['Feed.event_id']) {
echo sprintf(
'<a href="%s/events/view/%s">%s</a>',
$baseurl,
h($data['Feed.event_id']),
__('Fixed event %s', h($data['Feed.event_id']))
);
} else {
echo __('New fixed event');
}
}
} else {
echo sprintf(
'<span class="bold red" title="%s">%s</span>',
__('New event each pull can lead to potentially endlessly growing correlation tables. Only use this setting if you are sure that the data in the feed will mostly be completely distinct between each individual pull, otherwise use fixed events. Generally this setting is NOT recommended.'),
__('New event each pull')
);
}
}
} else {
echo __('Feed not enabled');
}

View File

@ -1,3 +1,7 @@
<?php
echo h(Hash::extract($row, $field['data_path'])[0]);
$timestamp = Hash::extract($row, $field['data_path'])[0];
if (!empty($field['time_format'])) {
$timestamp = date($field['time_format'], $timestamp);
}
echo h($timestamp);
?>

View File

@ -12,7 +12,7 @@
} else {
if (!empty($header['element']) && $header['element'] === 'selector') {
$header_data = sprintf(
'<input class="%s" type="checkbox" %s>',
'<input id="select_all" class="%s" type="checkbox" %s>',
empty($header['select_all_class']) ? 'select_all' : $header['select_all_class'],
empty($header['select_all_function']) ? 'onclick="toggleAllAttributeCheckboxes();"' : 'onclick="' . $header['select_all_function'] . '"'
);
@ -38,3 +38,14 @@
$thead .= '</thead>';
echo $thead;
?>
<script type="text/javascript">
$(document).ready(function() {
$('.select_attribute').add('#select_all').on('change', function() {
if ($('.select_attribute:checked').length > 0) {
$('.mass-select').show();
} else {
$('.mass-select').hide();
}
});
});
</script>

View File

@ -11,7 +11,8 @@
* // field list with information for the paginator, the elements used for the individual cells, etc
* ),
* 'title' => optional title,
* 'description' => optional description
* 'description' => optional description,
* 'primary_id_path' => path to each primary ID (extracted and passed as $primary to fields)
* ));
*
*/
@ -31,6 +32,7 @@
if (!$skipPagination) {
$paginationData = !empty($data['paginatorOptions']) ? $data['paginatorOptions'] : array();
echo $this->element('/genericElements/IndexTable/pagination', array('paginationOptions' => $paginationData));
echo $this->element('/genericElements/IndexTable/pagination_links');
}
if (!empty($data['top_bar'])) {
echo $this->element('/genericElements/ListTopBar/scaffold', array('data' => $data['top_bar']));
@ -42,13 +44,18 @@
$dblclickActionArray = isset($data['actions']) ? Hash::extract($data['actions'], '{n}[dbclickAction]') : array();
$dbclickAction = '';
foreach ($data['data'] as $k => $data_row) {
$primary = null;
if (!empty($data['primary_id_path'])) {
$primary = Hash::extract($data_row, $data['primary_id_path'])[0];
}
if (!empty($dblclickActionArray)) {
$dbclickAction = sprintf("changeLocationFromIndexDblclick(%s)", $k);
}
$rows .= sprintf(
'<tr data-row-id="%s" ondblclick="%s">%s</tr>',
'<tr data-row-id="%s" %s %s>%s</tr>',
h($k),
$dbclickAction,
empty($dbclickAction) ? '' : 'ondblclick="' . $dbclickAction . '"',
empty($primary) ? '' : 'data-primary-id="' . $primary . '"',
$this->element(
'/genericElements/IndexTable/' . $row_element,
array(
@ -57,6 +64,7 @@
'fields' => $data['fields'],
'options' => $options,
'actions' => $actions,
'primary' => $primary
)
)
);
@ -74,6 +82,22 @@
echo '</div>';
if (!$skipPagination) {
echo $this->element('/genericElements/IndexTable/pagination_counter', $paginationData);
echo $this->element('/genericElements/IndexTable/pagination', $paginationData);
echo $this->element('/genericElements/IndexTable/pagination_links');
}
?>
<script type="text/javascript">
$(document).ready(function() {
$('.privacy-toggle').on('click', function() {
var $privacy_target = $(this).parent().find('.privacy-value');
if ($(this).hasClass('fa-eye')) {
$privacy_target.text($privacy_target.data('hidden-value'));
$(this).removeClass('fa-eye');
$(this).addClass('fa-eye-slash');
} else {
$privacy_target.text('****************************************');
$(this).removeClass('fa-eye-slash');
$(this).addClass('fa-eye');
}
});
});
</script>

View File

@ -9,10 +9,4 @@
$options = array_merge($options, $paginationOptions);
}
echo $this->Paginator->options($options);
echo sprintf(
'<div class="pagination"><ul>%s%s%s</ul></div>',
$this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span')),
$this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span')),
$this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'))
);
?>

View File

@ -0,0 +1,7 @@
<?php
echo sprintf(
'<div class="pagination"><ul>%s%s%s</ul></div>',
$this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span')),
$this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span')),
$this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'))
);

View File

@ -1,4 +1,14 @@
<?php
/*
* Constructs the individual rows for the index system
* Each row has a list of fields and optionally a set of actions
* These are passed via the main constructor passed to the index_table
*
* Rows optionally have doubleclick actions and can store 2 data points
* to ease front-end scripting:
* - data-row-id is the n-th row currently rendered
* - data-primary-id is the database ID of the element described by each row
*/
$rowHtml = '';
foreach ($fields as $column => $field) {
$field['data_path'] = empty($field['data_path']) ? '' : $field['data_path'];
@ -13,7 +23,8 @@
'row' => $row,
'column' => $column,
'data_path' => empty($field['data_path']) ? '' : $field['data_path'],
'k' => $k
'k' => $k,
'primary' => $primary
)
);
}
@ -23,7 +34,12 @@
(empty($field['class'])) ? '' : sprintf(' class="%s"', $field['class']),
(empty($field['style'])) ? '' : sprintf(' style="%s"', $field['style']),
(empty($field['title'])) ? '' : sprintf(' title="%s"', $field['title']),
(empty($field['name'])) ? '' : sprintf(' data-path="%s"', (h($field['data_path']))),
(empty($field['name'])) ? '' : sprintf(
' data-path="%s"',
is_array($field['data_path']) ?
h(implode(', ', $field['data_path'])) :
(h($field['data_path']))
),
(empty($field['encode_raw_value']) || empty($field['data_path'])) ? '' : sprintf(' data-value="%s"', (h(Hash::extract($row, $field['data_path'])[0]))),
(empty($field['ondblclick'])) ? '' : sprintf(' ondblclick="%s"', $field['ondblclick']),
$valueField
@ -36,7 +52,8 @@
array(
'actions' => $actions,
'row' => $row,
'column' => $column
'column' => $column,
'primary' => $primary
)
);
}

View File

@ -34,7 +34,7 @@
}
$dataFields = implode(' ', $dataFields);
echo sprintf(
'<a class="btn btn-small %s %s" %s href="%s" %s %s %s %s>%s%s%s</a>',
'<a class="btn btn-small %s %s" %s href="%s" %s %s %s %s %s>%s%s%s</a>',
empty($data['class']) ? '' : h($data['class']),
empty($data['active']) ? 'btn-inverse' : 'btn-primary', // Change the default class for highlighted/active toggles here
empty($data['id']) ? '' : 'id="' . h($data['id']) . '"',
@ -42,7 +42,8 @@
empty($onClick) ? '' : $onClick, // pass $data['onClick'] for the function name to call and $data['onClickParams'] for the parameter list
empty($dataFields) ? '' : $dataFields,
empty($data['title']) ? '' : sprintf('title="%s"', h($data['title'])),
!empty($data['text']) ? '' : !empty($data['title']) ? sprintf('aria-label="%s"', h($data['title'])) : '',
empty($data['style']) ? '' : sprintf('style="%s"', h($data['style'])),
!empty($data['text']) ? '' : (!empty($data['title']) ? sprintf('aria-label="%s"', h($data['title'])) : ''),
empty($data['fa-icon']) ? '' : sprintf(
'<i class="%s fa-%s"></i> ',
empty($data['fa-source']) ? 'fas' : h($data['fa-source']),

View File

@ -734,6 +734,11 @@
'url' => '/admin/users/index',
'text' => __('List Users')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'registrations',
'url' => '/users/registrations',
'text' => __('Pending registrations')
));
}
if ($isAdmin) {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
@ -773,13 +778,16 @@
),
'text' => __('Merge Organisation')
));
}
if ($menuItem === 'editOrg' || $menuItem === 'viewOrg') {
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'viewOrg',
'url' => '/organisations/view/' . h($id),
'text' => __('View Organisation')
));
echo $this->element('/genericElements/SideMenu/side_menu_post_link', array(
'url' => '/admin/organisations/delete/' . h($id),
'text' => __('Delete Organisation'),
'message' => __('Are you sure you want to delete # %s?', h($id))
));
}
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'element_id' => 'indexOrg',
@ -804,6 +812,10 @@
'url' => '/servers/serverSettings',
'text' => __('Server Settings & Maintenance')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => '/inbox',
'text' => __('Inbox')
));
echo $this->element('/genericElements/SideMenu/side_menu_link', array(
'url' => '/servers/updateProgress',
'text' => __('Update Progress')

View File

@ -304,6 +304,10 @@
'text' => __('Contact Users'),
'url' => '/admin/users/email'
),
array(
'text' => __('User Registrations'),
'url' => '/users/registrations'
),
array(
'type' => 'separator'
),

View File

@ -85,7 +85,8 @@
);
$rows = '';
foreach ($dbSchemaDiagnostics as $tableName => $tableDiagnostic) {
$rows .= sprintf('<tr data-tablename="%s">', $tableName);
$tableContainsCritical = array_filter(Hash::extract($tableDiagnostic, '{n}.is_critical'));
$rows .= sprintf('<tr class="%s" data-tablename="%s">', $tableContainsCritical ? '' : 'noncritical', $tableName);
$rows .= sprintf('<td rowspan="%s" colspan="0" class="bold">%s</td>', count($tableDiagnostic)+1, h($tableName));
$rows .= '</tr>';

View File

@ -10,7 +10,7 @@
</div>
<div id="eventdistri_graph" data-event-id="<?php echo h($event['Event']['id']); ?>" data-event-distribution="<?php echo h($event['Event']['distribution']); ?>" data-event-distribution-text="<?php echo $event['Event']['distribution'] == 4 ? h($event['SharingGroup']['name']) : h($distributionLevels[$event['Event']['distribution']]); ?>" data-user-manipulation="<?php echo $mayModify || $isSiteAdmin ? 'true' : 'false'; ?>" data-extended="<?php echo $extended; ?>">
<canvas id="distribution_graph_canvas" height="290px"width="400px"></canvas>
<canvas id="distribution_graph_canvas" height="290" width="400"></canvas>
</div>
<div class="popupDistriSeparator"></div>
<div id="eventdistri_pb_container">

View File

@ -6,9 +6,19 @@
<strong><?php echo __('Make sure you keep your API key secret as it gives access to the all of the data that you normally have access to in MISP.');?></strong>
<?php echo __('To view the old MISP automation page, click <a href="automation/1">here</a>.');?>
</p>
<p><?php echo __('Your current key is: <code>%s</code>.
You can %s this key.', $me['authkey'], $this->Html->link(__('reset'), array('controller' => 'users', 'action' => 'resetauthkey', 'me')));?>
</p>
<span>
<?php
echo __(
'Your current key is: <code>%s</code>. You can %s this key.',
$me['authkey'],
$this->Form->postLink(
__('reset'),
array('controller' => 'users', 'action' => 'resetauthkey', 'me'),
array('div' => false)
)
);
?>
</span>
<?php
$data = array(
'title' => __('Search'),

View File

@ -301,27 +301,27 @@
)
)
);
if (!Configure::read('MISP.completely_disable_correlation') && Configure::read('MISP.allow_disabling_correlation')) {
$table_data[] = array(
'key' => __('Correlation'),
'class' => $event['Event']['disable_correlation'] ? 'background-red bold' : '',
'html' => sprintf(
'%s%s',
$event['Event']['disable_correlation'] ? __('Disabled') : __('Enabled'),
(!$mayModify && !$isSiteAdmin) ? '' : sprintf(
}
if (!Configure::read('MISP.completely_disable_correlation') && Configure::read('MISP.allow_disabling_correlation')) {
$table_data[] = array(
'key' => __('Correlation'),
'class' => $event['Event']['disable_correlation'] ? 'background-red bold' : '',
'html' => sprintf(
'%s%s',
$event['Event']['disable_correlation'] ? __('Disabled') : __('Enabled'),
(!$mayModify && !$isSiteAdmin) ? '' : sprintf(
sprintf(
' (<a onClick="getPopup(%s);" style="%scursor:pointer;font-weight:normal;">%s</a>)',
sprintf(
' (<a onClick="getPopup(%s);" style="%scursor:pointer;font-weight:normal;">%s</a>)',
sprintf(
"'%s', 'events', 'toggleCorrelation', '', '#confirmation_box'",
h($event['Event']['id'])
),
$event['Event']['disable_correlation'] ? 'color:white;' : '',
$event['Event']['disable_correlation'] ? __('enable') : __('disable')
)
"'%s', 'events', 'toggleCorrelation', '', '#confirmation_box'",
h($event['Event']['id'])
),
$event['Event']['disable_correlation'] ? 'color:white;' : '',
$event['Event']['disable_correlation'] ? __('enable') : __('disable')
)
)
);
}
)
);
}
?>
@ -379,7 +379,7 @@
$count++;
if ($count == $display_threshold+1 && $total > $display_threshold):
?>
<div class="no-side-padding correlation-expand-button useCursorPointer linkButton blue"><?php echo __('Show (%s more)', $total - $count);?></div>
<div class="no-side-padding correlation-expand-button useCursorPointer linkButton blue"><?php echo __('Show (%s more)', $total - ($count-1));?></div>
<?php
endif;
?>

View File

@ -2,10 +2,16 @@
<?php echo $this->Form->create('Feed');?>
<fieldset>
<legend><?php echo __('Add MISP Feed');?></legend>
<p><?php echo __('Add a new MISP feed source.');?></p>
<?php
echo $this->Form->input('enabled', array());
echo $this->Form->input('caching_enabled', array('label' => __('Caching enabled')));
<?php
if (!empty(Configure::read('Security.disable_local_feed_access'))) {
echo sprintf(
'<p class="red bold">%s</p>',
__('Warning: local feeds are currently disabled by policy, to re-enable the feature, set the Security.allow_local_feed_access flag in the server settings. This setting can only be set via the CLI.')
);
}
echo '<p>' . __('Add a new MISP feed source.') . '</p>';
echo $this->Form->input('enabled', array());
echo $this->Form->input('caching_enabled', array('label' => __('Caching enabled')));
?>
<div class="input clear"></div>
<?php
@ -21,10 +27,14 @@
'placeholder' => __('Name of the content provider'),
'class' => 'form-control span6'
));
$options = array('network' => 'Network');
if (empty(Configure::read('Security.disable_local_feed_access'))) {
$options['local'] = 'Local';
}
echo $this->Form->input('input_source', array(
'label' => __('Input Source'),
'div' => 'input clear',
'options' => array('network' => 'Network', 'local' => 'Local'),
'options' => $options,
'class' => 'form-control span6'
));
?>
@ -78,6 +88,16 @@
</div>
</div><br />
</div>
<div id="OrgcDiv" class="optionalField">
<?php
echo $this->Form->input('orgc_id', array(
'label' => __('Creator organisation'),
'div' => 'input clear',
'options' => $orgs,
'class' => 'form-control span6'
));
?>
</div>
<div id="TargetDiv" class="optionalField">
<?php
echo $this->Form->input('fixed_event', array(

View File

@ -2,8 +2,14 @@
<?php echo $this->Form->create('Feed');?>
<fieldset>
<legend><?php echo __('Edit MISP Feed');?></legend>
<p><?php echo __('Edit a new MISP feed source.');?></p>
<?php
<?php
if (!empty(Configure::read('Security.disable_local_feed_access'))) {
echo sprintf(
'<p class="red bold">%s</p>',
__('Warning: local feeds are currently disabled by policy, to re-enable the feature, set the Security.allow_local_feed_access flag in the server settings. This setting can only be set via the CLI.')
);
}
echo '<p>' . __('Edit a new MISP feed source.') . '</p>';
echo $this->Form->input('enabled', array(
'type' => 'checkbox'
));
@ -26,9 +32,13 @@
'placeholder' => __('Name of the content provider'),
'class' => 'form-control span6'
));
$options = array('network' => 'Network');
if (empty(Configure::read('Security.disable_local_feed_access'))) {
$options['local'] = 'Local';
}
echo $this->Form->input('input_source', array(
'div' => 'input clear',
'options' => array('network' => 'Network', 'local' => 'Local'),
'options' => $options,
'class' => 'form-control span6'
));
?>
@ -82,6 +92,16 @@
'class' => 'form-control span6'
));
?>
<div id="OrgcDiv" class="optionalField">
<?php
echo $this->Form->input('orgc_id', array(
'label' => __('Creator organisation'),
'div' => 'input clear',
'options' => $orgs,
'class' => 'form-control span6'
));
?>
</div>
<div id="TargetDiv" class="optionalField">
<?php
echo $this->Form->input('fixed_event', array(

View File

@ -1,316 +1,325 @@
<div class="feed index">
<h2><?php echo __('Feeds');?></h2>
<b><?php echo __('Generate feed lookup caches or fetch feed data (enabled feeds only)');?></b>
<div class="toggleButtons">
<a href="<?php echo $baseurl; ?>/feeds/cacheFeeds/all" class="toggle-left qet btn btn-inverse"><?php echo __('Cache all feeds');?></a>
<a href="<?php echo $baseurl; ?>/feeds/cacheFeeds/freetext" class="toggle qet btn btn-inverse"><?php echo __('Cache freetext/CSV feeds');?></a>
<a href="<?php echo $baseurl; ?>/feeds/cacheFeeds/misp" class="toggle-right qet btn btn-inverse"><?php echo __('Cache MISP feeds');?></a>
<a href="<?php echo $baseurl; ?>/feeds/fetchFromAllFeeds" class="btn btn-primary qet" style="margin-left:20px;"><?php echo __('Fetch and store all feed data');?></a>
</div><br />
<div class="pagination">
<ul>
<?php
$this->Paginator->options(array(
'update' => '.span12',
'evalScripts' => true,
'before' => '$(".progress").show()',
'complete' => '$(".progress").hide()',
));
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
?>
</ul>
</div>
<?php
$canViewFeedData = $isSiteAdmin || intval(Configure::read('MISP.host_org_id')) === $me['org_id'];
$data = array(
'children' => array(
<?php
echo '<div class="index">';
echo $this->element('/genericElements/IndexTable/index_table', array(
'data' => array(
'data' => $feeds,
'primary_id_path' => 'Feed.id',
'top_bar' => array(
'children' => array(
array(
'children' => array(
array(
'class' => 'hidden mass-select',
'text' => __('Enable selected'),
'onClick' => "multiSelectToggleFeeds",
'onClickParams' => array('1', '0')
),
array(
'class' => 'hidden mass-select',
'text' => __('Disable selected'),
'onClick' => "multiSelectToggleFeeds",
'onClickParams' => array('0', '0')
),
array(
'class' => 'hidden mass-select',
'text' => __('Enable caching for selected'),
'onClick' => "multiSelectToggleFeeds",
'onClickParams' => array('1', '1')
),
array(
'class' => 'hidden mass-select',
'text' => __('Disable caching for selected'),
'onClick' => "multiSelectToggleFeeds",
'onClickParams' => array('0', '1')
)
)
),
array(
'children' => array(
array(
'url' => '/feeds/index/scope:default',
'text' => __('Default feeds'),
'active' => $scope === 'default',
'style' => 'display:inline;'
),
array(
'url' => '/feeds/index/scope:custom',
'text' => __('Custom feeds'),
'active' => $scope === 'custom',
'style' => 'display:inline;'
),
array(
'url' => '/feeds/index/scope:all',
'text' => __('All feeds'),
'active' => $scope === 'all',
'style' => 'display:inline;'
),
array(
'url' => '/feeds/index/scope:enabled',
'text' => __('Enabled feeds'),
'active' => $scope === 'enabled',
'style' => 'display:inline;'
)
)
),
array(
'type' => 'search',
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value'
)
)
),
'fields' => array(
array(
'children' => array(
array(
'class' => 'hidden mass-select',
'text' => __('Enable selected'),
'onClick' => "multiSelectToggleFeeds",
'onClickParams' => array('1', '0')
),
array(
'class' => 'hidden mass-select',
'text' => __('Disable selected'),
'onClick' => "multiSelectToggleFeeds",
'onClickParams' => array('0', '0')
),
array(
'class' => 'hidden mass-select',
'text' => __('Enable caching for selected'),
'onClick' => "multiSelectToggleFeeds",
'onClickParams' => array('1', '1')
),
array(
'class' => 'hidden mass-select',
'text' => __('Disable caching for selected'),
'onClick' => "multiSelectToggleFeeds",
'onClickParams' => array('0', '1')
),
'element' => 'selector',
'class' => 'short',
'data' => array(
'id' => array(
'value_path' => 'Feed.id'
)
)
),
array(
'children' => array(
array(
'url' => '/feeds/index/scope:default',
'text' => __('Default feeds'),
'active' => $scope === 'default'
),
array(
'url' => '/feeds/index/scope:custom',
'text' => __('Custom feeds'),
'active' => $scope === 'custom'
),
array(
'url' => '/feeds/index/scope:all',
'text' => __('All feeds'),
'active' => $scope === 'all'
),
array(
'url' => '/feeds/index/scope:enabled',
'text' => __('Enabled feeds'),
'active' => $scope === 'enabled'
)
)
'name' => __('Id'),
'sort' => 'Feed.id',
'class' => 'short',
'data_path' => 'Feed.id',
),
array(
'name' => __('Enabled'),
'sort' => 'Feed.enabled',
'title' => __('Enable pulling the feed into your MISP as events/attributes.'),
'class' => 'short',
'element' => 'boolean',
'data_path' => 'Feed.enabled',
'rule_path' => 'Feed.rules'
),
array(
'name' => __('Caching'),
'sort' => 'Feed.caching_enabled',
'title' => __('Enable caching the feed into Redis - allowing for correlations to the feed to be shown.'),
'class' => 'short',
'element' => 'boolean',
'data_path' => 'Feed.caching_enabled',
),
array(
'name' => __('Name'),
'class' => 'shortish',
'data_path' => 'Feed.name',
),
array(
'name' => __('Format'),
'class' => 'short',
'sort' => 'Feed.source_format',
'data_path' => 'Feed.source_format'
),
array(
'name' => __('Provider'),
'class' => 'short',
'data_path' => 'Feed.provider',
'sort' => 'Feed.provider'
),
array(
'name' => __('Org'),
'class' => 'short',
'data_path' => 'Orgc',
'sort' => 'Feed.Orgc',
'element' => 'org'
),
array(
'name' => __('Source'),
'class' => 'short',
'data_path' => 'Feed.input_source',
'sort' => 'Feed.input_source'
),
array(
'name' => __('URL'),
'class' => 'shortish',
'data_path' => 'Feed.url',
'sort' => 'Feed.url'
),
array(
'name' => __('Headers'),
'class' => 'shortish',
'data_path' => 'Feed.headers'
),
array(
'name' => __('Target'),
'class' => 'short',
'data_path' => array(
'Feed.fixed_event',
'Feed.source_format',
'Feed.event_error',
'Feed.event_id',
'Feed.enabled'
),
'element' => 'target_event'
),
array(
'name' => __('Publish'),
'class' => 'short',
'element' => 'boolean',
'sort' => 'Feed.publish',
'data_path' => 'Feed.publish'
),
array(
'name' => __('Delta'),
'title' => __('Delta Merge strategy - align the local feed with the remote state'),
'class' => 'short',
'element' => 'boolean',
'sort' => 'Feed.delta_merge',
'data_path' => 'Feed.delta_merge'
),
array(
'name' => __('Override'),
'title' => __('Override the IDS flags and set all derived attribute to IDS off'),
'class' => 'short',
'element' => 'boolean',
'sort' => 'Feed.ids',
'data_path' => 'Feed.ids'
),
array(
'name' => __('Distribution'),
'class' => 'short',
'data_path' => 'Feed.distribution',
'element' => 'distribution_levels'
),
array(
'name' => __('Tag'),
'class' => 'short',
'data_path' => 'Tag',
'element' => 'tags'
),
array(
'name' => __('Visible'),
'class' => 'short',
'data_path' => 'Feed.lookup_visible',
'element' => 'boolean',
'sort' => 'Feed.lookup_visible'
),
array(
'name' => __('Caching'),
'class' => 'short',
'data_path' => 'Feed.cache_timestamp',
'enabled_path' => 'Feed.caching_enabled',
'element' => 'caching',
'sort' => 'Feed.cache_timestamp'
)
)
);
echo $this->element('/genericElements/ListTopBar/scaffold', array('data' => $data));
?>
<table class="table table-striped table-hover table-condensed">
<tr>
<?php if ($isSiteAdmin): ?>
<th>
<input class="select_all select" type="checkbox" title="<?php echo __('Select all');?>" role="button" tabindex="0" aria-label="<?php echo __('Select all events on current page');?>" onClick="toggleAllCheckboxes();" />&nbsp;
</th>
<?php else: ?>
<th style="padding-left:0px;padding-right:0px;">&nbsp;</th>
<?php endif;?>
<th><?php echo $this->Paginator->sort('id');?></th>
<th title="<?php echo __('Enable pulling the feed into your MISP as events/attributes.'); ?>"><?php echo $this->Paginator->sort('enabled');?></th>
<th title="<?php echo __('Enable caching the feed into Redis - allowing for correlations to the feed to be shown.'); ?>"><?php echo $this->Paginator->sort('caching_enabled', __('Caching enabled'));?></th>
<th><?php echo $this->Paginator->sort('name');?></th>
<th><?php echo $this->Paginator->sort('source_format', __('Feed Format'));?></th>
<th><?php echo $this->Paginator->sort('provider', __('Provider'));?></th>
<th><?php echo $this->Paginator->sort('input_source', __('Input'));?></th>
<th><?php echo $this->Paginator->sort('url', __('URL'));?></th>
<th><?php echo $this->Paginator->sort('headers');?></th>
<th><?php echo __('Target');?></th>
<th><?php echo __('Publish');?></th>
<th><?php echo __('Delta Merge');?></th>
<th><?php echo __('Override IDS');?></th>
<th><?php echo $this->Paginator->sort('distribution');?></th>
<th><?php echo $this->Paginator->sort('tag');?></th>
<th><?php echo $this->Paginator->sort('lookup_visible', __('Lookup visible'));?></th>
<th class="actions"><?php echo __('Caching');?></th>
<th class="actions"><?php echo __('Actions');?></th>
</tr><?php
foreach ($feeds as $item):
$rules = array();
$rules = json_decode($item['Feed']['rules'], true);
$fieldOptions = array('tags', 'orgs');
$typeOptions = array('OR' => array('colour' => 'green', 'text' => 'allowed'), 'NOT' => array('colour' => 'red', 'text' => 'blocked'));
$ruleDescription = '';
foreach ($fieldOptions as $fieldOption) {
foreach ($typeOptions as $typeOption => $typeData) {
if (isset($rules[$fieldOption][$typeOption]) && !empty($rules[$fieldOption][$typeOption])) {
$ruleDescription .= '<span class=\'bold\'>' .
ucfirst($fieldOption) . ' ' .
$typeData['text'] . '</span>: <span class=\'' .
$typeData['colour'] . '\'>';
foreach ($rules[$fieldOption][$typeOption] as $k => $temp) {
if ($k != 0) $ruleDescription .= ', ';
$ruleDescription .= h($temp);
}
$ruleDescription .= '</span><br />';
}
}
}
?>
<tr>
<?php
if ($isSiteAdmin):
?>
<td style="width:10px;" data-id="<?php echo h($item['Feed']['id']); ?>">
<input class="select" type="checkbox" data-id="<?php echo $item['Feed']['id'];?>" aria-label="select <?php echo $item['Feed']['name'];?>" />
</td>
<?php
else:
?>
<td style="padding-left:0px;padding-right:0px;"></td>
<?php
endif;
?>
<td class="short">
<?php
if ($canViewFeedData) {
echo sprintf(
'<a href="%s/feeds/view/%s" title="%s">%s</a>',
$baseurl,
h($item['Feed']['id']),
sprintf(
__('View feed #%s', h($item['Feed']['id']))
),
'title' => __('Feeds'),
'description' => __('Generate feed lookup caches or fetch feed data (enabled feeds only)'),
'html' => sprintf(
'<div class="toggleButtons">%s%s%s%s%s</div><br />',
$this->Form->postButton(
__('Load default feed metadata'),
array('controller' => 'feeds', 'action' => 'loadDefaultFeeds'),
array(
'class' => 'qet btn btn-inverse',
'div' => false,
'style' => 'margin-right:20px;'
)
),
sprintf(
'<a href="%s/feeds/cacheFeeds/all" class="%s">%s</a>',
$baseurl,
'toggle-left qet btn btn-inverse',
__('Cache all feeds')
),
sprintf(
'<a href="%s/feeds/cacheFeeds/freetext" class="%s">%s</a>',
$baseurl,
'toggle qet btn btn-inverse',
__('Cache freetext/CSV feeds')
),
sprintf(
'<a href="%s/feeds/cacheFeeds/misp" class="%s">%s</a>',
$baseurl,
'toggle-right qet btn btn-inverse',
__('Cache MISP feeds')
),
sprintf(
'<a href="%s/feeds/fetchFromAllFeeds" class="%s" style="%s">%s</a>',
$baseurl,
'btn btn-primary qet',
'margin-left:20px;',
__('Fetch and store all feed data')
)
),
'actions' => array(
array(
'url' => $baseurl . '/feeds/previewIndex',
'url_params_data_paths' => 'Feed.id',
'icon' => 'search',
'title' => __('Explore the events remotely')
),
array(
'url' => $baseurl . '/feeds/fetchFromFeed',
'url_params_data_paths' => 'Feed.id',
'icon' => 'arrow-circle-down',
'title' => __('Fetch all events'),
'requirement' => $isSiteAdmin,
'complex_requirement' => array(
'options' => array(
'datapath' => array(
'event_error' => 'Feed.event_error',
'enabled' => 'Feed.enabled'
),
),
h($item['Feed']['id'])
);
} else {
echo h($item['Feed']['id']);
}
?>
</td>
<td class="short">
<span role="img" <?php echo ($item['Feed']['enabled'] ? 'class="icon-ok" aria-label="Yes"' : 'class="icon-remove" aria-label="No"'); ?>"></span>
<span
class="short <?php if (!$item['Feed']['enabled'] || empty($ruleDescription)) echo "hidden"; ?>"
data-toggle="popover"
title="Filter rules"
data-content="<?php echo $ruleDescription; ?>"
>
(<?php echo __('Rules');?>)
</span>
</td>
<td class="short">
<span role="img" <?php echo ($item['Feed']['caching_enabled'] ? 'class="icon-ok" aria-label="Yes"' : 'class="icon-remove" aria-label="No"'); ?>"></span>
</td>
<td>
<?php
echo h($item['Feed']['name']);
if ($item['Feed']['default']):
?>
<img src="<?php echo $baseurl;?>/img/orgs/MISP.png" width="24" height="24" style="padding-bottom:3px;" />
<?php
endif;
?>
</td>
<td><?php echo $feed_types[$item['Feed']['source_format']]['name']; ?>&nbsp;</td>
<td><?php echo h($item['Feed']['provider']); ?>&nbsp;</td>
<td><?php echo h($item['Feed']['input_source']); ?>&nbsp;</td>
<td><?php echo h($item['Feed']['url']); ?>&nbsp;</td>
<td class="short"><?php echo nl2br(h($item['Feed']['headers'])); ?>&nbsp;</td>
<td class="shortish">
<?php
if (in_array($item['Feed']['source_format'], array('freetext', 'csv'))):
if ($item['Feed']['fixed_event']):
if (isset($item['Feed']['event_error'])):
?>
<span class="red bold"><?php echo __('Error: Invalid event!');?></span>
<?php
else:
if ($item['Feed']['event_id']):
?>
<a href="<?php echo $baseurl;?>/events/view/<?php echo h($item['Feed']['event_id']); ?>"><?php echo __('Fixed event %s', h($item['Feed']['event_id']));?></a>
<?php
else:
echo __('New fixed event');
endif;
endif;
endif;
else:
echo ' ';
endif;
?>
</td>
<?php
if ($item['Feed']['source_format'] != 'misp'):
?>
<td><span role="img" <?php echo ($item['Feed']['publish'] ? 'class="icon-ok" aria-label="Yes"' : 'class="icon-remove" aria-label="No"'); ?>"></span></td>
<td><span role="img" <?php echo ($item['Feed']['delta_merge'] ? 'class="icon-ok" aria-label="Yes"' : 'class="icon-remove" aria-label="No"'); ?>"></span></td>
<td><span role="img" <?php echo ($item['Feed']['override_ids'] ? 'class="icon-ok" aria-label="Yes"' : 'class="icon-remove" aria-label="No"'); ?>"></span></td>
<?php
else:
?>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td>&nbsp;</td>
<?php
endif;
?>
<td <?php if ($item['Feed']['distribution'] == 0) echo 'class="red"'; ?>>
<?php
echo $item['Feed']['distribution'] == 4 ? '<a href="' . $baseurl . '/sharing_groups/view/' . h($item['SharingGroup']['id']) . '">' . h($item['SharingGroup']['name']) . '</a>' : $distributionLevels[$item['Feed']['distribution']] ;
?>
</td>
<td>
<?php if ($item['Feed']['tag_id']): ?>
<a href="<?php echo $baseurl;?>/events/index/searchtag:<?php echo h($item['Tag']['id']); ?>" class=tag style="background-color:<?php echo h($item['Tag']['colour']);?>;color:<?php echo $this->TextColour->getTextColour($item['Tag']['colour']);?>"><?php echo h($item['Tag']['name']); ?></a>
<?php else: ?>
&nbsp;
<?php endif;?>
</td>
<td class="short"><span role="img" <?php echo ($item['Feed']['lookup_visible'] ? 'class="icon-ok" aria-label="Yes"' : 'class="icon-remove" aria-label="No"'); ?>"></span>
<td class="short action-links <?php echo !empty($item['Feed']['cache_timestamp']) ? 'bold' : 'bold red';?>">
<?php
if (!empty($item['Feed']['cache_timestamp'])):
$units = array('m', 'h', 'd');
$intervals = array(60, 60, 24);
$unit = 's';
$last = time() - $item['Feed']['cache_timestamp'];
foreach ($units as $k => $v) {
if ($last > $intervals[$k]) {
$unit = $v;
$last = floor($last / $intervals[$k]);
} else {
break;
'function' => function($row, $options) {
if (!empty($options['datapath']['event_error'])) {
return false;
}
if (empty($options['datapath']['enabled'])) {
return false;
}
return true;
}
}
echo __('Age: ') . $last . $unit;
else:
echo __('Not cached');
endif;
if ($item['Feed']['caching_enabled'] && $isSiteAdmin):
?>
<a href="<?php echo $baseurl;?>/feeds/cacheFeeds/<?php echo h($item['Feed']['id']); ?>" title="<?php echo __('Cache feed');?>" aria-label="<?php echo __('Cache feed');?>"><span class="black fa fa-memory"></span></a>
<?php
endif;
?>
</td>
<td class="short action-links">
<?php
echo $this->Html->link('', array('action' => 'previewIndex', $item['Feed']['id']), array('class' => 'fa fa-search', 'title' => __('Explore the events remotely'), 'aria-label' => __('Explore the events remotely')));
if (!isset($item['Feed']['event_error']) && $isSiteAdmin) {
if ($item['Feed']['enabled']) echo $this->Html->link('', array('action' => 'fetchFromFeed', $item['Feed']['id']), array('class' => 'fa fa-arrow-circle-down', 'title' => __('Fetch all events'), 'aria-label' => __('Fetch all events')));
}
if ($isSiteAdmin):
?>
<a href="<?php echo $baseurl;?>/feeds/edit/<?php echo h($item['Feed']['id']); ?>" aria-label="<?php echo __('Edit');?>"><span class="black fa fa-edit" title="<?php echo __('Edit');?>">&nbsp;</span></a>
<?php echo $this->Form->postLink('', array('action' => 'delete', h($item['Feed']['id'])), array('class' => 'fa fa-trash', 'title' => __('Delete'), 'aria-label' => __('Delete')), __('Are you sure you want to permanently remove the feed (%s)?', h($item['Feed']['name']))); ?>
<?php endif; ?>
<a href="<?php echo $baseurl;?>/feeds/view/<?php echo h($item['Feed']['id']); ?>.json" title="<?php echo __('Download feed metadata as JSON');?>" download><span class="fa fa-cloud-download-alt black"></span></a>
</td>
</tr><?php
endforeach; ?>
</table>
<p>
<?php
echo $this->Paginator->counter(array(
'format' => __('Page {:page} of {:pages}, showing {:current} records out of {:count} total, starting on record {:start}, ending on {:end}')
)
),
array(
'url' => $baseurl . '/feeds/edit',
'url_params_data_paths' => 'Feed.id',
'icon' => 'edit',
'title' => __('Edit'),
'requirement' => $isSiteAdmin
),
array(
'url' => $baseurl . '/feeds/delete',
'url_params_data_paths' => 'Feed.id',
'icon' => 'trash',
'title' => __('Delete'),
'postLink' => 1,
'postLinkConfirm' => __('Are you sure you want to permanently remove the feed?'),
'requirement' => $isSiteAdmin
),
array(
'url' => $baseurl . '/feeds/view',
'url_params_data_paths' => 'Feed.id',
'url_extension' => 'json',
'icon' => 'cloud-download-alt',
'title' => __('Download feed metadata as JSON')
),
)
)
));
?>
</p>
<div class="pagination">
<ul>
<?php
echo $this->Paginator->prev('&laquo; ' . __('previous'), array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'prev disabled', 'escape' => false, 'disabledTag' => 'span'));
echo $this->Paginator->numbers(array('modulus' => 20, 'separator' => '', 'tag' => 'li', 'currentClass' => 'active', 'currentTag' => 'span'));
echo $this->Paginator->next(__('next') . ' &raquo;', array('tag' => 'li', 'escape' => false), null, array('tag' => 'li', 'class' => 'next disabled', 'escape' => false, 'disabledTag' => 'span'));
?>
</ul>
</div>
</div>
echo '</div>';
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'feeds', 'menuItem' => 'index'));
?>
<script type="text/javascript">
$(document).ready(function(){
popoverStartup();
$('.select').on('change', function() {
listCheckboxesChecked();
});
$('#quickFilterButton').click(function() {
runIndexQuickFilter();
});
$('#quickFilterField').on('keypress', function (e) {
if(e.which === 13) {
runIndexQuickFilter();
}
});
});
</script>
<?php
echo $this->element('/genericElements/SideMenu/side_menu', array('menuList' => 'feeds', 'menuItem' => 'index'));

View File

@ -27,8 +27,6 @@
'button' => __('Filter'),
'placeholder' => __('Enter value to search'),
'data' => '',
'searchKey' => 'value',
'value' => $searchall
)
)
),
@ -101,6 +99,19 @@
<script type="text/javascript">
$(document).ready(function(){
var passedArgsArray = <?php echo $passedArgs; ?>;
var galaxyId = "<?php echo h($galaxy_id); ?>";
if (passedArgsArray['context'] === undefined || passedArgsArray['context'] === "") {
passedArgsArray['context'] = 'all';
}
$('#quickFilterButton').click(function() {
runIndexQuickFilter('/' + galaxyId + '/context:' + passedArgsArray['context']);
});
$('#quickFilterField').on('keypress', function (e) {
if(e.which === 13) {
runIndexQuickFilter('/' + galaxyId + '/context:' + passedArgsArray['context']);
}
});
});
</script>
<?php echo $this->Js->writeBuffer(); ?>

View File

@ -0,0 +1,32 @@
<?php
/*
* Helper used to extract variables from an array based on path
* Used by the index factories
*
*/
App::uses('AppHelper', 'View/Helper');
class DataPathCollectorHelper extends AppHelper {
public function extract($data, $data_path, $options = array())
{
$result = array();
if (!is_array($data_path)) {
$data_path = array($data_path);
}
foreach ($data_path as $path) {
$temp = Hash::extract($data, $path);
if (is_array($temp)) {
if (count($temp) > 1) {
$temp = implode(', ', $temp);
} else {
if (count($temp) > 0) {
$temp = $temp[0];
} else {
$temp = '';
}
}
}
$result[$path] = $temp;
}
return $result;
}
}

Some files were not shown because too many files have changed in this diff Show More