mirror of https://github.com/MISP/MISP
Merge branch '2.4' into tools
commit
9af4a5a50d
|
@ -20,7 +20,7 @@ jobs:
|
|||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
os: [ubuntu-20.04]
|
||||
php: ['7.2', '7.3', '7.4']
|
||||
|
||||
# Steps represent a sequence of tasks that will be executed as part of the job
|
||||
|
@ -66,9 +66,12 @@ jobs:
|
|||
run: |
|
||||
git submodule update --init --recursive
|
||||
sudo apt-get -y update
|
||||
sudo apt-get -y install curl python3 python3-venv virtualenv python3-pip python3-nose python3-redis python3-lxml apache2 libapache2-mod-php$php_version php$php_version-xml php$php_version-mbstring php$php_version-mysql libfuzzy-dev libemail-address-perl libemail-outlook-message-perl
|
||||
sudo pip3 install --upgrade pip setuptools requests pyzmq poetry
|
||||
# Repo is missing for unknown reason
|
||||
LC_ALL=C.UTF-8 sudo apt-add-repository ppa:ondrej/php -y
|
||||
sudo apt-get -y install curl python3 python3-zmq python3-requests python3-pip python3-nose python3-redis python3-lxml apache2 libapache2-mod-php$php_version libfuzzy-dev
|
||||
sudo pip3 install virtualenv # virtualenv must be instaled from pip and not from ubuntu pacckages
|
||||
sudo pip3 install --upgrade -r requirements.txt
|
||||
curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
|
||||
sudo chown $USER:www-data $HOME/.composer
|
||||
pushd app
|
||||
sudo -H -u $USER php composer.phar install --no-progress
|
||||
|
@ -185,7 +188,7 @@ jobs:
|
|||
sudo chmod -R 777 ./tests
|
||||
# Start workers
|
||||
# Dirty install python stuff
|
||||
virtualenv -p python3 ./venv
|
||||
python3 -m virtualenv -p python3 ./venv
|
||||
sudo -E su $USER -c 'app/Console/cake Admin setSetting "MISP.python_bin" "$GITHUB_WORKSPACE/venv/bin/python"'
|
||||
. ./venv/bin/activate
|
||||
pushd cti-python-stix2
|
||||
|
@ -209,9 +212,11 @@ jobs:
|
|||
|
||||
- name: Run tests
|
||||
run: |
|
||||
source $HOME/.poetry/env # enable poetry binary
|
||||
./app/Vendor/bin/parallel-lint --exclude app/Lib/cakephp/ --exclude app/Vendor/ --exclude app/Lib/random_compat/ -e php,ctp app/
|
||||
./app/Vendor/bin/phpunit app/Test/ComplexTypeToolTest.php
|
||||
./app/Vendor/bin/phpunit app/Test/JSONConverterToolTest.php
|
||||
./app/Vendor/bin/phpunit app/Test/CidrToolTest.php
|
||||
|
||||
# Ensure the perms of config files
|
||||
sudo chown -R $USER:www-data `pwd`/app/Config
|
||||
|
|
|
@ -150,6 +150,8 @@ MISPvars () {
|
|||
post_max_size="50M"
|
||||
max_execution_time="300"
|
||||
memory_limit="2048M"
|
||||
session0sid_length="32"
|
||||
session0use_strict_mode="1"
|
||||
|
||||
CAKE="${PATH_TO_MISP}/app/Console/cake"
|
||||
|
||||
|
@ -836,6 +838,8 @@ installDepsPhp70 () {
|
|||
do
|
||||
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
|
||||
done
|
||||
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
|
||||
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
|
||||
}
|
||||
|
||||
# Install Php 7.3 deps
|
||||
|
@ -1208,6 +1212,8 @@ installDepsPhp74 () {
|
|||
do
|
||||
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
|
||||
done
|
||||
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
|
||||
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
|
||||
}
|
||||
|
||||
# Install Php 7.3 deps
|
||||
|
@ -1260,6 +1266,8 @@ installDepsPhp72 () {
|
|||
do
|
||||
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
|
||||
done
|
||||
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
|
||||
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
|
||||
}
|
||||
|
||||
# Install Php 7.0 dependencies
|
||||
|
@ -1280,6 +1288,8 @@ installDepsPhp70 () {
|
|||
do
|
||||
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
|
||||
done
|
||||
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
|
||||
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
|
||||
}
|
||||
|
||||
prepareDB () {
|
||||
|
@ -1536,6 +1546,7 @@ coreCAKE () {
|
|||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.email" "$GPG_EMAIL_ADDRESS"
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.homedir" "$PATH_TO_MISP/.gnupg"
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.password" "$GPG_PASSPHRASE"
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.obscure_subject" true
|
||||
# FIXME: what if we have not gpg binary but a gpg2 one?
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "GnuPG.binary" "$(which gpg)"
|
||||
|
||||
|
@ -1565,6 +1576,9 @@ coreCAKE () {
|
|||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Plugin.Sightings_range" 365
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Plugin.Sightings_sighting_db_enable" false
|
||||
|
||||
# Plugin Enrichment hover defaults
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Plugin.Enrichment_hover_popover_only" false
|
||||
|
||||
# Plugin CustomAuth tuneable
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Plugin.CustomAuth_disable_logout" false
|
||||
|
||||
|
@ -1599,8 +1613,10 @@ coreCAKE () {
|
|||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.passwordResetText" "Dear MISP user,\\n\\nA password reset has been triggered for your account. Use the below provided temporary password to log into MISP at \$misp, where you will be prompted to manually change your password to something of your own choice.\\n\\nUsername: \$username\\nYour temporary password: \$password\\n\\nIf you have any questions, don't hesitate to contact us at: \$contact.\\n\\nBest regards,\\nYour \$org MISP support team"
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.enableEventBlocklisting" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.enableOrgBlocklisting" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_client_ip" false
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_client_ip" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_auth" false
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_user_ips" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.log_user_ips_authkeys" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.disableUserSelfManagement" false
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.disable_user_login_change" false
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "MISP.disable_user_password_change" false
|
||||
|
@ -1633,6 +1649,16 @@ coreCAKE () {
|
|||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.password_policy_complexity" '/^((?=.*\d)|(?=.*\W+))(?![\n])(?=.*[A-Z])(?=.*[a-z]).*$|.{16,}/'
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.self_registration_message" "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."
|
||||
|
||||
# Appease the security audit, #hardening
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.disable_browser_cache" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.check_sec_fetch_site_header" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.csp_enforce" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.advanced_authkeys" true
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.do_not_log_authkeys" true
|
||||
|
||||
# Appease the security audit, #loggin
|
||||
$SUDO_WWW $RUN_PHP -- $CAKE Admin setSetting "Security.username_in_response_header" true
|
||||
|
||||
# It is possible to updateMISP too, only here for reference how to to that on the CLI.
|
||||
## $SUDO_WWW $RUN_PHP -- $CAKE Admin updateMISP
|
||||
|
||||
|
@ -2032,7 +2058,6 @@ enableReposRHEL () {
|
|||
sudo subscription-manager refresh
|
||||
sudo subscription-manager repos --enable rhel-7-server-optional-rpms
|
||||
sudo subscription-manager repos --enable rhel-7-server-extras-rpms
|
||||
sudo subscription-manager repos --enable rhel-server-rhscl-7-rpms
|
||||
}
|
||||
|
||||
centosEPEL () {
|
||||
|
@ -2046,38 +2071,52 @@ centosEPEL () {
|
|||
|
||||
enableEPEL () {
|
||||
sudo yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -y
|
||||
sudo yum install http://rpms.remirepo.net/enterprise/remi-release-7.rpm -y
|
||||
sudo yum install yum-utils -y
|
||||
sudo yum-config-manager --enable remi-php74
|
||||
}
|
||||
|
||||
yumInstallCoreDeps () {
|
||||
# Install the dependencies:
|
||||
sudo yum install gcc git zip rh-git218 \
|
||||
httpd24 \
|
||||
sudo yum install gcc git zip \
|
||||
mod_ssl \
|
||||
rh-redis32 \
|
||||
rh-mariadb102 \
|
||||
redis \
|
||||
libxslt-devel zlib-devel ssdeep-devel -y
|
||||
|
||||
# Enable and start redis
|
||||
sudo systemctl enable --now rh-redis32-redis.service
|
||||
sudo systemctl enable --now redis.service
|
||||
|
||||
# Install MariaDB
|
||||
sudo yum install wget -y
|
||||
wget https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
|
||||
chmod +x mariadb_repo_setup
|
||||
sudo ./mariadb_repo_setup
|
||||
rm mariadb_repo_setup
|
||||
sudo yum install MariaDB-server -y
|
||||
|
||||
WWW_USER="apache"
|
||||
SUDO_WWW="sudo -H -u $WWW_USER"
|
||||
RUN_PHP="/usr/bin/scl enable rh-php72"
|
||||
PHP_INI="/etc/opt/rh/rh-php72/php.ini"
|
||||
# Install PHP 7.2 from SCL, see https://www.softwarecollections.org/en/scls/rhscl/rh-php72/
|
||||
sudo yum install rh-php72 rh-php72-php-fpm rh-php72-php-devel \
|
||||
rh-php72-php-mysqlnd \
|
||||
rh-php72-php-mbstring \
|
||||
rh-php72-php-xml \
|
||||
rh-php72-php-bcmath \
|
||||
rh-php72-php-opcache \
|
||||
rh-php72-php-zip \
|
||||
rh-php72-php-gd -y
|
||||
PHP_INI="/etc/opt/remi/php74/php.ini"
|
||||
# Install PHP 7.4 from Remi's repo, see https://rpms.remirepo.net/enterprise/7/php74/x86_64/repoview/
|
||||
sudo yum install php74 php74-php-fpm php74-php-devel \
|
||||
php74-php-mysqlnd \
|
||||
php74-php-mbstring \
|
||||
php74-php-xml \
|
||||
php74-php-bcmath \
|
||||
php74-php-opcache \
|
||||
php74-php-zip \
|
||||
php74-php-pear \
|
||||
php74-php-brotli \
|
||||
php74-php-intl \
|
||||
php74-php-gd -y
|
||||
|
||||
# cake has php baked in, thus we link to it
|
||||
sudo ln -s /usr/bin/php74 /usr/bin/php
|
||||
|
||||
# Python 3.6 is now available in RHEL 7.7 base
|
||||
sudo yum install python3 python3-devel -y
|
||||
|
||||
sudo systemctl enable --now rh-php72-php-fpm.service
|
||||
sudo systemctl enable --now php74-php-fpm.service
|
||||
}
|
||||
|
||||
installCoreRHEL () {
|
||||
|
@ -2110,17 +2149,17 @@ installCoreRHEL () {
|
|||
cd $PATH_TO_MISP/app/files/scripts
|
||||
$SUDO_WWW git clone https://github.com/CybOXProject/python-cybox.git
|
||||
$SUDO_WWW git clone https://github.com/STIXProject/python-stix.git
|
||||
$SUDO_WWW git clone --branch master --single-branch https://github.com/lief-project/LIEF.git lief
|
||||
##$SUDO_WWW git clone --branch master --single-branch https://github.com/lief-project/LIEF.git lief
|
||||
$SUDO_WWW git clone https://github.com/CybOXProject/mixbox.git
|
||||
|
||||
# If you umask is has been changed from the default, it is a good idea to reset it to 0022 before installing python modules
|
||||
UMASK=$(umask)
|
||||
umask 0022
|
||||
|
||||
|
||||
cd $PATH_TO_MISP/app/files/scripts/python-cybox
|
||||
$SUDO_WWW git config core.filemode false
|
||||
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install .
|
||||
|
||||
|
||||
cd $PATH_TO_MISP/app/files/scripts/python-stix
|
||||
$SUDO_WWW git config core.filemode false
|
||||
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install .
|
||||
|
@ -2143,40 +2182,8 @@ installCoreRHEL () {
|
|||
# install redis
|
||||
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install -U redis
|
||||
|
||||
# lief needs manual compilation
|
||||
sudo yum install devtoolset-7 cmake3 cppcheck libcxx-devel -y
|
||||
|
||||
cd $PATH_TO_MISP/app/files/scripts/lief
|
||||
$SUDO_WWW git config core.filemode false
|
||||
$SUDO_WWW mkdir build
|
||||
cd build
|
||||
$SUDO_WWW scl enable devtoolset-7 "bash -c 'cmake3 \
|
||||
-DLIEF_PYTHON_API=on \
|
||||
-DPYTHON_VERSION=3.6 \
|
||||
-DPYTHON_EXECUTABLE=$PATH_TO_MISP/venv/bin/python \
|
||||
-DLIEF_DOC=off \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
..'"
|
||||
$SUDO_WWW make -j3 pyLIEF
|
||||
|
||||
if [ $? == 2 ]; then
|
||||
# In case you get "internal compiler error: Killed (program cc1plus)"
|
||||
# You ran out of memory.
|
||||
# Create some swap
|
||||
sudo dd if=/dev/zero of=/var/swap.img bs=1024k count=4000
|
||||
sudo mkswap /var/swap.img
|
||||
sudo swapon /var/swap.img
|
||||
# And compile again
|
||||
$SUDO_WWW make -j3 pyLIEF
|
||||
sudo swapoff /var/swap.img
|
||||
sudo rm /var/swap.img
|
||||
fi
|
||||
|
||||
# The following adds a PYTHONPATH to where the pyLIEF module has been compiled
|
||||
echo $PATH_TO_MISP/app/files/scripts/lief/build/api/python |$SUDO_WWW tee $PATH_TO_MISP/venv/lib/python3.6/site-packages/lief.pth
|
||||
|
||||
# install magic, pydeep
|
||||
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install -U python-magic git+https://github.com/kbandla/pydeep.git plyara
|
||||
# install magic, pydeep, lief
|
||||
$SUDO_WWW $PATH_TO_MISP/venv/bin/pip install -U python-magic git+https://github.com/kbandla/pydeep.git plyara lief
|
||||
|
||||
# install PyMISP
|
||||
cd $PATH_TO_MISP/PyMISP
|
||||
|
@ -2184,7 +2191,7 @@ installCoreRHEL () {
|
|||
|
||||
# FIXME: Remove libfaup etc once the egg has the library baked-in
|
||||
# BROKEN: This needs to be tested on RHEL/CentOS
|
||||
##sudo apt-get install cmake libcaca-dev liblua5.3-dev -y
|
||||
sudo yum install libcaca-devel cmake3 -y
|
||||
cd /tmp
|
||||
[[ ! -d "faup" ]] && $SUDO_CMD git clone https://github.com/stricaud/faup.git faup
|
||||
[[ ! -d "gtcaca" ]] && $SUDO_CMD git clone https://github.com/stricaud/gtcaca.git gtcaca
|
||||
|
@ -2192,21 +2199,20 @@ installCoreRHEL () {
|
|||
cd gtcaca
|
||||
$SUDO_CMD mkdir -p build
|
||||
cd build
|
||||
$SUDO_CMD cmake .. && $SUDO_CMD make
|
||||
$SUDO_CMD cmake3 .. && $SUDO_CMD make
|
||||
sudo make install
|
||||
cd ../../faup
|
||||
$SUDO_CMD mkdir -p build
|
||||
cd build
|
||||
$SUDO_CMD cmake .. && $SUDO_CMD make
|
||||
$SUDO_CMD cmake3 .. && $SUDO_CMD make
|
||||
sudo make install
|
||||
sudo ldconfig
|
||||
|
||||
# Enable dependencies detection in the diagnostics page
|
||||
# This allows MISP to detect GnuPG, the Python modules' versions and to read the PHP settings.
|
||||
# The LD_LIBRARY_PATH setting is needed for rh-git218 to work
|
||||
echo "env[PATH] = /opt/rh/rh-git218/root/usr/bin:/opt/rh/rh-redis32/root/usr/bin:/opt/rh/rh-php72/root/usr/bin:/usr/local/bin:/usr/bin:/bin" |sudo tee -a /etc/opt/rh/rh-php72/php-fpm.d/www.conf
|
||||
sudo sed -i.org -e 's/^;\(clear_env = no\)/\1/' /etc/opt/rh/rh-php72/php-fpm.d/www.conf
|
||||
sudo systemctl restart rh-php72-php-fpm.service
|
||||
echo "env[PATH] = /usr/local/bin:/usr/bin:/bin" |sudo tee -a /etc/opt/remi/php74/php-fpm.d/www.conf
|
||||
sudo sed -i.org -e 's/^;\(clear_env = no\)/\1/' /etc/opt/remi/php74/php-fpm.d/www.conf
|
||||
sudo systemctl restart php74-php-fpm.service
|
||||
umask $UMASK
|
||||
}
|
||||
|
||||
|
@ -2218,31 +2224,20 @@ installCake_RHEL ()
|
|||
cd $PATH_TO_MISP/app
|
||||
# Update composer.phar (optional)
|
||||
#EXPECTED_SIGNATURE="$(wget -q -O - https://composer.github.io/installer.sig)"
|
||||
#$SUDO_WWW $RUN_PHP -- php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||
#$SUDO_WWW $RUN_PHP -- php -r "if (hash_file('SHA384', 'composer-setup.php') === '$EXPECTED_SIGNATURE') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
|
||||
#$SUDO_WWW $RUN_PHP "php composer-setup.php"
|
||||
#$SUDO_WWW $RUN_PHP -- php -r "unlink('composer-setup.php');"
|
||||
$SUDO_WWW $RUN_PHP "php composer.phar install"
|
||||
#$SUDO_WWW php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
|
||||
#$SUDO_WWW php -r "if (hash_file('SHA384', 'composer-setup.php') === '$EXPECTED_SIGNATURE') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;"
|
||||
#$SUDO_WWW php composer-setup.php
|
||||
#$SUDO_WWW php -r "unlink('composer-setup.php');"
|
||||
$SUDO_WWW php composer.phar install
|
||||
|
||||
## sudo yum install php-redis -y
|
||||
sudo scl enable rh-php72 'pecl channel-update pecl.php.net'
|
||||
sudo scl enable rh-php72 'yes no|pecl install redis'
|
||||
echo "extension=redis.so" |sudo tee /etc/opt/rh/rh-php72/php.d/99-redis.ini
|
||||
sudo yum install php74-php-pecl-redis php74-php-pecl-ssdeep php74-php-pecl-gnupg -y
|
||||
|
||||
sudo ln -s /usr/lib64/libfuzzy.so /usr/lib/libfuzzy.so
|
||||
sudo scl enable rh-php72 'pecl install ssdeep'
|
||||
echo "extension=ssdeep.so" |sudo tee /etc/opt/rh/rh-php72/php.d/99-ssdeep.ini
|
||||
|
||||
# Install gnupg extension
|
||||
sudo yum install gpgme-devel -y
|
||||
sudo scl enable rh-php72 'pecl install gnupg'
|
||||
echo "extension=gnupg.so" |sudo tee /etc/opt/rh/rh-php72/php.d/99-gnupg.ini
|
||||
sudo systemctl restart rh-php72-php-fpm.service
|
||||
sudo systemctl restart php74-php-fpm.service
|
||||
|
||||
# If you have not yet set a timezone in php.ini
|
||||
echo 'date.timezone = "Asia/Tokyo"' |sudo tee /etc/opt/rh/rh-php72/php.d/timezone.ini
|
||||
echo 'date.timezone = "Asia/Tokyo"' |sudo tee /etc/opt/remi/php74/php.d/timezone.ini
|
||||
|
||||
# Recommended: Change some PHP settings in /etc/opt/rh/rh-php72/php.ini
|
||||
# Recommended: Change some PHP settings in /etc/opt/remi/php74/php.ini
|
||||
# max_execution_time = 300
|
||||
# memory_limit = 2048M
|
||||
# upload_max_filesize = 50M
|
||||
|
@ -2251,71 +2246,40 @@ installCake_RHEL ()
|
|||
do
|
||||
sudo sed -i "s/^\($key\).*/\1 = $(eval echo \${$key})/" $PHP_INI
|
||||
done
|
||||
sudo systemctl restart rh-php72-php-fpm.service
|
||||
sudo sed -i "s/^\(session.sid_length\).*/\1 = $(eval echo \${session0sid_length})/" $PHP_INI
|
||||
sudo sed -i "s/^\(session.use_strict_mode\).*/\1 = $(eval echo \${session0use_strict_mode})/" $PHP_INI
|
||||
sudo systemctl restart php74-php-fpm.service
|
||||
|
||||
# To use the scheduler worker for scheduled tasks, do the following:
|
||||
sudo cp -fa $PATH_TO_MISP/INSTALL/setup/config.php $PATH_TO_MISP/app/Plugin/CakeResque/Config/config.php
|
||||
}
|
||||
|
||||
prepareDB_RHEL () {
|
||||
RUN_MYSQL="/usr/bin/scl enable rh-mariadb102"
|
||||
# Enable, start and secure your mysql database server
|
||||
sudo systemctl enable --now rh-mariadb102-mariadb.service
|
||||
echo [mysqld] |sudo tee /etc/opt/rh/rh-mariadb102/my.cnf.d/bind-address.cnf
|
||||
echo bind-address=127.0.0.1 |sudo tee -a /etc/opt/rh/rh-mariadb102/my.cnf.d/bind-address.cnf
|
||||
sudo systemctl restart rh-mariadb102-mariadb
|
||||
sudo systemctl enable --now mariadb.service
|
||||
echo [mysqld] |sudo tee /etc/my.cnf.d/bind-address.cnf
|
||||
echo bind-address=127.0.0.1 |sudo tee -a /etc/my.cnf.d/bind-address.cnf
|
||||
sudo systemctl restart mariadb
|
||||
|
||||
sudo yum install expect -y
|
||||
# Kill the anonymous users
|
||||
sudo mysql -h $DBHOST -e "DROP USER IF EXISTS ''@'localhost'"
|
||||
# Because our hostname varies we'll use some Bash magic here.
|
||||
sudo mysql -h $DBHOST -e "DROP USER IF EXISTS ''@'$(hostname)'"
|
||||
# Kill off the demo database
|
||||
sudo mysql -h $DBHOST -e "DROP DATABASE IF EXISTS test"
|
||||
# No root remote logins
|
||||
sudo mysql -h $DBHOST -e "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')"
|
||||
# Make sure that NOBODY can access the server without a password
|
||||
sudo mysqladmin -h $DBHOST -u "${DBUSER_ADMIN}" password "${DBPASSWORD_ADMIN}"
|
||||
# Make our changes take effect
|
||||
sudo mysql -h $DBHOST -e "FLUSH PRIVILEGES"
|
||||
|
||||
## The following needs some thoughts about scl enable foo
|
||||
#if [[ ! -e /var/opt/rh/rh-mariadb102/lib/mysql/misp/users.ibd ]]; then
|
||||
|
||||
# We ask interactively your password if not run as root
|
||||
pw=""
|
||||
if [[ "$EUID" -ne 0 ]]; then
|
||||
read -s -p "Enter sudo password: " pw
|
||||
fi
|
||||
|
||||
expect -f - <<-EOF
|
||||
set timeout 10
|
||||
|
||||
spawn sudo scl enable rh-mariadb102 mysql_secure_installation
|
||||
expect {
|
||||
"*sudo*" {
|
||||
send "$pw\r"
|
||||
exp_continue
|
||||
}
|
||||
"Enter current password for root (enter for none):" {
|
||||
send -- "\r"
|
||||
}
|
||||
}
|
||||
expect "Set root password?"
|
||||
send -- "y\r"
|
||||
expect "New password:"
|
||||
send -- "${DBPASSWORD_ADMIN}\r"
|
||||
expect "Re-enter new password:"
|
||||
send -- "${DBPASSWORD_ADMIN}\r"
|
||||
expect "Remove anonymous users?"
|
||||
send -- "y\r"
|
||||
expect "Disallow root login remotely?"
|
||||
send -- "y\r"
|
||||
expect "Remove test database and access to it?"
|
||||
send -- "y\r"
|
||||
expect "Reload privilege tables now?"
|
||||
send -- "y\r"
|
||||
expect eof
|
||||
EOF
|
||||
|
||||
sudo yum remove tcl expect -y
|
||||
|
||||
sudo systemctl restart rh-mariadb102-mariadb
|
||||
|
||||
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e 'CREATE DATABASE $DBNAME;'"
|
||||
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e \"GRANT USAGE on *.* to $DBUSER_MISP@localhost IDENTIFIED by '$DBPASSWORD_MISP';\""
|
||||
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e \"GRANT ALL PRIVILEGES on $DBNAME.* to '$DBUSER_MISP'@'localhost';\""
|
||||
scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_ADMIN -p$DBPASSWORD_ADMIN -e 'FLUSH PRIVILEGES;'"
|
||||
|
||||
$SUDO_WWW cat $PATH_TO_MISP/INSTALL/MYSQL.sql | sudo scl enable rh-mariadb102 "mysql -h $DBHOST -u $DBUSER_MISP -p$DBPASSWORD_MISP $DBNAME"
|
||||
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "CREATE DATABASE ${DBNAME};"
|
||||
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "CREATE USER '${DBUSER_MISP}'@'localhost' IDENTIFIED BY '${DBPASSWORD_MISP}';"
|
||||
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "GRANT USAGE ON *.* to '${DBUSER_MISP}'@'localhost';"
|
||||
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "GRANT ALL PRIVILEGES on ${DBNAME}.* to '${DBUSER_MISP}'@'localhost';"
|
||||
sudo mysql -h $DBHOST -u "${DBUSER_ADMIN}" -p"${DBPASSWORD_ADMIN}" -e "FLUSH PRIVILEGES;"
|
||||
# Import the empty MISP database from MYSQL.sql
|
||||
${SUDO_WWW} cat ${PATH_TO_MISP}/INSTALL/MYSQL.sql | mysql -h $DBHOST -u "${DBUSER_MISP}" -p"${DBPASSWORD_MISP}" ${DBNAME}
|
||||
}
|
||||
|
||||
apacheConfig_RHEL () {
|
||||
|
@ -2350,13 +2314,12 @@ apacheConfig_RHEL () {
|
|||
sudo chcon -t httpd_sys_rw_content_t $PATH_TO_MISP/app/files/scripts/tmp
|
||||
sudo chcon -t httpd_sys_rw_content_t $PATH_TO_MISP/app/Plugin/CakeResque/tmp
|
||||
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/Console/cake
|
||||
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/Console/worker/*.sh
|
||||
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/*.py
|
||||
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/*/*.py
|
||||
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/lief/build/api/python/lief.so
|
||||
sudo sh -c "chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/Console/worker/*.sh"
|
||||
sudo sh -c "chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/*.py"
|
||||
sudo sh -c "chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/files/scripts/*/*.py"
|
||||
sudo chcon -t httpd_sys_script_exec_t $PATH_TO_MISP/app/Vendor/pear/crypt_gpg/scripts/crypt-gpg-pinentry
|
||||
sudo chcon -R -t bin_t $PATH_TO_MISP/venv/bin/*
|
||||
find $PATH_TO_MISP/venv -type f -name "*.so*" -or -name "*.so.*" | xargs sudo chcon -t lib_t
|
||||
sudo sh -c "chcon -R -t bin_t $PATH_TO_MISP/venv/bin/*"
|
||||
sudo find $PATH_TO_MISP/venv -type f -name "*.so*" -or -name "*.so.*" | xargs sudo chcon -t lib_t
|
||||
# Only run these if you want to be able to update MISP from the web interface
|
||||
sudo chcon -R -t httpd_sys_rw_content_t $PATH_TO_MISP/.git
|
||||
sudo chcon -R -t httpd_sys_rw_content_t $PATH_TO_MISP/app/tmp
|
||||
|
@ -2478,6 +2441,7 @@ configMISP_RHEL () {
|
|||
|
||||
# If you want to be able to change configuration parameters from the webinterface:
|
||||
sudo chown $WWW_USER:$WWW_USER $PATH_TO_MISP/app/Config/config.php
|
||||
sudo chmod 660 $PATH_TO_MISP/app/Config/config.php
|
||||
sudo chcon -t httpd_sys_rw_content_t $PATH_TO_MISP/app/Config/config.php
|
||||
|
||||
# Generate a GPG encryption key.
|
||||
|
@ -2512,13 +2476,13 @@ EOF
|
|||
configWorkersRHEL () {
|
||||
echo "[Unit]
|
||||
Description=MISP background workers
|
||||
After=rh-mariadb102-mariadb.service rh-redis32-redis.service rh-php72-php-fpm.service
|
||||
After=mariadb.service redis.service php74-php-fpm.service
|
||||
|
||||
[Service]
|
||||
Type=forking
|
||||
User=$WWW_USER
|
||||
Group=$WWW_USER
|
||||
ExecStart=/usr/bin/scl enable rh-php72 rh-redis32 rh-mariadb102 $PATH_TO_MISP/app/Console/worker/start.sh
|
||||
ExecStart=$PATH_TO_MISP/app/Console/worker/start.sh
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
|
||||
|
@ -3288,6 +3252,7 @@ x86_64-debian-stretch
|
|||
x86_64-debian-buster
|
||||
x86_64-ubuntu-bionic
|
||||
x86_64-ubuntu-focal
|
||||
x86_64-ubuntu-hirsute
|
||||
x86_64-kali-2020.4
|
||||
armv6l-raspbian-stretch
|
||||
armv7l-raspbian-stretch
|
||||
|
@ -3322,6 +3287,11 @@ if [[ "${FLAVOUR}" == "ubuntu" ]]; then
|
|||
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
|
||||
installSupported PHP="7.4" && exit || exit
|
||||
fi
|
||||
if [[ "${RELEASE}" == "21.04" ]]; then
|
||||
echo "Install on Ubuntu 21.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"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
; Generated by RHash v1.3.9 on 2021-02-03 at 15:13.46
|
||||
; Generated by RHash v1.3.9 on 2021-03-25 at 12:56.17
|
||||
; Written by Kravchenko Aleksey (Akademgorodok) - http://rhash.sf.net/
|
||||
;
|
||||
; 137499 15:13.46 2021-02-03 INSTALL.sh
|
||||
INSTALL.sh 5645164D7C2701EC0E0FF7D33CD8263D41B27947 803115D518C0EF187B041B942B0298CEAF329F4ADD07DB405B508D6E7ABB5D45 6B8019972E761EDC6E8CD1335E8B280376315DD762F6C98AE36C149D7960A0FC5D3C6E3F228DCB2949BDCB747942B557 ABE5D6541D23895E863BAB8BE306E54058897823EA703E6A8A225097ED8CA24F365441E8E98851576E5289B34D81C351C1A1A2520A0865BFE0D37AD77D018282
|
||||
; 137691 12:56.17 2021-03-25 INSTALL.sh
|
||||
INSTALL.sh 5694A8F77384677CA3DC84FB5A5F3C06D6FFF03F 5F3A9B04BEEE449E96F4A698F3FA497390E46E2AD1DBDDED37F54E29FED76221 ABCB35B681F9A5E3568A055465976EC0996C0CC2FD8A39384E05D90413D8300B7356AAE23A540912D7D9907BECCDCD9F 54712D3100DAF92EA6201D86941222F6877B772533D048C8F758332D9B45418B64AA767A0D78C8A39E491BE114F139FFEF5A2E0436EA8503AA593556E56C0992
|
||||
|
|
|
@ -1 +1 @@
|
|||
5645164d7c2701ec0e0ff7d33cd8263d41b27947 INSTALL.sh
|
||||
5694a8f77384677ca3dc84fb5a5f3c06d6fff03f INSTALL.sh
|
||||
|
|
|
@ -1 +1 @@
|
|||
803115d518c0ef187b041b942b0298ceaf329f4add07db405b508d6e7abb5d45 INSTALL.sh
|
||||
5f3a9b04beee449e96f4a698f3fa497390e46e2ad1dbdded37f54e29fed76221 INSTALL.sh
|
||||
|
|
|
@ -1 +1 @@
|
|||
6b8019972e761edc6e8cd1335e8b280376315dd762f6c98ae36c149d7960a0fc5d3c6e3f228dcb2949bdcb747942b557 INSTALL.sh
|
||||
abcb35b681f9a5e3568a055465976ec0996c0cc2fd8a39384e05d90413d8300b7356aae23a540912d7d9907beccdcd9f INSTALL.sh
|
||||
|
|
|
@ -1 +1 @@
|
|||
abe5d6541d23895e863bab8be306e54058897823ea703e6a8a225097ed8ca24f365441e8e98851576e5289b34d81c351c1a1a2520a0865bfe0d37ad77d018282 INSTALL.sh
|
||||
54712d3100daf92ea6201d86941222f6877b772533d048c8f758332d9b45418b64aa767a0d78c8a39e491be114f139ffef5a2e0436ea8503aa593556e56c0992 INSTALL.sh
|
||||
|
|
|
@ -88,6 +88,7 @@
|
|||
## 2_logRotation.sh ##
|
||||
## 2_backgroundWorkers.sh ##
|
||||
## 3_misp-modules.sh ##
|
||||
## 3_misp-modules-cake.sh ##
|
||||
## 4_misp-dashboard.sh ##
|
||||
## 4_misp-dashboard-cake.sh ##
|
||||
## 5_mail_to_misp.sh ##
|
||||
|
@ -147,12 +148,12 @@ generateInstaller () {
|
|||
cp ../INSTALL.tpl.sh .
|
||||
|
||||
# Pull code snippets out of Main Install Documents
|
||||
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
|
||||
for f in `echo INSTALL.ubuntu2004.md INSTALL.ubuntu1804.md xINSTALL.debian10.md xINSTALL.tsurugi.md INSTALL.rhel7.md INSTALL.rhel8.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 misp-modules-centos.md`; do
|
||||
for f in `echo globalVariables.md mail_to_misp-debian.md MISP_CAKE_init.md misp-dashboard-debian.md misp-dashboard-centos.md misp-dashboard-cake.md misp-modules-debian.md misp-modules-centos.md misp-modules-cake.md gnupg.md ssdeep-debian.md sudo_etckeeper.md supportFunctions.md viper-debian.md`; do
|
||||
xsnippet . ../../docs/generic/${f}
|
||||
done
|
||||
|
||||
|
@ -181,6 +182,7 @@ generateInstaller () {
|
|||
perl -pe 's/^## 2_logRotation.sh ##/`cat 2_logRotation.sh`/ge' -i INSTALL.tpl.sh
|
||||
perl -pe 's/^## 2_backgroundWorkers.sh ##/`cat 2_backgroundWorkers.sh`/ge' -i INSTALL.tpl.sh
|
||||
perl -pe 's/^## 2_core-cake.sh ##/`cat 2_core-cake.sh`/ge' -i INSTALL.tpl.sh
|
||||
perl -pe 's/^## 3_misp-modules-cake.sh ##/`cat 3_misp-modules-cake.sh`/ge' -i INSTALL.tpl.sh
|
||||
perl -pe 's/^## 3_misp-modules.sh ##/`cat 3_misp-modules.sh`/ge' -i INSTALL.tpl.sh
|
||||
perl -pe 's/^## 4_misp-dashboard-cake.sh ##/`cat 4_misp-dashboard-cake.sh`/ge' -i INSTALL.tpl.sh
|
||||
perl -pe 's/^## 4_misp-dashboard.sh ##/`cat 4_misp-dashboard.sh`/ge' -i INSTALL.tpl.sh
|
||||
|
@ -661,7 +663,7 @@ installMISPRHEL () {
|
|||
fi
|
||||
|
||||
debug "Enabling Extras Repos (SCL)"
|
||||
if [[ "${FLAVOUR}" == "rhel" ]]; then
|
||||
if [[ "${DISTRI}" == "rhel7" ]]; then
|
||||
sudo subscription-manager register --auto-attach
|
||||
enableReposRHEL
|
||||
enableEPEL
|
||||
|
@ -782,12 +784,19 @@ fi
|
|||
SUPPORT_MAP="
|
||||
x86_64-centos-7
|
||||
x86_64-rhel-7
|
||||
x86_64-fedora-30
|
||||
x86_64-centos-8
|
||||
x86_64-rhel-8
|
||||
x86_64-fedora-33
|
||||
x86_64-debian-stretch
|
||||
x86_64-debian-buster
|
||||
x86_64-ubuntu-bionic
|
||||
x86_64-ubuntu-focal
|
||||
x86_64-ubuntu-hirsute
|
||||
x86_64-kali-2020.4
|
||||
x86_64-kali-2021.1
|
||||
x86_64-kali-2021.2
|
||||
x86_64-kali-2021.3
|
||||
x86_64-kali-2021.4
|
||||
armv6l-raspbian-stretch
|
||||
armv7l-raspbian-stretch
|
||||
armv7l-debian-jessie
|
||||
|
@ -821,6 +830,11 @@ if [[ "${FLAVOUR}" == "ubuntu" ]]; then
|
|||
echo "Please report bugs/issues here: https://github.com/MISP/MISP/issues"
|
||||
installSupported PHP="7.4" && exit || exit
|
||||
fi
|
||||
if [[ "${RELEASE}" == "21.04" ]]; then
|
||||
echo "Install on Ubuntu 21.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"
|
||||
|
@ -887,7 +901,7 @@ if [[ "${FLAVOUR}" == "kali" ]]; then
|
|||
fi
|
||||
|
||||
# If RHEL/CentOS is detected, run appropriate script
|
||||
if [[ "${FLAVOUR}" == "rhel" ]] || [[ "${FLAVOUR}" == "centos" ]]; then
|
||||
if [[ "${FLAVOUR}" == "rhel" ]] || [[ "${FLAVOUR}" == "centos" ]] || [[ "${FLAVOUR}" == "fedora" ]]; then
|
||||
installMISPRHEL
|
||||
echo "Installation done !"
|
||||
exit
|
||||
|
|
|
@ -243,7 +243,8 @@ CREATE TABLE IF NOT EXISTS event_reports (
|
|||
`deleted` tinyint(1) NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT u_uuid UNIQUE (uuid),
|
||||
INDEX `name` (`name`)
|
||||
INDEX `name` (`name`),
|
||||
INDEX `event_id` (`event_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
|
||||
-- --------------------------------------------------------
|
||||
|
@ -455,7 +456,7 @@ CREATE TABLE IF NOT EXISTS `galaxy_clusters` (
|
|||
`collection_uuid` varchar(255) COLLATE utf8_bin NOT NULL,
|
||||
`type` varchar(255) COLLATE utf8_bin NOT NULL,
|
||||
`value` text COLLATE utf8_bin NOT NULL,
|
||||
`tag_name` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '',
|
||||
`tag_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
|
||||
`description` text COLLATE utf8_bin NOT NULL,
|
||||
`galaxy_id` int(11) NOT NULL,
|
||||
`source` varchar(255) COLLATE utf8_bin NOT NULL DEFAULT '',
|
||||
|
|
|
@ -7,10 +7,10 @@ require {
|
|||
type httpd_sys_content_t;
|
||||
type httpd_sys_rw_content_t;
|
||||
class dir { ioctl read getattr lock search open remove_name };
|
||||
class file { unlink write };
|
||||
class file { unlink write rename };
|
||||
}
|
||||
#============= logrotate_t ==============
|
||||
allow logrotate_t httpd_sys_content_t:dir { ioctl read getattr lock search open };
|
||||
allow logrotate_t httpd_sys_rw_content_t:dir { ioctl read getattr lock search open };
|
||||
allow httpd_t httpd_log_t:dir remove_name;
|
||||
allow { httpd_t httpd_sys_script_t } httpd_log_t:file { unlink write };
|
||||
allow { httpd_t httpd_sys_script_t } httpd_log_t:file { unlink write rename };
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
../docs/xINSTALL.centos6.md
|
|
@ -1 +0,0 @@
|
|||
../docs/xINSTALL.debian9-postgresql.md
|
|
@ -1 +0,0 @@
|
|||
../docs/xINSTALL.debian9.md
|
|
@ -1 +0,0 @@
|
|||
../docs/xINSTALL.ubuntu1804.with.webmin.md
|
2
PyMISP
2
PyMISP
|
@ -1 +1 @@
|
|||
Subproject commit 5b97b7d0158906cd0f646a7273a3ca5b1828cd15
|
||||
Subproject commit 51edb8ab33c5ee6bd3b9b05ea5809299f37c4fbe
|
|
@ -20,6 +20,10 @@ MISP - Threat Intelligence Sharing Platform
|
|||
<td>Twitter</td>
|
||||
<td><a href="https://twitter.com/MISPProject"><img src="https://img.shields.io/twitter/follow/MISPProject.svg?style=social&label=Follow" /></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Localization</td>
|
||||
<td><a href="https://crowdin.com/project/misp"><img src="https://badges.crowdin.net/misp/localized.svg" /></a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Contributors</td>
|
||||
<td><img src="https://img.shields.io/github/contributors/MISP/MISP.svg" /></td>
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"major":2, "minor":4, "hotfix":137}
|
||||
{"major":2, "minor":4, "hotfix":141}
|
||||
|
|
|
@ -169,15 +169,37 @@ class AdminShell extends AppShell
|
|||
}
|
||||
}
|
||||
|
||||
# FIXME: Make Taxonomy->update() return a status string on API if successful
|
||||
public function updateTaxonomies()
|
||||
{
|
||||
$this->ConfigLoad->execute();
|
||||
$result = $this->Taxonomy->update();
|
||||
if ($result) {
|
||||
echo 'Taxonomies updated' . PHP_EOL;
|
||||
} else {
|
||||
echo 'Could not update Taxonomies' . PHP_EOL;
|
||||
$successes = count(!empty($result['success']) ? $result['success'] : []);
|
||||
$fails = count(!empty($result['fails']) ? $result['fails'] : []);
|
||||
$message = '';
|
||||
if ($successes == 0 && $fails == 0) {
|
||||
$message = __('All taxonomies are up to date already.');
|
||||
} elseif ($successes == 0 && $fails > 0) {
|
||||
$message = __('Could not update any of the taxonomies.');
|
||||
} elseif ($successes > 0 ) {
|
||||
$message = __('Successfully updated %s taxonomies.', $successes);
|
||||
if ($fails != 0) {
|
||||
$message .= __(' However, could not update %s taxonomies.', $fails);
|
||||
}
|
||||
}
|
||||
echo $message . PHP_EOL;
|
||||
}
|
||||
|
||||
public function enableTaxonomyTags()
|
||||
{
|
||||
if (empty($this->args[0]) || !is_numeric($this->args[0])) {
|
||||
echo 'Usage: ' . APP . '/cake ' . 'Admin enableTaxonomyTags [taxonomy_id]' . PHP_EOL;
|
||||
} else {
|
||||
$result = $this->Taxonomy->addTags(intval($this->args[0]));
|
||||
if ($result) {
|
||||
echo 'Taxonomy tags enabled' . PHP_EOL;
|
||||
} else {
|
||||
echo 'Could not enable taxonomy tags' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -214,19 +236,24 @@ class AdminShell extends AppShell
|
|||
if (empty($user)) {
|
||||
echo 'User with ID: ' . $userId . ' not found' . PHP_EOL;
|
||||
$result = $this->ObjectTemplate->update();
|
||||
if ($result) {
|
||||
echo 'Object templates updated' . PHP_EOL;
|
||||
} else {
|
||||
echo 'Could not update object templates' . PHP_EOL;
|
||||
}
|
||||
} else {
|
||||
$result = $this->ObjectTemplate->update($user, false,false);
|
||||
if ($result) {
|
||||
echo 'Object templates updated' . PHP_EOL;
|
||||
} else {
|
||||
echo 'Could not update object templates' . PHP_EOL;
|
||||
}
|
||||
|
||||
$successes = count(!empty($result['success']) ? $result['success'] : []);
|
||||
$fails = count(!empty($result['fails']) ? $result['fails'] : []);
|
||||
$message = '';
|
||||
if ($successes == 0 && $fails == 0) {
|
||||
$message = __('All object templates are up to date already.');
|
||||
} elseif ($successes == 0 && $fails > 0) {
|
||||
$message = __('Could not update any of the object templates.');
|
||||
} elseif ($successes > 0 ) {
|
||||
$message = __('Successfully updated %s object templates.', $successes);
|
||||
if ($fails != 0) {
|
||||
$message .= __(' However, could not update %s object templates.', $fails);
|
||||
}
|
||||
}
|
||||
echo $message . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -666,4 +693,11 @@ class AdminShell extends AppShell
|
|||
$this->Job->saveField('message', 'Job done.');
|
||||
$this->Job->saveField('status', 4);
|
||||
}
|
||||
|
||||
public function updatesDone()
|
||||
{
|
||||
$blocking = !empty($this->args[0]);
|
||||
$done = $this->AdminSetting->updatesDone($blocking);
|
||||
$this->out($done ? 'True' : 'False');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
class DevShell extends AppShell {
|
||||
|
||||
public $uses = [];
|
||||
|
||||
public function cleanFeedDefault() {
|
||||
$this->out(__('Massaging the feed metadata file.'));
|
||||
$data = file_get_contents(APP . 'files/feed-metadata/defaults.json');
|
||||
if (empty($data)) {
|
||||
$this->stdout->styles('error');
|
||||
$this->out(__('Could not read the defaults.json file at %s. Exiting', APP . 'files/feed-metadata/defaults.json'));
|
||||
} else {
|
||||
$data = json_decode($data, true);
|
||||
$validFields = [
|
||||
'Feed' => [
|
||||
'name', 'provider', 'url', 'rules', 'enabled', 'distribution',
|
||||
'default', 'source_format', 'fixed_event', 'delta_merge',
|
||||
'publish', 'override_ids', 'settings', 'input_source',
|
||||
'delete_local_file', 'lookup_visible'
|
||||
],
|
||||
'Tag' => [
|
||||
'name', 'colour', 'exportable', 'hide_tag'
|
||||
]
|
||||
];
|
||||
foreach ($data as $k => $feedData) {
|
||||
$temp = [];
|
||||
foreach ($validFields as $scope => $fieldNames) {
|
||||
foreach ($fieldNames as $fieldName) {
|
||||
if (isset($feedData[$scope][$fieldName])) {
|
||||
$temp[$scope][$fieldName] = $feedData[$scope][$fieldName];
|
||||
}
|
||||
}
|
||||
}
|
||||
$data[$k] = $temp;
|
||||
}
|
||||
if (!empty($data)) {
|
||||
file_put_contents(APP . 'files/feed-metadata/defaults.json', json_encode($data, JSON_PRETTY_PRINT));
|
||||
$this->out(__(
|
||||
'Done. The feed definitions contain %s feeds and can be found at %s.',
|
||||
count($data),
|
||||
APP . 'files/feed-metadata/defaults.json'
|
||||
));
|
||||
} else {
|
||||
$this->stdout->styles('error');
|
||||
$this->out(__('Something went wrong.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,16 +29,20 @@ class EventShell extends AppShell
|
|||
],
|
||||
)
|
||||
));
|
||||
$parser->addSubcommand('testEventNotificationEmail', [
|
||||
'help' => __('Generate event notification email in EML format.'),
|
||||
'arguments' => [
|
||||
'event_id' => ['help' => __('Event ID'), 'required' => true],
|
||||
'user_id' => ['help' => __('User ID'), 'required' => true],
|
||||
],
|
||||
]);
|
||||
return $parser;
|
||||
}
|
||||
|
||||
public function import()
|
||||
{
|
||||
list($userId, $path) = $this->args;
|
||||
$user = $this->User->getAuthUser($userId);
|
||||
if (empty($user)) {
|
||||
$this->error("User with ID $userId does not exists.");
|
||||
}
|
||||
$user = $this->getUser($userId);
|
||||
|
||||
if (!file_exists($path)) {
|
||||
$this->error("File '$path' does not exists.");
|
||||
|
@ -113,7 +117,7 @@ class EventShell extends AppShell
|
|||
$timeStart = time();
|
||||
$userId = $this->args[0];
|
||||
$id = $this->args[1];
|
||||
$user = $this->User->getAuthUser($userId);
|
||||
$user = $this->getUser($userId);
|
||||
$this->Job->id = $id;
|
||||
$export_type = $this->args[2];
|
||||
file_put_contents('/tmp/test', $export_type);
|
||||
|
@ -165,7 +169,7 @@ class EventShell extends AppShell
|
|||
$this->ConfigLoad->execute();
|
||||
$timeStart = time();
|
||||
$userId = $this->args[0];
|
||||
$user = $this->User->getAuthUser($userId);
|
||||
$user = $this->getUser($userId);
|
||||
$id = $this->args[1];
|
||||
$this->Job->id = $id;
|
||||
$this->Job->saveField('progress', 1);
|
||||
|
@ -204,10 +208,7 @@ class EventShell extends AppShell
|
|||
$jobId = $this->args[1];
|
||||
$eventId = $this->args[2];
|
||||
$oldpublish = $this->args[3];
|
||||
$user = $this->User->getUserById($userId);
|
||||
if (empty($user)) {
|
||||
die("Invalid user ID '$userId' provided.");
|
||||
}
|
||||
$user = $this->getUser($userId);
|
||||
$this->Event->sendAlertEmail($eventId, $user, $oldpublish, $jobId);
|
||||
}
|
||||
|
||||
|
@ -220,10 +221,7 @@ class EventShell extends AppShell
|
|||
$userId = $this->args[3];
|
||||
$processId = $this->args[4];
|
||||
|
||||
$user = $this->User->getUserById($userId);
|
||||
if (empty($user)) {
|
||||
die("Invalid user ID '$userId' provided.");
|
||||
}
|
||||
$user = $this->getUser($userId);
|
||||
$result = $this->Event->sendContactEmail($id, $message, $all, $user);
|
||||
$this->Job->saveStatus($processId, $result);
|
||||
}
|
||||
|
@ -305,10 +303,7 @@ class EventShell extends AppShell
|
|||
$passAlong = $this->args[1];
|
||||
$jobId = $this->args[2];
|
||||
$userId = $this->args[3];
|
||||
$user = $this->User->getAuthUser($userId);
|
||||
if (empty($user)) {
|
||||
die("Invalid user ID '$userId' provided.");
|
||||
}
|
||||
$user = $this->getUser($userId);
|
||||
$job = $this->Job->read(null, $jobId);
|
||||
$this->Event->Behaviors->unload('SysLogLogable.SysLogLogable');
|
||||
$result = $this->Event->publish($id, $passAlong);
|
||||
|
@ -321,7 +316,6 @@ class EventShell extends AppShell
|
|||
}
|
||||
$this->Job->save($job);
|
||||
$log = ClassRegistry::init('Log');
|
||||
$log->create();
|
||||
$log->createLogEntry($user, 'publish', 'Event', $id, 'Event (' . $id . '): published.', 'published () => (1)');
|
||||
}
|
||||
|
||||
|
@ -332,10 +326,7 @@ class EventShell extends AppShell
|
|||
$passAlong = $this->args[1];
|
||||
$jobId = $this->args[2];
|
||||
$userId = $this->args[3];
|
||||
$user = $this->User->getAuthUser($userId);
|
||||
if (empty($user)) {
|
||||
die("Invalid user ID '$userId' provided.");
|
||||
}
|
||||
$user = $this->getUser($userId);
|
||||
$job = $this->Job->read(null, $jobId);
|
||||
$this->Event->Behaviors->unload('SysLogLogable.SysLogLogable');
|
||||
$result = $this->Event->publish_sightings($id, $passAlong);
|
||||
|
@ -348,7 +339,6 @@ class EventShell extends AppShell
|
|||
}
|
||||
$this->Job->save($job);
|
||||
$log = ClassRegistry::init('Log');
|
||||
$log->create();
|
||||
$log->createLogEntry($user, 'publish_sightings', 'Event', $id, 'Sightings for event (' . $id . '): published.', 'publish_sightings updated');
|
||||
}
|
||||
|
||||
|
@ -359,7 +349,7 @@ class EventShell extends AppShell
|
|||
$jobId = $this->args[1];
|
||||
$userId = $this->args[2];
|
||||
$passAlong = $this->args[3];
|
||||
$user = $this->User->getAuthUser($userId);
|
||||
$user = $this->getUser($userId);
|
||||
$job = $this->Job->read(null, $jobId);
|
||||
$this->GalaxyCluster = ClassRegistry::init('GalaxyCluster');
|
||||
$result = $this->GalaxyCluster->publish($clusterId, $passAlong=$passAlong);
|
||||
|
@ -372,7 +362,6 @@ class EventShell extends AppShell
|
|||
}
|
||||
$this->Job->save($job);
|
||||
$log = ClassRegistry::init('Log');
|
||||
$log->create();
|
||||
$log->createLogEntry($user, 'publish', 'GalaxyCluster', $clusterId, 'GalaxyCluster (' . $clusterId . '): published.', 'published () => (1)');
|
||||
}
|
||||
|
||||
|
@ -383,8 +372,7 @@ class EventShell extends AppShell
|
|||
die('Usage: ' . $this->Server->command_line_functions['enrichment'] . PHP_EOL);
|
||||
}
|
||||
$userId = $this->args[0];
|
||||
$user = $this->User->getAuthUser($userId);
|
||||
if (empty($user)) die('Invalid user.');
|
||||
$user = $this->getUser($userId);
|
||||
$eventId = $this->args[1];
|
||||
$modulesRaw = $this->args[2];
|
||||
try {
|
||||
|
@ -425,7 +413,6 @@ class EventShell extends AppShell
|
|||
echo $job['Job']['message'] . PHP_EOL;
|
||||
$this->Job->save($job);
|
||||
$log = ClassRegistry::init('Log');
|
||||
$log->create();
|
||||
$log->createLogEntry($user, 'enrichment', 'Event', $eventId, 'Event (' . $eventId . '): enriched.', 'enriched () => (1)');
|
||||
}
|
||||
|
||||
|
@ -484,4 +471,45 @@ class EventShell extends AppShell
|
|||
$job['Job']['message'] = __('Recovery complete. Event #%s recovered, using %s log entries.', $id, $result);
|
||||
$this->Job->save($job);
|
||||
}
|
||||
|
||||
public function testEventNotificationEmail()
|
||||
{
|
||||
list($eventId, $userId) = $this->args;
|
||||
|
||||
$user = $this->getUser($userId);
|
||||
$eventForUser = $this->Event->fetchEvent($user, [
|
||||
'eventid' => $eventId,
|
||||
'includeAllTags' => true,
|
||||
'includeEventCorrelations' => true,
|
||||
'noEventReports' => true,
|
||||
'noSightings' => true,
|
||||
'metadata' => Configure::read('MISP.event_alert_metadata_only') ?: false,
|
||||
]);
|
||||
if (empty($eventForUser)) {
|
||||
$this->error("Event with ID $eventId not exists or given user don't have permission to access it.");
|
||||
}
|
||||
|
||||
$emailTemplate = $this->Event->prepareAlertEmail($eventForUser[0], $user);
|
||||
|
||||
App::uses('SendEmail', 'Tools');
|
||||
App::uses('GpgTool', 'Tools');
|
||||
$sendEmail = new SendEmail(GpgTool::initializeGpg());
|
||||
$sendEmail->setTransport('Debug');
|
||||
$result = $sendEmail->sendToUser(['User' => $user], null, $emailTemplate);
|
||||
|
||||
echo $result['contents']['headers'] . "\n\n" . $result['contents']['message'] . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
* @return array
|
||||
*/
|
||||
private function getUser($userId)
|
||||
{
|
||||
$user = $this->User->getAuthUser($userId);
|
||||
if (empty($user)) {
|
||||
$this->error("User with ID $userId does not exists.");
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,26 @@ class ServerShell extends AppShell
|
|||
public $uses = array('Server', 'Task', 'Job', 'User', 'Feed');
|
||||
public $tasks = array('ConfigLoad');
|
||||
|
||||
public function list()
|
||||
{
|
||||
$this->ConfigLoad->execute();
|
||||
$res = ['servers'=>[]];
|
||||
$servers = $this->Server->find('all', [
|
||||
'fields' => ['Server.id', 'Server.name', 'Server.url'],
|
||||
'recursive' => 0
|
||||
]);
|
||||
foreach ($servers as $server) {
|
||||
echo sprintf(
|
||||
'%sServer #%s :: %s :: %s',
|
||||
PHP_EOL,
|
||||
$server['Server']['id'],
|
||||
$server['Server']['name'],
|
||||
$server['Server']['url']
|
||||
);
|
||||
}
|
||||
echo PHP_EOL;
|
||||
}
|
||||
|
||||
public function listServers()
|
||||
{
|
||||
$this->ConfigLoad->execute();
|
||||
|
|
|
@ -25,8 +25,8 @@ class AppController extends Controller
|
|||
|
||||
public $helpers = array('OrgImg', 'FontAwesome', 'UserName', 'DataPathCollector');
|
||||
|
||||
private $__queryVersion = '122';
|
||||
public $pyMispVersion = '2.4.137';
|
||||
private $__queryVersion = '127';
|
||||
public $pyMispVersion = '2.4.140';
|
||||
public $phpmin = '7.2';
|
||||
public $phprec = '7.4';
|
||||
public $phptoonew = '8.0';
|
||||
|
@ -79,6 +79,7 @@ class AppController extends Controller
|
|||
),
|
||||
'Security',
|
||||
'ACL',
|
||||
'CompressedRequestHandler',
|
||||
'RestResponse',
|
||||
'Flash',
|
||||
'Toolbox',
|
||||
|
@ -93,26 +94,19 @@ class AppController extends Controller
|
|||
public function beforeFilter()
|
||||
{
|
||||
$this->_setupBaseurl();
|
||||
$this->Auth->loginRedirect = $this->baseurl. '/users/routeafterlogin';
|
||||
$this->Auth->loginRedirect = $this->baseurl . '/users/routeafterlogin';
|
||||
|
||||
$customLogout = Configure::read('Plugin.CustomAuth_custom_logout');
|
||||
$this->Auth->logoutRedirect = $customLogout ?: ($this->baseurl . '/users/login');
|
||||
|
||||
$this->__sessionMassage();
|
||||
if (Configure::read('Security.allow_cors')) {
|
||||
// Add CORS headers
|
||||
$this->response->cors($this->request,
|
||||
explode(',', Configure::read('Security.cors_origins')),
|
||||
['*'],
|
||||
['Origin', 'Content-Type', 'Authorization', 'Accept']);
|
||||
|
||||
if ($this->request->is('options')) {
|
||||
// Stop here!
|
||||
// CORS only needs the headers
|
||||
$this->response->send();
|
||||
$this->_stop();
|
||||
}
|
||||
// If server is running behind reverse proxy, PHP will not recognize that user is accessing site by HTTPS connection.
|
||||
// By setting `Security.force_https` to `true`, session cookie will be set as Secure and CSP headers will upgrade insecure requests.
|
||||
if (Configure::read('Security.force_https')) {
|
||||
$_SERVER['HTTPS'] = 'on';
|
||||
}
|
||||
$this->__cors();
|
||||
if (Configure::read('Security.check_sec_fetch_site_header')) {
|
||||
$secFetchSite = $this->request->header('Sec-Fetch-Site');
|
||||
if ($secFetchSite !== false && $secFetchSite !== 'same-origin' && ($this->request->is('post') || $this->request->is('put') || $this->request->is('ajax'))) {
|
||||
|
@ -122,7 +116,10 @@ class AppController extends Controller
|
|||
if (Configure::read('Security.disable_browser_cache')) {
|
||||
$this->response->disableCache();
|
||||
}
|
||||
$this->response->header('X-XSS-Protection', '1; mode=block');
|
||||
if (!$this->_isRest()) {
|
||||
$this->__contentSecurityPolicy();
|
||||
$this->response->header('X-XSS-Protection', '1; mode=block');
|
||||
}
|
||||
|
||||
if (!empty($this->params['named']['sql'])) {
|
||||
$this->sql_dump = intval($this->params['named']['sql']);
|
||||
|
@ -229,6 +226,10 @@ class AppController extends Controller
|
|||
$this->Security->csrfCheck = false;
|
||||
}
|
||||
if ($this->__loginByAuthKey() === false || $this->Auth->user() === null) {
|
||||
if ($this->__loginByAuthKey() === null) {
|
||||
$this->loadModel('Log');
|
||||
$this->Log->createLogEntry('SYSTEM', 'auth_fail', 'User', 0, "Failed API authentication. No authkey was provided.");
|
||||
}
|
||||
throw new ForbiddenException('Authentication failed. Please make sure you pass the API key of an API enabled user along in the Authorization header.');
|
||||
}
|
||||
} elseif (!$this->Session->read(AuthComponent::$sessionKey)) {
|
||||
|
@ -313,11 +314,11 @@ class AppController extends Controller
|
|||
$this->__accessMonitor($user);
|
||||
|
||||
} else {
|
||||
$pre_auth_actions = array('login', 'register', 'getGpgPublicKey');
|
||||
$preAuthActions = array('login', 'register', 'getGpgPublicKey');
|
||||
if (!empty(Configure::read('Security.email_otp_enabled'))) {
|
||||
$pre_auth_actions[] = 'email_otp';
|
||||
$preAuthActions[] = 'email_otp';
|
||||
}
|
||||
if (!$this->_isControllerAction(['users' => $pre_auth_actions])) {
|
||||
if (!$this->_isControllerAction(['users' => $preAuthActions, 'servers' => ['cspReport']])) {
|
||||
if (!$this->request->is('ajax')) {
|
||||
$this->Session->write('pre_login_requested_url', $this->here);
|
||||
}
|
||||
|
@ -442,22 +443,9 @@ class AppController extends Controller
|
|||
} else {
|
||||
// User not authenticated correctly
|
||||
// reset the session information
|
||||
$redis = $this->User->setupRedis();
|
||||
// Do not log every fail, but just once per hour
|
||||
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . $authKeyToStore)) {
|
||||
$redis->setex('misp:auth_fail_throttling:' . $authKeyToStore, 3600, 1);
|
||||
if ($this->_shouldLog($authKeyToStore)) {
|
||||
$this->loadModel('Log');
|
||||
$this->Log->create();
|
||||
$log = array(
|
||||
'org' => 'SYSTEM',
|
||||
'model' => 'User',
|
||||
'model_id' => 0,
|
||||
'email' => 'SYSTEM',
|
||||
'action' => 'auth_fail',
|
||||
'title' => "Failed authentication using API key ($authKeyToStore)",
|
||||
'change' => null,
|
||||
);
|
||||
$this->Log->save($log);
|
||||
$this->Log->createLogEntry('SYSTEM', 'auth_fail', 'User', 0, "Failed authentication using API key ($authKeyToStore)");
|
||||
}
|
||||
$this->Session->destroy();
|
||||
}
|
||||
|
@ -544,8 +532,10 @@ class AppController extends Controller
|
|||
}
|
||||
|
||||
if ($user['disabled']) {
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], 'Login attempt by disabled user.');
|
||||
if ($this->_shouldLog('disabled:' . $user['id'])) {
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], 'Login attempt by disabled user.');
|
||||
}
|
||||
|
||||
$this->Auth->logout();
|
||||
if ($this->_isRest()) {
|
||||
|
@ -561,11 +551,33 @@ class AppController extends Controller
|
|||
if (isset($user['authkey_expiration']) && $user['authkey_expiration']) {
|
||||
$time = isset($_SERVER['REQUEST_TIME']) ? $_SERVER['REQUEST_TIME'] : time();
|
||||
if ($user['authkey_expiration'] < $time) {
|
||||
if ($this->_shouldLog('expired:' . $user['authkey_id'])) {
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], "Login attempt by expired auth key {$user['authkey_id']}.");
|
||||
}
|
||||
$this->Auth->logout();
|
||||
throw new ForbiddenException('Auth key is expired');
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($user['allowed_ips'])) {
|
||||
App::uses('CidrTool', 'Tools');
|
||||
$cidrTool = new CidrTool($user['allowed_ips']);
|
||||
$remoteIp = $this->_remoteIp();
|
||||
if ($remoteIp === null) {
|
||||
$this->Auth->logout();
|
||||
throw new ForbiddenException('Auth key is limited to IP address, but IP address not found');
|
||||
}
|
||||
if (!$cidrTool->contains($remoteIp)) {
|
||||
if ($this->_shouldLog('not_allowed_ip:' . $user['authkey_id'] . ':' . $remoteIp)) {
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$this->Log->createLogEntry($user, 'auth_fail', 'User', $user['id'], "Login attempt from not allowed IP address for auth key {$user['authkey_id']}.");
|
||||
}
|
||||
$this->Auth->logout();
|
||||
throw new ForbiddenException('It is not possible to use this Auth key from your IP address');
|
||||
}
|
||||
}
|
||||
|
||||
$isUserRequest = !$this->_isRest() && !$this->request->is('ajax') && !$this->_isAutomation();
|
||||
// Next checks makes sense just for user direct HTTP request, so skip REST and AJAX calls
|
||||
if (!$isUserRequest) {
|
||||
|
@ -628,7 +640,7 @@ class AppController extends Controller
|
|||
return;
|
||||
}
|
||||
|
||||
$remoteAddress = trim($_SERVER['REMOTE_ADDR']);
|
||||
$remoteAddress = $this->_remoteIp();
|
||||
|
||||
$pipe = $redis->multi(Redis::PIPELINE);
|
||||
// keep for 30 days
|
||||
|
@ -676,10 +688,69 @@ class AppController extends Controller
|
|||
$change .= PHP_EOL . 'Request body: ' . $payload;
|
||||
}
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
try {
|
||||
$this->Log->createLogEntry($user, 'request', 'User', $user['id'], 'Paranoid log entry', $change);
|
||||
} catch (Exception $e) {
|
||||
// When `MISP.log_skip_db_logs_completely` is enabled, Log::createLogEntry method throws exception
|
||||
$this->Log->createLogEntry($user, 'request', 'User', $user['id'], 'Paranoid log entry', $change);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Content-Security-Policy HTTP header
|
||||
*/
|
||||
private function __contentSecurityPolicy()
|
||||
{
|
||||
$default = [
|
||||
'default-src' => "'self' data: 'unsafe-inline' 'unsafe-eval'",
|
||||
'style-src' => "'self' 'unsafe-inline'",
|
||||
'object-src' => "'none'",
|
||||
'frame-ancestors' => "'none'",
|
||||
'worker-src' => "'none'",
|
||||
'child-src' => "'none'",
|
||||
'frame-src' => "'none'",
|
||||
'base-uri' => "'self'",
|
||||
'img-src' => "'self' data:",
|
||||
'font-src' => "'self'",
|
||||
'form-action' => "'self'",
|
||||
'connect-src' => "'self'",
|
||||
'manifest-src' => "'none'",
|
||||
'report-uri' => '/servers/cspReport',
|
||||
];
|
||||
if (env('HTTPS')) {
|
||||
$default['upgrade-insecure-requests'] = null;
|
||||
}
|
||||
$custom = Configure::read('Security.csp');
|
||||
if ($custom === false) {
|
||||
return;
|
||||
}
|
||||
if (is_array($custom)) {
|
||||
$default = $custom + $default;
|
||||
}
|
||||
$header = [];
|
||||
foreach ($default as $key => $value) {
|
||||
if ($value !== false) {
|
||||
if ($value === null) {
|
||||
$header[] = $key;
|
||||
} else {
|
||||
$header[] = "$key $value";
|
||||
}
|
||||
}
|
||||
}
|
||||
$headerName = Configure::read('Security.csp_enforce') ? 'Content-Security-Policy' : 'Content-Security-Policy-Report-Only';
|
||||
$this->response->header($headerName, implode('; ', $header));
|
||||
}
|
||||
|
||||
private function __cors()
|
||||
{
|
||||
if (Configure::read('Security.allow_cors')) {
|
||||
// Add CORS headers
|
||||
$this->response->cors($this->request,
|
||||
explode(',', Configure::read('Security.cors_origins')),
|
||||
['*'],
|
||||
['Origin', 'Content-Type', 'Authorization', 'Accept']);
|
||||
|
||||
if ($this->request->is('options')) {
|
||||
// Stop here!
|
||||
// CORS only needs the headers
|
||||
$this->response->send();
|
||||
$this->_stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -737,7 +808,7 @@ class AppController extends Controller
|
|||
* Configure the debugMode view parameter
|
||||
*/
|
||||
protected function _setupDebugMode() {
|
||||
$this->set('debugMode', (Configure::read('debug') > 1) ? 'debugOn' : 'debugOff');
|
||||
$this->set('debugMode', (Configure::read('debug') >= 1) ? 'debugOn' : 'debugOff');
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1212,7 +1283,7 @@ class AppController extends Controller
|
|||
|
||||
private function __sessionMassage()
|
||||
{
|
||||
if (!empty(Configure::read('MISP.uuid'))) {
|
||||
if (empty(Configure::read('Session.cookie')) && !empty(Configure::read('MISP.uuid'))) {
|
||||
Configure::write('Session.cookie', 'MISP-' . Configure::read('MISP.uuid'));
|
||||
}
|
||||
if (!empty(Configure::read('Session.cookieTimeout')) || !empty(Configure::read('Session.timeout'))) {
|
||||
|
@ -1346,6 +1417,18 @@ class AppController extends Controller
|
|||
}
|
||||
$elementCounter = 0;
|
||||
$renderView = false;
|
||||
$responseType = empty($this->$scope->validFormats[$returnFormat][0]) ? 'json' : $this->$scope->validFormats[$returnFormat][0];
|
||||
// halt execution if we were to query for items above the ID. Blocks the endless caching bug
|
||||
if (!empty($filters['page']) && !empty($filters['returnFormat']) && $filters['returnFormat'] === 'cache') {
|
||||
if ($this->__cachingOverflow($filters, $scope)) {
|
||||
$filename = $this->RestSearch->getFilename($filters, $scope, $responseType);
|
||||
return $this->RestResponse->viewData('', $responseType, false, true, $filename, [
|
||||
'X-Result-Count' => 0,
|
||||
'X-Export-Module-Used' => $returnFormat,
|
||||
'X-Response-Format' => $responseType
|
||||
]);
|
||||
}
|
||||
}
|
||||
$final = $this->$scope->restSearch($user, $returnFormat, $filters, false, false, $elementCounter, $renderView);
|
||||
if (!empty($renderView) && !empty($final)) {
|
||||
$this->layout = false;
|
||||
|
@ -1355,12 +1438,29 @@ class AppController extends Controller
|
|||
}
|
||||
$this->render('/Events/module_views/' . $renderView);
|
||||
} else {
|
||||
$responseType = $this->$scope->validFormats[$returnFormat][0];
|
||||
$filename = $this->RestSearch->getFilename($filters, $scope, $responseType);
|
||||
return $this->RestResponse->viewData($final, $responseType, false, true, $filename, array('X-Result-Count' => $elementCounter, 'X-Export-Module-Used' => $returnFormat, 'X-Response-Format' => $responseType));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Halt execution if we were to query for items above the ID. Blocks the endless caching bug.
|
||||
*
|
||||
* @param array $filters
|
||||
* @param string $scope
|
||||
* @return bool
|
||||
*/
|
||||
private function __cachingOverflow($filters, $scope)
|
||||
{
|
||||
$offset = ($filters['page'] * (empty($filters['limit']) ? 60 : $filters['limit'])) + 1;
|
||||
$max_id = $this->$scope->query(sprintf('SELECT max(id) as max_id from %s;', Inflector::tableize($scope)));
|
||||
$max_id = intval($max_id[0][0]['max_id']);
|
||||
if ($max_id < $offset) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if user can modify given event.
|
||||
*
|
||||
|
@ -1376,10 +1476,10 @@ class AppController extends Controller
|
|||
if ($this->userRole['perm_site_admin']) {
|
||||
return true;
|
||||
}
|
||||
if ($this->userRole['perm_modify_org'] && $event['Event']['orgc_id'] == $this->Auth->user('org_id')) {
|
||||
if ($this->userRole['perm_modify_org'] && $event['Event']['orgc_id'] == $this->Auth->user()['org_id']) {
|
||||
return true;
|
||||
}
|
||||
if ($this->userRole['perm_modify'] && $event['Event']['user_id'] == $this->Auth->user('id')) {
|
||||
if ($this->userRole['perm_modify'] && $event['Event']['user_id'] == $this->Auth->user()['id']) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -1423,17 +1523,50 @@ class AppController extends Controller
|
|||
throw new RuntimeException("User with ID {$sessionUser['id']} not exists.");
|
||||
}
|
||||
if (isset($sessionUser['authkey_id'])) {
|
||||
// Reload authkey
|
||||
$this->loadModel('AuthKey');
|
||||
if (!$this->AuthKey->exists($sessionUser['authkey_id'])) {
|
||||
$authKey = $this->AuthKey->find('first', [
|
||||
'conditions' => ['id' => $sessionUser['authkey_id'], 'user_id' => $user['id']],
|
||||
'fields' => ['id', 'expiration', 'allowed_ips'],
|
||||
'recursive' => -1,
|
||||
]);
|
||||
if (empty($authKey)) {
|
||||
throw new RuntimeException("Auth key with ID {$sessionUser['authkey_id']} not exists.");
|
||||
}
|
||||
$user['authkey_id'] = $authKey['AuthKey']['id'];
|
||||
$user['authkey_expiration'] = $authKey['AuthKey']['expiration'];
|
||||
$user['allowed_ips'] = $authKey['AuthKey']['allowed_ips'];
|
||||
}
|
||||
foreach (['authkey_id', 'authkey_expiration', 'logged_by_authkey'] as $copy) {
|
||||
if (isset($sessionUser[$copy])) {
|
||||
$user[$copy] = $sessionUser[$copy];
|
||||
}
|
||||
if (isset($sessionUser['logged_by_authkey'])) {
|
||||
$user['logged_by_authkey'] = $sessionUser['logged_by_authkey'];
|
||||
}
|
||||
$this->Auth->login($user);
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
protected function _remoteIp()
|
||||
{
|
||||
$ipHeader = Configure::read('MISP.log_client_ip_header') ?: 'REMOTE_ADDR';
|
||||
return isset($_SERVER[$ipHeader]) ? trim($_SERVER[$ipHeader]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool Returns true if the same log defined by $key was not stored in last hour
|
||||
*/
|
||||
protected function _shouldLog($key)
|
||||
{
|
||||
if (Configure::read('Security.log_each_individual_auth_fail')) {
|
||||
return true;
|
||||
}
|
||||
$redis = $this->User->setupRedis();
|
||||
if ($redis && !$redis->exists('misp:auth_fail_throttling:' . $key)) {
|
||||
$redis->setex('misp:auth_fail_throttling:' . $key, 3600, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -998,6 +998,7 @@ class AttributesController extends AppController
|
|||
'includeAllTags' => false,
|
||||
'includeAttributeUuid' => true,
|
||||
'flatten' => true,
|
||||
'deleted' => [0, 1]
|
||||
);
|
||||
|
||||
if ($this->_isRest()) {
|
||||
|
@ -1505,7 +1506,6 @@ class AttributesController extends AppController
|
|||
'request' => $this->request,
|
||||
'named_params' => $this->params['named'],
|
||||
'paramArray' => $paramArray,
|
||||
'ordered_url_params' => @compact($paramArray),
|
||||
'additional_delimiters' => PHP_EOL
|
||||
);
|
||||
$exception = false;
|
||||
|
@ -2582,9 +2582,10 @@ class AttributesController extends AppController
|
|||
}
|
||||
$totalAttributes = $this->Attribute->find('count', array());
|
||||
$attributes = $this->Attribute->find('all', array(
|
||||
'recursive' => -1,
|
||||
'fields' => array($type, 'COUNT(id) as attribute_count'),
|
||||
'group' => array($type)
|
||||
'recursive' => -1,
|
||||
'fields' => array($type, 'COUNT(id) as attribute_count'),
|
||||
'group' => array($type),
|
||||
'order' => ''
|
||||
));
|
||||
$results = array();
|
||||
foreach ($attributes as $attribute) {
|
||||
|
@ -2677,7 +2678,12 @@ class AttributesController extends AppController
|
|||
}
|
||||
}
|
||||
} else {
|
||||
$tag = $this->Event->EventTag->Tag->find('first', array('recursive' => -1, 'conditions' => $conditions));
|
||||
$conditions = array('LOWER(Tag.name)' => strtolower(trim($tag_id)));
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
$conditions['Tag.org_id'] = array('0', $this->Auth->user('org_id'));
|
||||
$conditions['Tag.user_id'] = array('0', $this->Auth->user('id'));
|
||||
}
|
||||
$tag = $this->Attribute->AttributeTag->Tag->find('first', array('recursive' => -1, 'conditions' => $conditions));
|
||||
if (empty($tag)) {
|
||||
return new CakeResponse(array('body'=> json_encode(array('saved' => false, 'errors' => 'Invalid Tag.')), 'status'=>200, 'type' => 'json'));
|
||||
}
|
||||
|
@ -2708,15 +2714,15 @@ class AttributesController extends AppController
|
|||
} else {
|
||||
$attribute = $attributes[0];
|
||||
}
|
||||
if (!$this->__canModifyTag($attribute, $local)) {
|
||||
$fails++;
|
||||
continue;
|
||||
}
|
||||
$eventId = $attribute['Attribute']['event_id'];
|
||||
$event = $this->Attribute->Event->find('first', array(
|
||||
'conditions' => array('Event.id' => $eventId),
|
||||
'recursive' => -1
|
||||
));
|
||||
if (!$this->__canModifyTag($event, $local)) {
|
||||
$fails++;
|
||||
continue;
|
||||
}
|
||||
if (!$this->_isRest()) {
|
||||
$this->Attribute->Event->insertLock($this->Auth->user(), $eventId);
|
||||
}
|
||||
|
|
|
@ -71,8 +71,34 @@ class AuthKeysController extends AppController
|
|||
|
||||
public function edit($id)
|
||||
{
|
||||
$this->set('metaGroup', 'admin');
|
||||
$this->set('metaAction', 'authkeys_edit');
|
||||
$this->CRUD->edit($id, [
|
||||
'conditions' => $this->__prepareConditions(),
|
||||
'afterFind' => function (array $authKey) {
|
||||
unset($authKey['AuthKey']['authkey']);
|
||||
if (is_array($authKey['AuthKey']['allowed_ips'])) {
|
||||
$authKey['AuthKey']['allowed_ips'] = implode("\n", $authKey['AuthKey']['allowed_ips']);
|
||||
}
|
||||
$authKey['AuthKey']['expiration'] = date('Y-m-d H:i:s', $authKey['AuthKey']['expiration']);
|
||||
return $authKey;
|
||||
},
|
||||
'fields' => ['comment', 'allowed_ips', 'expiration'],
|
||||
]);
|
||||
if ($this->IndexFilter->isRest()) {
|
||||
return $this->restResponsePayload;
|
||||
}
|
||||
$this->set('dropdownData', [
|
||||
'user' => $this->User->find('list', [
|
||||
'sort' => ['username' => 'asc'],
|
||||
'conditions' => ['id' => $this->request->data['AuthKey']['user_id']],
|
||||
])
|
||||
]);
|
||||
$this->set('menuData', [
|
||||
'menuList' => $this->_isSiteAdmin() ? 'admin' : 'globalActions',
|
||||
'menuItem' => 'authKeyAdd',
|
||||
]);
|
||||
$this->set('edit', true);
|
||||
$this->set('validity', Configure::read('Security.advanced_authkeys_validity'));
|
||||
$this->render('add');
|
||||
}
|
||||
|
||||
public function add($user_id = false)
|
||||
|
|
|
@ -262,7 +262,9 @@ class ACLComponent extends Component
|
|||
'viewEventAttributes' => array('*'),
|
||||
'viewGraph' => array('*'),
|
||||
'viewGalaxyMatrix' => array('*'),
|
||||
'xml' => array('*')
|
||||
'xml' => array('*'),
|
||||
'addEventLock' => ['perm_auth'],
|
||||
'removeEventLock' => ['perm_auth'],
|
||||
),
|
||||
'favouriteTags' => array(
|
||||
'toggle' => array('*'),
|
||||
|
@ -346,7 +348,9 @@ class ACLComponent extends Component
|
|||
'view' => array('*'),
|
||||
),
|
||||
'galaxyElements' => array(
|
||||
'index' => array('*')
|
||||
'delete' => array('perm_galaxy_editor'),
|
||||
'flattenJson' => array('perm_galaxy_editor'),
|
||||
'index' => array('*'),
|
||||
),
|
||||
'jobs' => array(
|
||||
'cache' => array('*'),
|
||||
|
@ -412,6 +416,7 @@ class ACLComponent extends Component
|
|||
'edit' => array('perm_object_template'),
|
||||
'delete' => array('perm_object_template'),
|
||||
'getToggleField' => array(),
|
||||
'getRaw' => array('perm_object_template'),
|
||||
'objectChoice' => array('*'),
|
||||
'objectMetaChoice' => array('perm_add'),
|
||||
'view' => array('*'),
|
||||
|
@ -492,7 +497,7 @@ class ACLComponent extends Component
|
|||
'getSubmodulesStatus' => array(),
|
||||
'getSubmoduleQuickUpdateForm' => array(),
|
||||
'getWorkers' => array(),
|
||||
'getVersion' => array('*'),
|
||||
'getVersion' => array('perm_auth'),
|
||||
'idTranslator' => ['OR' => [
|
||||
'host_org_user',
|
||||
'perm_site_admin',
|
||||
|
@ -500,7 +505,7 @@ class ACLComponent extends Component
|
|||
'import' => array(),
|
||||
'index' => array(),
|
||||
'ondemandAction' => array(),
|
||||
'postTest' => array('perm_sync'),
|
||||
'postTest' => array('*'),
|
||||
'previewEvent' => array(),
|
||||
'previewIndex' => array(),
|
||||
'compareServers' => [],
|
||||
|
@ -528,6 +533,7 @@ class ACLComponent extends Component
|
|||
'uploadFile' => array(),
|
||||
'viewDeprecatedFunctionUse' => array(),
|
||||
'killAllWorkers' => ['perm_site_admin'],
|
||||
'cspReport' => ['*'],
|
||||
),
|
||||
'shadowAttributes' => array(
|
||||
'accept' => array('perm_add'),
|
||||
|
@ -542,6 +548,7 @@ class ACLComponent extends Component
|
|||
'generateCorrelation' => array(),
|
||||
'index' => array('*'),
|
||||
'view' => array('*'),
|
||||
'viewPicture' => array('*'),
|
||||
),
|
||||
'sharingGroups' => array(
|
||||
'add' => array('perm_sharing_group'),
|
||||
|
@ -680,7 +687,7 @@ class ACLComponent extends Component
|
|||
'register' => array('*'),
|
||||
'registrations' => array('perm_site_admin'),
|
||||
'resetAllSyncAuthKeys' => array(),
|
||||
'resetauthkey' => array('*'),
|
||||
'resetauthkey' => ['AND' => ['self_management_enabled', 'perm_auth']],
|
||||
'request_API' => array('*'),
|
||||
'routeafterlogin' => array('*'),
|
||||
'statistics' => array('*'),
|
||||
|
@ -720,6 +727,7 @@ class ACLComponent extends Component
|
|||
),
|
||||
'eventGraph' => array(
|
||||
'view' => array('*'),
|
||||
'viewPicture' => array('*'),
|
||||
'add' => array('perm_add'),
|
||||
'delete' => array('perm_modify'),
|
||||
)
|
||||
|
|
|
@ -145,13 +145,21 @@ class CRUDComponent extends Component
|
|||
if (empty($id)) {
|
||||
throw new NotFoundException(__('Invalid %s.', $modelName));
|
||||
}
|
||||
$data = $this->Controller->{$modelName}->find('first',
|
||||
isset($params['get']) ? $params['get'] : [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'id' => $id
|
||||
]
|
||||
]);
|
||||
$query = isset($params['get']) ? $params['get'] : [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'id' => $id
|
||||
],
|
||||
];
|
||||
if (!empty($params['conditions'])) {
|
||||
$query['conditions']['AND'][] = $params['conditions'];
|
||||
}
|
||||
/** @var Model $model */
|
||||
$model = $this->Controller->{$modelName};
|
||||
$data = $model->find('first', $query);
|
||||
if (isset($params['afterFind'])) {
|
||||
$data = $params['afterFind']($data);
|
||||
}
|
||||
if ($this->Controller->request->is('post') || $this->Controller->request->is('put')) {
|
||||
$input = $this->Controller->request->data;
|
||||
if (empty($input[$modelName])) {
|
||||
|
@ -171,7 +179,10 @@ class CRUDComponent extends Component
|
|||
$data[$modelName][$field] = $fieldData;
|
||||
}
|
||||
}
|
||||
if ($this->Controller->{$modelName}->save($data)) {
|
||||
if (isset($params['beforeSave'])) {
|
||||
$data = $params['beforeSave']($data);
|
||||
}
|
||||
if ($model->save($data)) {
|
||||
$message = __('%s updated.', $modelName);
|
||||
if ($this->Controller->IndexFilter->isRest()) {
|
||||
$this->Controller->restResponsePayload = $this->Controller->RestResponse->viewData($data, 'json');
|
||||
|
@ -182,7 +193,9 @@ class CRUDComponent extends Component
|
|||
}
|
||||
} else {
|
||||
if ($this->Controller->IndexFilter->isRest()) {
|
||||
|
||||
$controllerName = $this->Controller->params['controller'];
|
||||
$actionName = $this->Controller->params['action'];
|
||||
$this->Controller->restResponsePayload = $this->Controller->RestResponse->saveFailResponse($controllerName, $actionName, false, $model->validationErrors, 'json');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
class CompressedRequestHandlerComponent extends Component
|
||||
{
|
||||
// Maximum size of uncompressed data to prevent zip bombs
|
||||
const MAX_SIZE = 1024 * 1024 * 100;
|
||||
|
||||
public function startup(Controller $controller)
|
||||
{
|
||||
$contentEncoding = CakeRequest::header('CONTENT_ENCODING');
|
||||
if (!empty($contentEncoding)) {
|
||||
if ($contentEncoding === 'br') {
|
||||
$controller->request->setInput($this->decodeBrotliEncodedContent($controller));
|
||||
} else if ($contentEncoding === 'gzip') {
|
||||
$controller->request->setInput($this->decodeGzipEncodedContent($controller));
|
||||
} else {
|
||||
throw new MethodNotAllowedException("Unsupported content encoding '$contentEncoding'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function supportedEncodings()
|
||||
{
|
||||
$supportedEncodings = [];
|
||||
if (function_exists('gzdecode') || function_exists('inflate_init')) {
|
||||
$supportedEncodings[] = 'gzip';
|
||||
}
|
||||
if (function_exists('brotli_uncompress') || function_exists('brotli_uncompress_init')) {
|
||||
$supportedEncodings[] = 'br';
|
||||
}
|
||||
return $supportedEncodings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @see CakeRequest::_readInput()
|
||||
*/
|
||||
private function decodeGzipEncodedContent(Controller $controller)
|
||||
{
|
||||
if (function_exists('inflate_init')) {
|
||||
// Decompress data on the fly if supported
|
||||
$resource = inflate_init(ZLIB_ENCODING_GZIP);
|
||||
if ($resource === false) {
|
||||
throw new Exception('GZIP incremental uncompress init failed.');
|
||||
}
|
||||
$uncompressed = '';
|
||||
foreach ($this->streamInput() as $data) {
|
||||
$uncompressedChunk = inflate_add($resource, $data);
|
||||
if ($uncompressedChunk === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
$uncompressed .= $uncompressedChunk;
|
||||
if (strlen($uncompressed) > self::MAX_SIZE) {
|
||||
throw new Exception("Uncompressed data are bigger than is limit.");
|
||||
}
|
||||
}
|
||||
$uncompressedChunk = inflate_add($resource, '', ZLIB_FINISH);
|
||||
if ($uncompressedChunk === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
return $uncompressed . $uncompressedChunk;
|
||||
|
||||
} else if (function_exists('gzdecode')) {
|
||||
$decoded = gzdecode($controller->request->input(), self::MAX_SIZE);
|
||||
if ($decoded === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
if (strlen($decoded) >= self::MAX_SIZE) {
|
||||
throw new Exception("Uncompressed data are bigger than is limit.");
|
||||
}
|
||||
return $decoded;
|
||||
} else {
|
||||
throw new MethodNotAllowedException("This server doesn't support GZIP compressed requests.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Controller $controller
|
||||
* @return string
|
||||
* @throws Exception
|
||||
* @see CakeRequest::_readInput()
|
||||
*/
|
||||
private function decodeBrotliEncodedContent(Controller $controller)
|
||||
{
|
||||
if (function_exists('brotli_uncompress_init')) {
|
||||
// Decompress data on the fly if supported
|
||||
$resource = brotli_uncompress_init();
|
||||
if ($resource === false) {
|
||||
throw new Exception('Brotli incremental uncompress init failed.');
|
||||
}
|
||||
$uncompressed = '';
|
||||
foreach ($this->streamInput() as $data) {
|
||||
$uncompressedChunk = brotli_uncompress_add($resource, $data, BROTLI_PROCESS);
|
||||
if ($uncompressedChunk === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
$uncompressed .= $uncompressedChunk;
|
||||
if (strlen($uncompressed) > self::MAX_SIZE) {
|
||||
throw new Exception("Uncompressed data are bigger than is limit.");
|
||||
}
|
||||
}
|
||||
$uncompressedChunk = brotli_uncompress_add($resource, '', BROTLI_FINISH);
|
||||
if ($uncompressedChunk === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
return $uncompressed . $uncompressedChunk;
|
||||
|
||||
} else if (function_exists('brotli_uncompress')) {
|
||||
$decoded = brotli_uncompress($controller->request->input(), self::MAX_SIZE);
|
||||
if ($decoded === false) {
|
||||
throw new MethodNotAllowedException('Invalid compressed data.');
|
||||
}
|
||||
if (strlen($decoded) >= self::MAX_SIZE) {
|
||||
throw new Exception("Uncompressed data are bigger than is limit.");
|
||||
}
|
||||
return $decoded;
|
||||
} else {
|
||||
throw new MethodNotAllowedException("This server doesn't support brotli compressed requests.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $chunkSize
|
||||
* @return Generator<string>
|
||||
* @throws Exception
|
||||
*/
|
||||
private function streamInput($chunkSize = 8192)
|
||||
{
|
||||
$fh = fopen('php://input', 'rb');
|
||||
if ($fh === false) {
|
||||
throw new Exception("Could not open PHP input for reading.");
|
||||
}
|
||||
while (!feof($fh)) {
|
||||
$data = fread($fh, $chunkSize);
|
||||
if ($data === false) {
|
||||
throw new Exception("Could not read PHP input.");
|
||||
}
|
||||
yield $data;
|
||||
}
|
||||
fclose($fh);
|
||||
}
|
||||
}
|
|
@ -48,7 +48,15 @@ class RestResponseComponent extends Component
|
|||
'mandatory' => array('returnFormat'),
|
||||
'optional' => array('page', 'limit', 'value' , 'type', 'category', 'org', 'tags', 'date', 'last', 'eventid', 'withAttachments', 'uuid', 'publish_timestamp', 'timestamp', 'attribute_timestamp', 'enforceWarninglist', 'to_ids', 'deleted', 'includeEventUuid', 'includeEventTags', 'event_timestamp', 'threat_level_id', 'eventinfo', 'includeProposals', 'includeDecayScore', 'includeFullModel', 'decayingModel', 'excludeDecayed', 'score', 'first_seen', 'last_seen'),
|
||||
'params' => array()
|
||||
)
|
||||
),
|
||||
'addTag' => array(
|
||||
'description' => "Add a tag or a tag collection to an attribute.",
|
||||
'mandatory' => array('attribute', 'tag')
|
||||
),
|
||||
'removeTag' => array(
|
||||
'description' => "Remove a tag from an attribute.",
|
||||
'mandatory' => array('attribute', 'tag')
|
||||
),
|
||||
),
|
||||
'Community' => array(
|
||||
'requestAccess' => array(
|
||||
|
@ -80,7 +88,15 @@ class RestResponseComponent extends Component
|
|||
'mandatory' => array('returnFormat'),
|
||||
'optional' => array('page', 'limit', 'value', 'type', 'category', 'org', 'tag', 'tags', 'searchall', 'date', 'last', 'eventid', 'withAttachments', 'metadata', 'uuid', 'published', 'publish_timestamp', 'timestamp', 'enforceWarninglist', 'sgReferenceOnly', 'eventinfo', 'excludeLocalTags', 'threat_level_id'),
|
||||
'params' => array()
|
||||
)
|
||||
),
|
||||
'addTag' => array(
|
||||
'description' => "Add a tag or a tag collection to an event.",
|
||||
'mandatory' => array('event', 'tag')
|
||||
),
|
||||
'removeTag' => array(
|
||||
'description' => "Remove a tag from an event.",
|
||||
'mandatory' => array('event', 'tag')
|
||||
),
|
||||
),
|
||||
'EventGraph' => array(
|
||||
'add' => array(
|
||||
|
@ -270,10 +286,9 @@ class RestResponseComponent extends Component
|
|||
'optional' => array('name', 'colour', 'exportable', 'hide_tag', 'org_id', 'user_id'),
|
||||
'params' => array('tag_id')
|
||||
),
|
||||
'removeTag' => array(
|
||||
'description' => "POST a request object in JSON format to this API to create detach a tag from an event. #FIXME Function does not exists",
|
||||
'mandatory' => array('event', 'tag'),
|
||||
'params' => array('tag_id')
|
||||
'removeTagFromObject' => array(
|
||||
'description' => "Untag an event or attribute. Tag can be the id or the name.",
|
||||
'mandatory' => array('uuid', 'tag')
|
||||
),
|
||||
'attachTagToObject' => array(
|
||||
'description' => "Attach a Tag to an object, refenced by an UUID. Tag can either be a tag id or a tag name.",
|
||||
|
@ -532,7 +547,9 @@ class RestResponseComponent extends Component
|
|||
$response['sql_dump'] = $this->Log->getDataSource()->getLog(false, false);
|
||||
}
|
||||
}
|
||||
$response = json_encode($response, JSON_PRETTY_PRINT);
|
||||
// Do not pretty print response for automatic tools
|
||||
$flags = $this->isAutomaticTool() ? JSON_UNESCAPED_UNICODE : JSON_PRETTY_PRINT;
|
||||
$response = json_encode($response, $flags);
|
||||
} else {
|
||||
if ($dumpSql) {
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
|
@ -582,6 +599,16 @@ class RestResponseComponent extends Component
|
|||
return $cakeResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect if request comes from automatic tool, like other MISP instance or PyMISP
|
||||
* @return bool
|
||||
*/
|
||||
public function isAutomaticTool()
|
||||
{
|
||||
$userAgent = CakeRequest::header('User-Agent');
|
||||
return $userAgent && (substr($userAgent, 0, 6) === 'PyMISP' || substr($userAgent, 0, 4) === 'MISP');
|
||||
}
|
||||
|
||||
private function __generateURL($action, $controller, $id)
|
||||
{
|
||||
$controller = Inflector::underscore(Inflector::pluralize($controller));
|
||||
|
@ -932,6 +959,13 @@ class RestResponseComponent extends Component
|
|||
'values' => array(1 => 'True', 0 => 'False' ),
|
||||
'help' => __('Should the warning list be enforced. Adds `blocked` field for matching attributes')
|
||||
),
|
||||
'event' => array(
|
||||
'input' => 'number',
|
||||
'type' => 'integer',
|
||||
'operators' => array('equal', 'not_equal'),
|
||||
'validation' => array('min' => 0, 'step' => 1),
|
||||
'help' => __('Event id')
|
||||
),
|
||||
'event_id' => array(
|
||||
'input' => 'number',
|
||||
'type' => 'integer',
|
||||
|
@ -1659,12 +1693,6 @@ class RestResponseComponent extends Component
|
|||
'operators' => array('equal'),
|
||||
'help' => __('Not supported (warninglist->checkvalues) expect an array')
|
||||
),
|
||||
'event' => array(
|
||||
'input' => 'text',
|
||||
'type' => 'string',
|
||||
'operators' => array('equal'),
|
||||
'help' => __('Not supported (removeTag)')
|
||||
),
|
||||
'push_rules' => array(
|
||||
'input' => 'text',
|
||||
'type' => 'string',
|
||||
|
@ -1789,6 +1817,9 @@ class RestResponseComponent extends Component
|
|||
case "last_seen":
|
||||
$this->__overwriteSeen($scope, $action, $fieldsConstraint[$field]);
|
||||
break;
|
||||
case "attribute":
|
||||
$this->__overwriteAttribute($scope, $action, $fieldsConstraint[$field]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -1806,8 +1837,6 @@ class RestResponseComponent extends Component
|
|||
private function __overwriteReturnFormat($scope, $action, &$field) {
|
||||
switch($scope) {
|
||||
case "Attribute":
|
||||
$field['values'] = array_keys(ClassRegistry::init($scope)->validFormats);
|
||||
break;
|
||||
case "Event":
|
||||
$field['values'] = array_keys(ClassRegistry::init($scope)->validFormats);
|
||||
break;
|
||||
|
@ -1872,6 +1901,11 @@ class RestResponseComponent extends Component
|
|||
$field['help'] = __('Also supports array of tags');
|
||||
}
|
||||
}
|
||||
private function __overwriteAttribute($scope, $action, &$field){
|
||||
if ($action == 'addTag' || $action == 'removeTag') {
|
||||
$field['help'] = __('Attribute id');
|
||||
}
|
||||
}
|
||||
private function __overwriteNationality($scope, $action, &$field) {
|
||||
$field['values'] = ClassRegistry::init("Organisation")->getCountries();
|
||||
}
|
||||
|
@ -1880,10 +1914,13 @@ class RestResponseComponent extends Component
|
|||
}
|
||||
private function __overwriteRoleId($scope, $action, &$field) {
|
||||
$this->{$scope} = ClassRegistry::init("Role");
|
||||
$roles = $this->{$scope}->find('column', array(
|
||||
'fields' => array('name')
|
||||
$roles = $this->{$scope}->find('list', array(
|
||||
'fields' => array('id', 'name')
|
||||
));
|
||||
$field['values'] = $roles;
|
||||
$field['values'] = [];
|
||||
foreach ($roles as $id => $name) {
|
||||
$field['values'][] = ['label' => $name, 'value' => $id];
|
||||
}
|
||||
}
|
||||
private function __overwriteSeen($scope, $action, &$field) {
|
||||
if ($action == 'restSearch') {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller');
|
||||
|
||||
/**
|
||||
* @property Dashboard $Dashboard
|
||||
*/
|
||||
class DashboardsController extends AppController
|
||||
{
|
||||
public $components = array('Session', 'RequestHandler');
|
||||
|
@ -147,47 +150,50 @@ class DashboardsController extends AppController
|
|||
|
||||
public function renderWidget($widget_id, $force = false)
|
||||
{
|
||||
if ($this->request->is('post')) {
|
||||
if (empty($this->request->data['data'])) {
|
||||
$this->request->data = array('data' => $this->request->data);
|
||||
|
||||
}
|
||||
if (empty($this->request->data['data'])) {
|
||||
throw new MethodNotAllowedException(__('You need to specify the widget to use along with the configuration.'));
|
||||
}
|
||||
$value = $this->request->data['data'];
|
||||
$dashboardWidget = $this->Dashboard->loadWidget($this->Auth->user(), $value['widget']);
|
||||
$this->layout = false;
|
||||
$this->set('title', $dashboardWidget->title);
|
||||
$redis = $this->Dashboard->setupRedis();
|
||||
$org_scope = $this->_isSiteAdmin() ? 0 : $this->Auth->user('org_id');
|
||||
$lookup_hash = hash('sha256', $value['widget'] . $value['config']);
|
||||
$data = $redis->get('misp:dashboard:' . $org_scope . ':' . $lookup_hash);
|
||||
if (!isset($dashboardWidget->cacheLifetime)) {
|
||||
$dashboardWidget->cacheLifetime = false;
|
||||
}
|
||||
if (empty($dashboardWidget->cacheLifetime) || empty($data)) {
|
||||
$data = $dashboardWidget->handler($this->Auth->user(), json_decode($value['config'], true));
|
||||
if (!empty($dashboardWidget->cacheLifetime)) {
|
||||
$redis->set('misp:dashboard:' . $org_scope . ':' . $lookup_hash, json_encode(array('data' => $data)));
|
||||
$redis->expire('misp:dashboard:' . $org_scope . ':' . $lookup_hash, $dashboardWidget->cacheLifetime);
|
||||
}
|
||||
} else {
|
||||
$data = json_decode($data, true)['data'];
|
||||
}
|
||||
$valueConfig = json_decode($value['config'], true);
|
||||
$config = array(
|
||||
'render' => $dashboardWidget->render,
|
||||
'autoRefreshDelay' => empty($dashboardWidget->autoRefreshDelay) ? false : $dashboardWidget->autoRefreshDelay,
|
||||
'widget_config' => empty($valueConfig['widget_config']) ? array() : $valueConfig['widget_config']
|
||||
);
|
||||
$this->set('widget_id', $widget_id);
|
||||
$this->set('data', $data);
|
||||
$this->set('config', $config);
|
||||
$this->render('widget_loader');
|
||||
} else {
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException(__('This endpoint can only be reached via POST requests.'));
|
||||
}
|
||||
|
||||
@session_write_close(); // allow concurrent AJAX requests (session hold lock by default)
|
||||
|
||||
if (empty($this->request->data['data'])) {
|
||||
$this->request->data = array('data' => $this->request->data);
|
||||
}
|
||||
if (empty($this->request->data['data'])) {
|
||||
throw new MethodNotAllowedException(__('You need to specify the widget to use along with the configuration.'));
|
||||
}
|
||||
$value = $this->request->data['data'];
|
||||
$valueConfig = json_decode($value['config'], true);
|
||||
$dashboardWidget = $this->Dashboard->loadWidget($this->Auth->user(), $value['widget']);
|
||||
|
||||
$redis = $this->Dashboard->setupRedis();
|
||||
$org_scope = $this->_isSiteAdmin() ? 0 : $this->Auth->user('org_id');
|
||||
$lookup_hash = hash('sha256', $value['widget'] . $value['config']);
|
||||
$cacheKey = 'misp:dashboard:' . $org_scope . ':' . $lookup_hash;
|
||||
$data = $redis->get($cacheKey);
|
||||
if (!isset($dashboardWidget->cacheLifetime)) {
|
||||
$dashboardWidget->cacheLifetime = false;
|
||||
}
|
||||
if (empty($dashboardWidget->cacheLifetime) || empty($data)) {
|
||||
$data = $dashboardWidget->handler($this->Auth->user(), $valueConfig);
|
||||
if (!empty($dashboardWidget->cacheLifetime)) {
|
||||
$redis->setex($cacheKey, $dashboardWidget->cacheLifetime, json_encode(array('data' => $data)));
|
||||
}
|
||||
} else {
|
||||
$data = json_decode($data, true)['data'];
|
||||
}
|
||||
$config = array(
|
||||
'render' => $dashboardWidget->render,
|
||||
'autoRefreshDelay' => empty($dashboardWidget->autoRefreshDelay) ? false : $dashboardWidget->autoRefreshDelay,
|
||||
'widget_config' => empty($valueConfig['widget_config']) ? array() : $valueConfig['widget_config']
|
||||
);
|
||||
|
||||
$this->layout = false;
|
||||
$this->set('title', $dashboardWidget->title);
|
||||
$this->set('widget_id', $widget_id);
|
||||
$this->set('data', $data);
|
||||
$this->set('config', $config);
|
||||
$this->render('widget_loader');
|
||||
}
|
||||
|
||||
public function import()
|
||||
|
@ -392,7 +398,7 @@ class DashboardsController extends AppController
|
|||
$element['User']['email'] = '';
|
||||
}
|
||||
}
|
||||
$this->set('passedArgs', $this->passedArgs);
|
||||
$this->set('passedArgs', json_encode($this->passedArgs));
|
||||
$this->set('data', $data);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,6 +50,37 @@ class EventGraphController extends AppController
|
|||
return $this->RestResponse->viewData($eventGraphs, $this->response->type());
|
||||
}
|
||||
|
||||
public function viewPicture($event_id, $graph_id)
|
||||
{
|
||||
$this->loadModel('Event');
|
||||
$event = $this->Event->fetchSimpleEvent($this->Auth->user(), $event_id);
|
||||
if (empty($event)) {
|
||||
throw new NotFoundException('Invalid event');
|
||||
}
|
||||
|
||||
$conditions = [
|
||||
'EventGraph.event_id' => $event['Event']['id'],
|
||||
'EventGraph.org_id' => $this->Auth->user('org_id'),
|
||||
'EventGraph.id' => $graph_id,
|
||||
];
|
||||
$eventGraph = $this->EventGraph->find('first', array(
|
||||
'conditions' => $conditions,
|
||||
'contain' => array(
|
||||
'User' => array(
|
||||
'fields' => array(
|
||||
'User.email'
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
if (empty($eventGraph)) {
|
||||
throw new MethodNotAllowedException('Invalid event graph');
|
||||
}
|
||||
$eventGraph = $eventGraph;
|
||||
$imageData = $this->EventGraph->getPictureData($eventGraph);
|
||||
return new CakeResponse(array('body' => $imageData, 'type' => 'png'));
|
||||
}
|
||||
|
||||
public function add($event_id = false)
|
||||
{
|
||||
if ($this->request->is('get')) {
|
||||
|
|
|
@ -124,12 +124,14 @@ class EventReportsController extends AppController
|
|||
{
|
||||
$report = $this->EventReport->fetchIfAuthorized($this->Auth->user(), $id, 'delete', $throwErrors=true, $full=false);
|
||||
if ($this->request->is('post')) {
|
||||
$errors = $this->EventReport->deleteReport($this->Auth->user(), $report, $hard=$hard);
|
||||
if (!empty($this->request->data['hard'])) {
|
||||
$hard = true;
|
||||
}
|
||||
$errors = $this->EventReport->deleteReport($this->Auth->user(), $report, $hard);
|
||||
$redirectTarget = $this->referer();
|
||||
if (empty($errors)) {
|
||||
$successMessage = __('Event Report %s %s deleted', $id, $hard ? __('hard') : __('soft'));
|
||||
$report = $hard ? null : $this->EventReport->simpleFetchById($this->Auth->user(), $id);
|
||||
return $this->__getSuccessResponseBasedOnContext($successMessage, $report, 'delete', $id, $redirectTarget);
|
||||
return $this->__getSuccessResponseBasedOnContext($successMessage, null, 'delete', $id, $redirectTarget);
|
||||
} else {
|
||||
$errorMessage = __('Event Report %s could not be %s deleted.%sReasons: %s', $id, $hard ? __('hard') : __('soft'), PHP_EOL, json_encode($errors));
|
||||
return $this->__getFailResponseBasedOnContext($errorMessage, array(), 'edit', $id, $redirectTarget);
|
||||
|
@ -153,8 +155,7 @@ class EventReportsController extends AppController
|
|||
$redirectTarget = $this->referer();
|
||||
if (empty($errors)) {
|
||||
$successMessage = __('Event Report %s restored', $id);
|
||||
$report = $this->EventReport->simpleFetchById($this->Auth->user(), $id);
|
||||
return $this->__getSuccessResponseBasedOnContext($successMessage, $report, 'restore', $id, $redirectTarget);
|
||||
return $this->__getSuccessResponseBasedOnContext($successMessage, null, 'restore', $id, $redirectTarget);
|
||||
} else {
|
||||
$errorMessage = __('Event Report %s could not be %s restored.%sReasons: %s', $id, PHP_EOL, json_encode($errors));
|
||||
return $this->__getFailResponseBasedOnContext($errorMessage, array(), 'restore', $id, $redirectTarget);
|
||||
|
|
|
@ -590,13 +590,7 @@ class EventsController extends AppController
|
|||
$searchTermInternal = $searchTerm;
|
||||
if ($searchTerm == 'threatlevel') {
|
||||
$searchTermInternal = 'threat_level_id';
|
||||
$threatLevels = $this->Event->ThreatLevel->find('all', array(
|
||||
'recursive' => -1,
|
||||
'fields' => array('id', 'name'),
|
||||
));
|
||||
foreach ($threatLevels as $tl) {
|
||||
$terms[$tl['ThreatLevel']['id']] = $tl['ThreatLevel']['name'];
|
||||
}
|
||||
$terms = $this->Event->ThreatLevel->listThreatLevels();
|
||||
} elseif ($searchTerm == 'analysis') {
|
||||
$terms = $this->Event->analysisLevels;
|
||||
} else {
|
||||
|
@ -767,7 +761,7 @@ class EventsController extends AppController
|
|||
}
|
||||
}
|
||||
}
|
||||
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, false, false);
|
||||
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, false);
|
||||
}
|
||||
foreach ($events as $key => $event) {
|
||||
if (empty($event['SharingGroup']['name'])) {
|
||||
|
@ -830,7 +824,7 @@ class EventsController extends AppController
|
|||
if (Configure::read('MISP.showDiscussionsCountOnIndex')) {
|
||||
$events = $this->Event->attachDiscussionsCountToEvents($this->Auth->user(), $events);
|
||||
}
|
||||
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, true, false);
|
||||
$events = $this->GalaxyCluster->attachClustersToEventIndex($this->Auth->user(), $events, true);
|
||||
|
||||
if ($this->params['ext'] === 'csv') {
|
||||
App::uses('CsvExport', 'Export');
|
||||
|
@ -838,27 +832,7 @@ class EventsController extends AppController
|
|||
return $this->RestResponse->viewData($export->eventIndex($events), 'csv');
|
||||
}
|
||||
|
||||
$user = $this->Auth->user();
|
||||
$user = $this->Event->User->fillKeysToUser($user);
|
||||
|
||||
if (empty($user['gpgkey']) && Configure::read('GnuPG.onlyencrypted')) {
|
||||
// No GnuPG
|
||||
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
|
||||
// No GnuPG and No SMIME
|
||||
$this->Flash->info(__('No X.509 certificate or GnuPG key set in your profile. To receive emails, submit your public certificate or GnuPG key in your profile.'));
|
||||
} elseif (!Configure::read('SMIME.enabled')) {
|
||||
$this->Flash->info(__('No GnuPG key set in your profile. To receive emails, submit your public key in your profile.'));
|
||||
}
|
||||
} elseif ($this->Auth->user('autoalert') && empty($user['gpgkey']) && Configure::read('GnuPG.bodyonlyencrypted')) {
|
||||
// No GnuPG & autoalert
|
||||
if ($this->Auth->user('autoalert') && Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
|
||||
// No GnuPG and No SMIME & autoalert
|
||||
$this->Flash->info(__('No X.509 certificate or GnuPG key set in your profile. To receive attributes in emails, submit your public certificate or GnuPG key in your profile.'));
|
||||
} elseif (!Configure::read('SMIME.enabled')) {
|
||||
$this->Flash->info(__('No GnuPG key set in your profile. To receive attributes in emails, submit your public key in your profile.'));
|
||||
}
|
||||
}
|
||||
|
||||
$this->__noKeyNotification();
|
||||
$this->set('events', $events);
|
||||
$this->set('eventDescriptions', $this->Event->fieldDescriptions);
|
||||
$this->set('analysisLevels', $this->Event->analysisLevels);
|
||||
|
@ -876,6 +850,34 @@ class EventsController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
private function __noKeyNotification()
|
||||
{
|
||||
$onlyEncrypted = Configure::read('GnuPG.onlyencrypted');
|
||||
$bodyOnlyEncrypted = Configure::read('GnuPG.bodyonlyencrypted');
|
||||
if (!$onlyEncrypted && !$bodyOnlyEncrypted) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = $this->Event->User->fillKeysToUser($this->Auth->user());
|
||||
if (!empty($user['gpgkey'])) {
|
||||
return; // use has PGP key
|
||||
}
|
||||
|
||||
if ($onlyEncrypted) {
|
||||
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
|
||||
$this->Flash->info(__('No X.509 certificate or PGP key set in your profile. To receive emails, submit your public certificate or PGP key in your profile.'));
|
||||
} elseif (!Configure::read('SMIME.enabled')) {
|
||||
$this->Flash->info(__('No PGP key set in your profile. To receive emails, submit your public key in your profile.'));
|
||||
}
|
||||
} elseif ($bodyOnlyEncrypted && $user['autoalert']) {
|
||||
if (Configure::read('SMIME.enabled') && empty($user['certif_public'])) {
|
||||
$this->Flash->info(__('No X.509 certificate or PGP key set in your profile. To receive attributes in emails, submit your public certificate or PGP key in your profile.'));
|
||||
} elseif (!Configure::read('SMIME.enabled')) {
|
||||
$this->Flash->info(__('No PGP key set in your profile. To receive attributes in emails, submit your public key in your profile.'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function filterEventIndex()
|
||||
{
|
||||
$passedArgsArray = array();
|
||||
|
@ -1025,7 +1027,17 @@ class EventsController extends AppController
|
|||
if (isset($filters['focus'])) {
|
||||
$this->set('focus', $filters['focus']);
|
||||
}
|
||||
$conditions = array('eventid' => $id);
|
||||
$conditions = [
|
||||
'eventid' => $id,
|
||||
'includeFeedCorrelations' => true,
|
||||
'includeWarninglistHits' => true,
|
||||
'fetchFullClusters' => false,
|
||||
'includeAllTags' => true,
|
||||
'includeGranularCorrelations' => true,
|
||||
'includeEventCorrelations' => false,
|
||||
'noEventReports' => true, // event reports for view are loaded dynamically
|
||||
'noSightings' => true,
|
||||
];
|
||||
if (isset($filters['extended'])) {
|
||||
$conditions['extended'] = 1;
|
||||
$this->set('extended', 1);
|
||||
|
@ -1048,21 +1060,11 @@ class EventsController extends AppController
|
|||
if (isset($filters['toIDS']) && $filters['toIDS'] != 0) {
|
||||
$conditions['to_ids'] = $filters['toIDS'] == 2 ? 0 : 1;
|
||||
}
|
||||
$conditions['includeFeedCorrelations'] = true;
|
||||
$conditions['includeWarninglistHits'] = true;
|
||||
if (!isset($filters['includeServerCorrelations'])) {
|
||||
$conditions['includeServerCorrelations'] = 1;
|
||||
if ($this->_isRest()) {
|
||||
$conditions['includeServerCorrelations'] = 0;
|
||||
}
|
||||
} else {
|
||||
$conditions['includeServerCorrelations'] = $filters['includeServerCorrelations'];
|
||||
}
|
||||
$conditions['includeAllTags'] = true;
|
||||
$conditions['includeGranularCorrelations'] = 1;
|
||||
$conditions['includeEventCorrelations'] = false;
|
||||
$conditions['noEventReports'] = true; // event reports for view are loaded dynamically
|
||||
$conditions['noSightings'] = true;
|
||||
if (!empty($filters['includeRelatedTags'])) {
|
||||
$this->set('includeRelatedTags', 1);
|
||||
$conditions['includeRelatedTags'] = 1;
|
||||
|
@ -1224,6 +1226,11 @@ class EventsController extends AppController
|
|||
$this->render('/Elements/eventattribute');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $event
|
||||
* @param bool $continue
|
||||
* @param int $fromEvent
|
||||
*/
|
||||
private function __viewUI($event, $continue, $fromEvent)
|
||||
{
|
||||
$this->loadModel('Taxonomy');
|
||||
|
@ -1278,9 +1285,9 @@ class EventsController extends AppController
|
|||
// set the pivot data
|
||||
$this->helpers[] = 'Pivot';
|
||||
if ($continue) {
|
||||
$data = $this->__continuePivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date'], $fromEvent);
|
||||
$this->__continuePivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date'], $fromEvent);
|
||||
} else {
|
||||
$data = $this->__startPivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date']);
|
||||
$this->__startPivoting($event['Event']['id'], $event['Event']['info'], $event['Event']['date']);
|
||||
}
|
||||
$pivot = $this->Session->read('pivot_thread');
|
||||
$this->__arrangePivotVertical($pivot);
|
||||
|
@ -1479,7 +1486,7 @@ class EventsController extends AppController
|
|||
$this->set('includeSightingdb', (!empty($filters['includeSightingdb']) && Configure::read('Plugin.Sightings_sighting_db_enable')));
|
||||
$this->set('relatedEventCorrelationCount', $relatedEventCorrelationCount);
|
||||
$this->set('oldest_timestamp', $oldest_timestamp);
|
||||
$this->set('required_taxonomies', $this->Event->getRequiredTaxonomies());
|
||||
$this->set('missingTaxonomies', $this->Event->missingTaxonomies($event));
|
||||
$this->set('orgTable', $orgTable);
|
||||
$this->set('currentUri', $attributeUri);
|
||||
$this->set('filters', $filters);
|
||||
|
@ -1512,6 +1519,7 @@ class EventsController extends AppController
|
|||
$conditions['includeAllTags'] = true;
|
||||
$conditions['noEventReports'] = true; // event reports for view are loaded dynamically
|
||||
$conditions['noSightings'] = true;
|
||||
$conditions['fetchFullClusters'] = false;
|
||||
}
|
||||
$deleted = 0;
|
||||
if (isset($this->params['named']['deleted'])) {
|
||||
|
@ -1612,37 +1620,74 @@ class EventsController extends AppController
|
|||
}
|
||||
|
||||
if ($this->_isRest()) {
|
||||
$this->set('event', $event);
|
||||
} else {
|
||||
$this->set('deleted', isset($deleted) ? ($deleted == 2 ? 0 : 1) : 0);
|
||||
$this->set('includeRelatedTags', (!empty($this->params['named']['includeRelatedTags'])) ? 1 : 0);
|
||||
$this->set('includeDecayScore', (!empty($this->params['named']['includeDecayScore'])) ? 1 : 0);
|
||||
|
||||
if ($this->_isSiteAdmin() && $event['Event']['orgc_id'] !== $this->Auth->user('org_id')) {
|
||||
$this->Flash->info(__('You are currently logged in as a site administrator and about to edit an event not belonging to your organisation. This goes against the sharing model of MISP. Use a normal user account for day to day work.'));
|
||||
}
|
||||
$this->__viewUI($event, $continue, $fromEvent);
|
||||
return $this->__restResponse($event);
|
||||
}
|
||||
|
||||
$this->set('deleted', isset($deleted) ? ($deleted == 2 ? 0 : 1) : 0);
|
||||
$this->set('includeRelatedTags', (!empty($this->params['named']['includeRelatedTags'])) ? 1 : 0);
|
||||
$this->set('includeDecayScore', (!empty($this->params['named']['includeDecayScore'])) ? 1 : 0);
|
||||
|
||||
if ($this->_isSiteAdmin() && $event['Event']['orgc_id'] !== $this->Auth->user('org_id')) {
|
||||
$this->Flash->info(__('You are currently logged in as a site administrator and about to edit an event not belonging to your organisation. This goes against the sharing model of MISP. Use a normal user account for day to day work.'));
|
||||
}
|
||||
$this->__viewUI($event, $continue, $fromEvent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $info
|
||||
* @param string $date
|
||||
*/
|
||||
private function __startPivoting($id, $info, $date)
|
||||
{
|
||||
$this->Session->write('pivot_thread', null);
|
||||
$initial_pivot = array('id' => $id, 'info' => $info, 'date' => $date, 'depth' => 0, 'height' => 0, 'children' => array(), 'deletable' => true);
|
||||
$this->Session->write('pivot_thread', $initial_pivot);
|
||||
$initialPivot = [
|
||||
'id' => $id,
|
||||
'info' => $info,
|
||||
'date' => $date,
|
||||
'depth' => 0,
|
||||
'height' => 0,
|
||||
'children' => [],
|
||||
'deletable' => true,
|
||||
];
|
||||
$this->Session->write('pivot_thread', $initialPivot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
* @param string $info
|
||||
* @param string $date
|
||||
* @param int $fromEvent
|
||||
*/
|
||||
private function __continuePivoting($id, $info, $date, $fromEvent)
|
||||
{
|
||||
$pivot = $this->Session->read('pivot_thread');
|
||||
$newPivot = array('id' => $id, 'info' => $info, 'date' => $date, 'depth' => null, 'children' => array(), 'deletable' => true);
|
||||
if (!is_array($pivot)) {
|
||||
$this->__startPivoting($id, $info, $date);
|
||||
return;
|
||||
}
|
||||
|
||||
$newPivot = [
|
||||
'id' => $id,
|
||||
'info' => $info,
|
||||
'date' => $date,
|
||||
'depth' => null,
|
||||
'children' => [],
|
||||
'deletable' => true,
|
||||
];
|
||||
if (!$this->__checkForPivot($pivot, $id)) {
|
||||
$pivot = $this->__insertPivot($pivot, $fromEvent, $newPivot, 0);
|
||||
}
|
||||
$this->Session->write('pivot_thread', $pivot);
|
||||
}
|
||||
|
||||
private function __insertPivot($pivot, $oldId, $newPivot, $depth)
|
||||
/**
|
||||
* @param array $pivot
|
||||
* @param int $oldId
|
||||
* @param array $newPivot
|
||||
* @param int $depth
|
||||
* @return array
|
||||
*/
|
||||
private function __insertPivot(array $pivot, $oldId, array $newPivot, $depth)
|
||||
{
|
||||
$depth++;
|
||||
if ($pivot['id'] == $oldId) {
|
||||
|
@ -1658,7 +1703,12 @@ class EventsController extends AppController
|
|||
return $pivot;
|
||||
}
|
||||
|
||||
private function __checkForPivot($pivot, $id)
|
||||
/**
|
||||
* @param array $pivot
|
||||
* @param int $id
|
||||
* @return bool
|
||||
*/
|
||||
private function __checkForPivot(array $pivot, $id)
|
||||
{
|
||||
if ($id == $pivot['id']) {
|
||||
return true;
|
||||
|
@ -1741,6 +1791,9 @@ class EventsController extends AppController
|
|||
|
||||
// search for all attributes in object
|
||||
foreach ($event['Object'] as $k => $object) {
|
||||
if ($this->__valueInFieldAttribute($object, ['id', 'uuid', 'name', 'comment'], $searchFor)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($object['Attribute'] as $k2 => $attribute) {
|
||||
if (!$this->__valueInFieldAttribute($attribute, $filterValue, $searchFor)) {
|
||||
unset($event['Object'][$k]['Attribute'][$k2]);
|
||||
|
@ -1914,9 +1967,7 @@ class EventsController extends AppController
|
|||
if (!empty($validationErrors)) {
|
||||
$event['errors'] = $validationErrors;
|
||||
}
|
||||
$this->set('event', $event);
|
||||
$this->render('view');
|
||||
return true;
|
||||
return $this->__restResponse($event);
|
||||
} else {
|
||||
// redirect to the view of the newly created event
|
||||
$this->Flash->success(__('The event has been saved'));
|
||||
|
@ -2168,9 +2219,12 @@ class EventsController extends AppController
|
|||
if (empty($source_event)) {
|
||||
throw new NotFoundException(__('Invalid source event.'));
|
||||
}
|
||||
$recovered_uuids = [];
|
||||
foreach ($source_event[0]['Attribute'] as &$attribute) {
|
||||
unset($attribute['id']);
|
||||
$originalUUID = $attribute['uuid'];
|
||||
$attribute['uuid'] = CakeText::uuid();
|
||||
$recovered_uuids[$originalUUID] = $attribute['uuid'];
|
||||
unset($attribute['ShadowAttribute']);
|
||||
$attribute['Tag'] = [];
|
||||
foreach ($attribute['AttributeTag'] as $aT) {
|
||||
|
@ -2181,10 +2235,14 @@ class EventsController extends AppController
|
|||
}
|
||||
foreach ($source_event[0]['Object'] as &$object) {
|
||||
unset($object['id']);
|
||||
$originalUUID = $object['uuid'];
|
||||
$object['uuid'] = CakeText::uuid();
|
||||
$recovered_uuids[$originalUUID] = $object['uuid'];
|
||||
foreach ($object['Attribute'] as &$attribute) {
|
||||
unset($attribute['id']);
|
||||
$originalUUID = $attribute['uuid'];
|
||||
$attribute['uuid'] = CakeText::uuid();
|
||||
$recovered_uuids[$originalUUID] = $attribute['uuid'];
|
||||
unset($attribute['ShadowAttribute']);
|
||||
$attribute['Tag'] = [];
|
||||
foreach ($attribute['AttributeTag'] as $aT) {
|
||||
|
@ -2194,21 +2252,39 @@ class EventsController extends AppController
|
|||
unset($attribute['AttributeTag']);
|
||||
}
|
||||
}
|
||||
foreach ($source_event[0]['Object'] as &$object) {
|
||||
foreach ($object['ObjectReference'] as &$reference) {
|
||||
if (isset($recovered_uuids[$object['uuid']])) {
|
||||
$reference['object_uuid'] = $recovered_uuids[$object['uuid']];
|
||||
}
|
||||
if (isset($recovered_uuids[$reference['referenced_uuid']])) {
|
||||
$reference['referenced_uuid'] = $recovered_uuids[$reference['referenced_uuid']];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($source_event[0]['EventReport'] as &$report) {
|
||||
unset($report['id'], $report['event_id']);
|
||||
$report['uuid'] = CakeText::uuid();
|
||||
}
|
||||
$results = [
|
||||
'results' => [
|
||||
'Object' => $source_event[0]['Object'],
|
||||
'Attribute' => $source_event[0]['Attribute']
|
||||
'Attribute' => $source_event[0]['Attribute'],
|
||||
'EventReport' => $source_event[0]['EventReport']
|
||||
]
|
||||
];
|
||||
if ($this->_isRest()) {
|
||||
$this->loadModel('Log');
|
||||
$save_results = ['attributes' => 0, 'objects' => 0];
|
||||
$save_results = ['attributes' => 0, 'objects' => 0, 'eventReports' => 0];
|
||||
foreach ($results['results']['Attribute'] as $attribute) {
|
||||
$this->Event->Attribute->captureAttribute($attribute, $target_id, $this->Auth->user(), false, $this->Log);
|
||||
}
|
||||
foreach ($results['results']['Object'] as $object) {
|
||||
$this->Event->Object->captureObject($object, $target_id, $this->Auth->user(), $this->Log);
|
||||
}
|
||||
foreach ($results['results']['EventReport'] as $report) {
|
||||
$this->Event->EventReport->captureReport($this->Auth->user(), $report, $target_id);
|
||||
}
|
||||
$event = $this->Event->fetchEvent(
|
||||
$this->Auth->user(),
|
||||
[
|
||||
|
@ -2279,9 +2355,7 @@ class EventsController extends AppController
|
|||
$metadata = $this->request->param('named.metadata');
|
||||
$results = $this->Event->fetchEvent($this->Auth->user(), ['eventid' => $id, 'metadata' => $metadata]);
|
||||
$event = $results[0];
|
||||
$this->set('event', $event);
|
||||
$this->render('view');
|
||||
return true;
|
||||
return $this->__restResponse($event);
|
||||
} else {
|
||||
$message = 'Error';
|
||||
if ($this->_isRest()) {
|
||||
|
@ -3699,9 +3773,6 @@ class EventsController extends AppController
|
|||
|
||||
public function filterEventIdsForPush()
|
||||
{
|
||||
if (!$this->userRole['perm_sync']) {
|
||||
throw new MethodNotAllowedException(__('You do not have the permission to do that.'));
|
||||
}
|
||||
if ($this->request->is('post')) {
|
||||
$incomingIDs = array();
|
||||
$incomingEvents = array();
|
||||
|
@ -3714,16 +3785,16 @@ class EventsController extends AppController
|
|||
'recursive' => -1,
|
||||
'fields' => array('Event.uuid', 'Event.timestamp', 'Event.locked'),
|
||||
));
|
||||
foreach ($events as $k => $v) {
|
||||
if ($v['Event']['timestamp'] >= $incomingEvents[$v['Event']['uuid']]) {
|
||||
unset($incomingEvents[$v['Event']['uuid']]);
|
||||
foreach ($events as $event) {
|
||||
if ($event['Event']['timestamp'] >= $incomingEvents[$event['Event']['uuid']]) {
|
||||
unset($incomingEvents[$event['Event']['uuid']]);
|
||||
continue;
|
||||
}
|
||||
if ($v['Event']['locked'] == 0) {
|
||||
unset($incomingEvents[$v['Event']['uuid']]);
|
||||
if ($event['Event']['locked'] == 0) {
|
||||
unset($incomingEvents[$event['Event']['uuid']]);
|
||||
}
|
||||
}
|
||||
$this->set('result', array_keys($incomingEvents));
|
||||
return $this->RestResponse->viewData(array_keys($incomingEvents), $this->response->type());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4506,16 +4577,23 @@ class EventsController extends AppController
|
|||
if ($scope == 'event') {
|
||||
$eventId = $scope_id;
|
||||
} elseif ($scope == 'attribute') {
|
||||
$attribute = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
|
||||
'conditions' => array('Attribute.id' => $scope_id),
|
||||
'fields' => array('event_id'),
|
||||
'flatten' => 1,
|
||||
));
|
||||
if (empty($attribute)) {
|
||||
throw new Exception("Invalid Attribute.");
|
||||
if ($scope_id == 'selected') {
|
||||
if (empty($this->params['named']['eventid'])) {
|
||||
throw new Exception("Invalid Event.");
|
||||
}
|
||||
$eventId = $this->params['named']['eventid'];
|
||||
} else {
|
||||
$attribute = $this->Event->Attribute->fetchAttributes($this->Auth->user(), array(
|
||||
'conditions' => array('Attribute.id' => $scope_id),
|
||||
'fields' => array('event_id'),
|
||||
'flatten' => 1,
|
||||
));
|
||||
if (empty($attribute)) {
|
||||
throw new Exception("Invalid Attribute.");
|
||||
}
|
||||
$attribute = $attribute[0];
|
||||
$eventId = $attribute['Attribute']['event_id'];
|
||||
}
|
||||
$attribute = $attribute[0];
|
||||
$eventId = $attribute['Attribute']['event_id'];
|
||||
} elseif ($scope == 'tag_collection') {
|
||||
$eventId = 0; // no event_id for tag_collection, consider all events
|
||||
} else {
|
||||
|
@ -4616,7 +4694,7 @@ class EventsController extends AppController
|
|||
$this->render('/Elements/view_galaxy_matrix');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Displays all the cluster relations for the provided event
|
||||
public function viewClusterRelations($eventId)
|
||||
{
|
||||
|
@ -4641,8 +4719,7 @@ class EventsController extends AppController
|
|||
$this->loadModel('GalaxyCluster');
|
||||
$clusters = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), array('conditions' => array('GalaxyCluster.id' => $clusterIds)), $full=true);
|
||||
App::uses('ClusterRelationsGraphTool', 'Tools');
|
||||
$grapher = new ClusterRelationsGraphTool();
|
||||
$grapher->construct($this->Auth->user(), $this->GalaxyCluster);
|
||||
$grapher = new ClusterRelationsGraphTool($this->Auth->user(), $this->GalaxyCluster);
|
||||
$relations = $grapher->getNetwork($clusters, $keepNotLinkedClusters=true, $includeReferencingRelation=true);
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->viewData($relations, $this->response->type());
|
||||
|
@ -4682,8 +4759,7 @@ class EventsController extends AppController
|
|||
);
|
||||
|
||||
$this->set('events', $this->paginate());
|
||||
$threat_levels = $this->Event->ThreatLevel->find('all');
|
||||
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
|
||||
$this->set('threatLevels', $this->Event->ThreatLevel->listThreatLevels());
|
||||
$this->set('eventDescriptions', $this->Event->fieldDescriptions);
|
||||
$this->set('analysisLevels', $this->Event->analysisLevels);
|
||||
$this->set('distributionLevels', $this->Event->distributionLevels);
|
||||
|
@ -4910,6 +4986,7 @@ class EventsController extends AppController
|
|||
if (!$event) {
|
||||
throw new NotFoundException(__('Invalid event.'));
|
||||
}
|
||||
$mayModify = $this->__canModifyEvent($event);
|
||||
$eventId = $event['Event']['id'];
|
||||
|
||||
$this->loadModel('Module');
|
||||
|
@ -5080,7 +5157,7 @@ class EventsController extends AppController
|
|||
$this->set('module', $module);
|
||||
$this->set('eventId', $eventId);
|
||||
$this->set('event', $event);
|
||||
$this->set('mayModify', $this->__canModifyEvent($event));
|
||||
$this->set('mayModify', $mayModify);
|
||||
}
|
||||
|
||||
public function exportModule($module, $id, $standard = false)
|
||||
|
@ -5141,7 +5218,7 @@ class EventsController extends AppController
|
|||
if ($this->request->is('Post')) {
|
||||
if (Configure::read('Plugin.ZeroMQ_enable')) {
|
||||
$pubSubTool = $this->Event->getPubSubTool();
|
||||
$event = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $id));
|
||||
$event = $this->Event->fetchEvent($this->Auth->user(), array('eventid' => $id, 'includeAllTags' => true));
|
||||
if (!empty($event)) {
|
||||
$pubSubTool->publishEvent($event[0]);
|
||||
$success = 1;
|
||||
|
@ -5188,7 +5265,7 @@ class EventsController extends AppController
|
|||
$kafkaPubTopic = Configure::read('Plugin.Kafka_event_publish_notifications_topic');
|
||||
if (!empty($event['Event']['published']) && Configure::read('Plugin.Kafka_event_publish_notifications_enable') && !empty($kafkaPubTopic)) {
|
||||
$kafkaPubTool = $this->Event->getKafkaPubTool();
|
||||
$params = array('eventid' => $id);
|
||||
$params = array('eventid' => $id, 'includeAllTags' => true);
|
||||
if (Configure::read('Plugin.Kafka_include_attachments')) {
|
||||
$params['includeAttachments'] = 1;
|
||||
}
|
||||
|
@ -5284,9 +5361,43 @@ class EventsController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
public function addEventLock($id)
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException('This endpoint requires a POST request.');
|
||||
}
|
||||
|
||||
$event = $this->Event->fetchSimpleEvent($this->Auth->user(), $id);
|
||||
if (empty($event)) {
|
||||
throw new MethodNotAllowedException(__('Invalid Event'));
|
||||
}
|
||||
if (!$this->__canModifyEvent($event)) {
|
||||
throw new UnauthorizedException(__('You do not have permission to do that.'));
|
||||
}
|
||||
|
||||
$this->loadModel('EventLock');
|
||||
$lockId = $this->EventLock->insertLockApi($event['Event']['id'], $this->Auth->user());
|
||||
return $this->RestResponse->viewData(['lock_id' => $lockId], $this->response->type());
|
||||
}
|
||||
|
||||
public function removeEventLock($id, $lockId)
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException('This endpoint requires a POST request.');
|
||||
}
|
||||
|
||||
$event = $this->Event->fetchSimpleEvent($this->Auth->User(), $id);
|
||||
if (empty($event)) {
|
||||
throw new MethodNotAllowedException(__('Invalid Event'));
|
||||
}
|
||||
|
||||
$this->loadModel('EventLock');
|
||||
$deleted = $this->EventLock->deleteApiLock($event['Event']['id'], $lockId, $this->Auth->user());
|
||||
return $this->RestResponse->viewData(['deleted' => $deleted], $this->response->type());
|
||||
}
|
||||
|
||||
public function checkLocks($id)
|
||||
{
|
||||
$this->loadModel('EventLock');
|
||||
$event = $this->Event->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array('Event.id' => $id),
|
||||
|
@ -5294,29 +5405,34 @@ class EventsController extends AppController
|
|||
));
|
||||
$locks = array();
|
||||
if (!empty($event) && ($event['Event']['orgc_id'] == $this->Auth->user('org_id') || $this->_isSiteAdmin())) {
|
||||
$this->loadModel('EventLock');
|
||||
$locks = $this->EventLock->checkLock($this->Auth->user(), $id);
|
||||
}
|
||||
if (!empty($locks)) {
|
||||
$temp = $locks;
|
||||
$locks = array();
|
||||
foreach ($temp as $t) {
|
||||
if ($t['User']['id'] !== $this->Auth->user('id')) {
|
||||
if ($t['type'] === 'user' && $t['User']['id'] !== $this->Auth->user('id')) {
|
||||
if (!$this->_isSiteAdmin() && $t['User']['org_id'] != $this->Auth->user('org_id')) {
|
||||
continue;
|
||||
$locks[] = __('another user');
|
||||
} else {
|
||||
$locks[] = $t['User']['email'];
|
||||
}
|
||||
$locks[] = $t['User']['email'];
|
||||
} else if ($t['type'] === 'job') {
|
||||
$locks[] = __('background job');
|
||||
} else if ($t['type'] === 'api') {
|
||||
$locks[] = __('external tool');
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO: i18n
|
||||
if (!empty($locks)) {
|
||||
$message = sprintf('Warning: Your view on this event might not be up to date as it is currently being edited by: %s', implode(', ', $locks));
|
||||
$this->set('message', $message);
|
||||
$this->layout = false;
|
||||
$this->render('/Events/ajax/event_lock');
|
||||
} else {
|
||||
if (empty($locks)) {
|
||||
return $this->RestResponse->viewData('', $this->response->type(), false, true);
|
||||
}
|
||||
|
||||
$message = __('Warning: Your view on this event might not be up to date as it is currently being edited by: %s', implode(', ', $locks));
|
||||
$this->set('message', $message);
|
||||
$this->layout = false;
|
||||
$this->render('/Events/ajax/event_lock');
|
||||
}
|
||||
|
||||
public function getEditStrategy($id)
|
||||
|
@ -5596,14 +5712,14 @@ class EventsController extends AppController
|
|||
$job = ClassRegistry::init('Job');
|
||||
$job->create();
|
||||
$data = array(
|
||||
'worker' => $this->Event->__getPrioWorkerIfPossible(),
|
||||
'job_type' => $job_type,
|
||||
'job_input' => sprintf('Event ID: %s', $id),
|
||||
'status' => 0,
|
||||
'retries' => 0,
|
||||
'org_id' => 0,
|
||||
'org' => 'ADMIN',
|
||||
'message' => $message
|
||||
'worker' => 'prio',
|
||||
'job_type' => $job_type,
|
||||
'job_input' => sprintf('Event ID: %s', $id),
|
||||
'status' => 0,
|
||||
'retries' => 0,
|
||||
'org_id' => 0,
|
||||
'org' => 'ADMIN',
|
||||
'message' => $message
|
||||
);
|
||||
$job->save($data);
|
||||
$jobId = $job->id;
|
||||
|
@ -5681,4 +5797,37 @@ class EventsController extends AppController
|
|||
}
|
||||
return $this->RestResponse->viewData($allConflicts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $event
|
||||
* @return CakeResponseTmp
|
||||
* @throws Exception
|
||||
*/
|
||||
private function __restResponse(array $event)
|
||||
{
|
||||
$tmpFile = new TmpFileTool();
|
||||
|
||||
if ($this->request->is('json')) {
|
||||
App::uses('JSONConverterTool', 'Tools');
|
||||
$converter = new JSONConverterTool();
|
||||
if ($this->RestResponse->isAutomaticTool()) {
|
||||
foreach ($converter->streamConvert($event) as $part) {
|
||||
$tmpFile->write($part);
|
||||
}
|
||||
} else {
|
||||
$tmpFile->write($converter->convert($event));
|
||||
}
|
||||
$format = 'json';
|
||||
} elseif ($this->request->is('xml')) {
|
||||
App::uses('XMLConverterTool', 'Tools');
|
||||
$converter = new XMLConverterTool();
|
||||
foreach ($converter->frameCollection($converter->convert($event)) as $chunk) {
|
||||
$tmpFile->write($chunk);
|
||||
}
|
||||
$format = 'xml';
|
||||
} else {
|
||||
throw new Exception("Invalid format, only JSON or XML is supported.");
|
||||
}
|
||||
return $this->RestResponse->viewData($tmpFile, $format, false, true);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -606,26 +606,30 @@ class FeedsController extends AppController
|
|||
|
||||
public function previewIndex($feedId)
|
||||
{
|
||||
$this->Feed->id = $feedId;
|
||||
if (!$this->Feed->exists()) {
|
||||
$feed = $this->Feed->find('first', [
|
||||
'conditions' => ['id' => $feedId],
|
||||
'recursive' => -1,
|
||||
]);
|
||||
if (empty($feed)) {
|
||||
throw new NotFoundException(__('Invalid feed.'));
|
||||
}
|
||||
$this->Feed->read();
|
||||
if (!empty($this->Feed->data['Feed']['settings'])) {
|
||||
$this->Feed->data['Feed']['settings'] = json_decode($this->Feed->data['Feed']['settings'], true);
|
||||
if (!empty($feed['Feed']['settings'])) {
|
||||
$feed['Feed']['settings'] = json_decode($feed['Feed']['settings'], true);
|
||||
}
|
||||
$params = array();
|
||||
if ($this->request->is('post')) {
|
||||
$params = $this->request->data['Feed'];
|
||||
}
|
||||
if ($this->Feed->data['Feed']['source_format'] == 'misp') {
|
||||
return $this->__previewIndex($this->Feed->data, $params);
|
||||
} elseif (in_array($this->Feed->data['Feed']['source_format'], array('freetext', 'csv'))) {
|
||||
return $this->__previewFreetext($this->Feed->data);
|
||||
if ($feed['Feed']['source_format'] === 'misp') {
|
||||
return $this->__previewIndex($feed, $params);
|
||||
} elseif (in_array($feed['Feed']['source_format'], ['freetext', 'csv'], true)) {
|
||||
return $this->__previewFreetext($feed);
|
||||
} else {
|
||||
throw new Exception("Invalid feed format `{$feed['Feed']['source_format']}`.");
|
||||
}
|
||||
}
|
||||
|
||||
private function __previewIndex($feed, $filterParams = array())
|
||||
private function __previewIndex(array $feed, $filterParams = array())
|
||||
{
|
||||
$urlparams = '';
|
||||
App::uses('CustomPaginationTool', 'Tools');
|
||||
|
@ -690,8 +694,7 @@ class FeedsController extends AppController
|
|||
}
|
||||
$this->set('events', $events);
|
||||
$this->loadModel('Event');
|
||||
$threat_levels = $this->Event->ThreatLevel->find('all');
|
||||
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
|
||||
$this->set('threatLevels', $this->Event->ThreatLevel->listThreatLevels());
|
||||
$this->set('eventDescriptions', $this->Event->fieldDescriptions);
|
||||
$this->set('analysisLevels', $this->Event->analysisLevels);
|
||||
$this->set('distributionLevels', $this->Event->distributionLevels);
|
||||
|
@ -704,7 +707,7 @@ class FeedsController extends AppController
|
|||
$this->set('passedArgsArray', $passedArgs);
|
||||
}
|
||||
|
||||
private function __previewFreetext($feed)
|
||||
private function __previewFreetext(array $feed)
|
||||
{
|
||||
if (isset($this->passedArgs['page'])) {
|
||||
$currentPage = $this->passedArgs['page'];
|
||||
|
@ -759,13 +762,15 @@ class FeedsController extends AppController
|
|||
|
||||
public function previewEvent($feedId, $eventUuid, $all = false)
|
||||
{
|
||||
$this->Feed->id = $feedId;
|
||||
if (!$this->Feed->exists()) {
|
||||
$feed = $this->Feed->find('first', [
|
||||
'conditions' => ['id' => $feedId],
|
||||
'recursive' => -1,
|
||||
]);
|
||||
if (empty($feed)) {
|
||||
throw new NotFoundException(__('Invalid feed.'));
|
||||
}
|
||||
$this->Feed->read();
|
||||
try {
|
||||
$event = $this->Feed->downloadEventFromFeed($this->Feed->data, $eventUuid);
|
||||
$event = $this->Feed->downloadEventFromFeed($feed, $eventUuid);
|
||||
} catch (Exception $e) {
|
||||
throw new Exception(__('Could not download the selected Event'), 0, $e);
|
||||
}
|
||||
|
@ -782,11 +787,11 @@ class FeedsController extends AppController
|
|||
$params = $this->Event->rearrangeEventForView($event, $this->passedArgs, $all);
|
||||
$this->params->params['paging'] = array('Feed' => $params);
|
||||
$this->set('event', $event);
|
||||
$this->set('feed', $this->Feed->data);
|
||||
$this->set('feed', $feed);
|
||||
$this->loadModel('Event');
|
||||
$dataForView = array(
|
||||
'Attribute' => array('attrDescriptions' => 'fieldDescriptions', 'distributionDescriptions' => 'distributionDescriptions', 'distributionLevels' => 'distributionLevels'),
|
||||
'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisLevels' => 'analysisLevels')
|
||||
'Attribute' => array('attrDescriptions' => 'fieldDescriptions', 'distributionDescriptions' => 'distributionDescriptions', 'distributionLevels' => 'distributionLevels'),
|
||||
'Event' => array('eventDescriptions' => 'fieldDescriptions', 'analysisLevels' => 'analysisLevels')
|
||||
);
|
||||
foreach ($dataForView as $m => $variables) {
|
||||
if ($m === 'Event') {
|
||||
|
@ -798,8 +803,7 @@ class FeedsController extends AppController
|
|||
$this->set($alias, $currentModel->{$variable});
|
||||
}
|
||||
}
|
||||
$threat_levels = $this->Event->ThreatLevel->find('all');
|
||||
$this->set('threatLevels', Set::combine($threat_levels, '{n}.ThreatLevel.id', '{n}.ThreatLevel.name'));
|
||||
$this->set('threatLevels', $this->Event->ThreatLevel->list());
|
||||
} else {
|
||||
if ($event === 'blocked') {
|
||||
throw new MethodNotAllowedException(__('This event is blocked by the Feed filters.'));
|
||||
|
|
|
@ -267,6 +267,7 @@ class GalaxiesController extends AppController
|
|||
{
|
||||
$mitreAttackGalaxyId = $this->Galaxy->getMitreAttackGalaxyId();
|
||||
$local = !empty($this->params['named']['local']) ? $this->params['named']['local'] : '0';
|
||||
$eventid = !empty($this->params['named']['eventid']) ? $this->params['named']['eventid'] : '0';
|
||||
$conditions = $namespace === '0' ? array() : array('namespace' => $namespace);
|
||||
$galaxies = $this->Galaxy->find('all', array(
|
||||
'recursive' => -1,
|
||||
|
@ -278,14 +279,14 @@ class GalaxiesController extends AppController
|
|||
$items = array(
|
||||
array(
|
||||
'name' => __('All clusters'),
|
||||
'value' => $this->baseurl . "/galaxies/selectCluster/" . h($target_id) . '/' . h($target_type) . '/0'. '/local:' . $local
|
||||
'value' => $this->baseurl . "/galaxies/selectCluster/" . h($target_id) . '/' . h($target_type) . '/0'. '/local:' . $local . '/eventid:' . $eventid
|
||||
)
|
||||
);
|
||||
foreach ($galaxies as $galaxy) {
|
||||
if (!isset($galaxy['Galaxy']['kill_chain_order']) || $noGalaxyMatrix) {
|
||||
$items[] = array(
|
||||
'name' => h($galaxy['Galaxy']['name']),
|
||||
'value' => $this->baseurl . "/galaxies/selectCluster/" . $target_id . '/' . $target_type . '/' . $galaxy['Galaxy']['id'] . '/local:' . $local,
|
||||
'value' => $this->baseurl . "/galaxies/selectCluster/" . $target_id . '/' . $target_type . '/' . $galaxy['Galaxy']['id'] . '/local:' . $local . '/eventid:' . $eventid,
|
||||
'template' => array(
|
||||
'preIcon' => 'fa-' . $galaxy['Galaxy']['icon'],
|
||||
'name' => $galaxy['Galaxy']['name'],
|
||||
|
@ -295,12 +296,14 @@ class GalaxiesController extends AppController
|
|||
} else { // should use matrix instead
|
||||
$param = array(
|
||||
'name' => $galaxy['Galaxy']['name'],
|
||||
'value' => $this->baseurl . "/galaxies/selectCluster/" . $target_id . '/' . $target_type . '/' . $galaxy['Galaxy']['id'] . '/local:' . $local . '/eventid:' . $eventid,
|
||||
'functionName' => sprintf(
|
||||
"getMatrixPopup('%s', '%s', '%s/local:%s')",
|
||||
"getMatrixPopup('%s', '%s', '%s/local:%s/eventid:%s')",
|
||||
$target_type,
|
||||
$target_id,
|
||||
$galaxy['Galaxy']['id'],
|
||||
$local
|
||||
$local,
|
||||
$eventid
|
||||
),
|
||||
'isPill' => true,
|
||||
'isMatrix' => true
|
||||
|
@ -325,15 +328,17 @@ class GalaxiesController extends AppController
|
|||
'order' => array('namespace asc')
|
||||
));
|
||||
$local = !empty($this->params['named']['local']) ? '1' : '0';
|
||||
$eventid = !empty($this->params['named']['eventid']) ? $this->params['named']['eventid'] : '0';
|
||||
$items = array();
|
||||
$noGalaxyMatrix = $noGalaxyMatrix ? '1' : '0';
|
||||
$items[] = array(
|
||||
'name' => __('All namespaces'),
|
||||
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . $target_id . '/' . $target_type . '/0' . '/' . $noGalaxyMatrix . '/local:' . $local
|
||||
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . $target_id . '/' . $target_type . '/0' . '/' . $noGalaxyMatrix . '/local:' . $local . '/eventid:' . $eventid
|
||||
);
|
||||
foreach ($namespaces as $namespace) {
|
||||
$items[] = array(
|
||||
'name' => $namespace,
|
||||
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . $target_id . '/' . $target_type . '/' . $namespace . '/' . $noGalaxyMatrix . '/local:' . $local
|
||||
'value' => $this->baseurl . "/galaxies/selectGalaxy/" . $target_id . '/' . $target_type . '/' . $namespace . '/' . $noGalaxyMatrix . '/local:' . $local . '/eventid:' . $eventid
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -553,8 +558,8 @@ class GalaxiesController extends AppController
|
|||
if (empty($clusters)) {
|
||||
throw new MethodNotAllowedException('Invalid Galaxy.');
|
||||
}
|
||||
$this->Galaxy->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
|
||||
foreach ($clusters as $k => $cluster) {
|
||||
$clusters[$k] = $this->Galaxy->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters[$k]);
|
||||
$clusters[$k] = $this->Galaxy->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
|
||||
}
|
||||
$galaxy = $this->Galaxy->find('first', array(
|
||||
|
@ -581,8 +586,7 @@ class GalaxiesController extends AppController
|
|||
'conditions' => array('Galaxy.id' => $galaxyId)
|
||||
));
|
||||
App::uses('ClusterRelationsGraphTool', 'Tools');
|
||||
$grapher = new ClusterRelationsGraphTool();
|
||||
$grapher->construct($this->Auth->user(), $this->Galaxy->GalaxyCluster);
|
||||
$grapher = new ClusterRelationsGraphTool($this->Auth->user(), $this->Galaxy->GalaxyCluster);
|
||||
$relations = $grapher->getNetwork($clusters);
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->viewData($relations, $this->response->type());
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
<?php
|
||||
App::uses('AppController', 'Controller');
|
||||
|
||||
/**
|
||||
* @property GalaxyClusterRelation $GalaxyClusterRelation
|
||||
*/
|
||||
class GalaxyClusterRelationsController extends AppController
|
||||
{
|
||||
public $components = array('Session', 'RequestHandler');
|
||||
|
@ -145,6 +148,7 @@ class GalaxyClusterRelationsController extends AppController
|
|||
|
||||
if (empty($errors)) {
|
||||
$message = __('Relationship added.');
|
||||
$this->GalaxyClusterRelation->SourceCluster->touchTimestamp($clusterSource['SourceCluster']['id']);
|
||||
$this->GalaxyClusterRelation->SourceCluster->unpublish($clusterSource['SourceCluster']['id']);
|
||||
} else {
|
||||
$message = __('Relationship could not be added.');
|
||||
|
@ -174,8 +178,7 @@ class GalaxyClusterRelationsController extends AppController
|
|||
}
|
||||
}
|
||||
}
|
||||
$existingRelations = $this->GalaxyClusterRelation->getExistingRelationships();
|
||||
$this->set('existingRelations', $existingRelations);
|
||||
$this->set('existingRelations', $this->GalaxyClusterRelation->getExistingRelationships());
|
||||
$this->set('distributionLevels', $distributionLevels);
|
||||
$this->set('initialDistribution', $initialDistribution);
|
||||
$this->set('sharingGroups', $sgs);
|
||||
|
@ -240,6 +243,7 @@ class GalaxyClusterRelationsController extends AppController
|
|||
|
||||
if (empty($errors)) {
|
||||
$message = __('Relationship added.');
|
||||
$this->GalaxyClusterRelation->SourceCluster->touchTimestamp($clusterSource['SourceCluster']['id']);
|
||||
$this->GalaxyClusterRelation->SourceCluster->unpublish($clusterSource['SourceCluster']['id']);
|
||||
} else {
|
||||
$message = __('Relationship could not be added.');
|
||||
|
@ -264,6 +268,7 @@ class GalaxyClusterRelationsController extends AppController
|
|||
}
|
||||
}
|
||||
$this->request->data = $existingRelation;
|
||||
$this->set('existingRelations', $this->GalaxyClusterRelation->getExistingRelationships());
|
||||
$this->set('distributionLevels', $distributionLevels);
|
||||
$this->set('initialDistribution', $initialDistribution);
|
||||
$this->set('sharingGroups', $sgs);
|
||||
|
@ -282,6 +287,7 @@ class GalaxyClusterRelationsController extends AppController
|
|||
$clusterSource = $this->GalaxyClusterRelation->SourceCluster->fetchIfAuthorized($this->Auth->user(), $relation['GalaxyClusterRelation']['galaxy_cluster_uuid'], array('edit', 'publish'), $throwErrors=true, $full=false);
|
||||
$result = $this->GalaxyClusterRelation->delete($id, true);
|
||||
if ($result) {
|
||||
$this->GalaxyClusterRelation->SourceCluster->touchTimestamp($clusterSource['SourceCluster']['id']);
|
||||
$this->GalaxyClusterRelation->SourceCluster->unpublish($clusterSource['SourceCluster']['id']);
|
||||
$message = __('Galaxy cluster relationship successfuly deleted.');
|
||||
if ($this->_isRest()) {
|
||||
|
|
|
@ -56,7 +56,7 @@ class GalaxyClustersController extends AppController
|
|||
$contextConditions['GalaxyCluster.deleted'] = true;
|
||||
}
|
||||
|
||||
$this->set('passedArgsArray', array('context' => $filters['context'], 'searchall' => isset($filters['searchall']) ? $filters['searchall'] : ''));
|
||||
$this->set('passedArgs', json_encode(array('context' => $filters['context'], 'searchall' => isset($filters['searchall']) ? $filters['searchall'] : '')));
|
||||
$this->set('context', $filters['context']);
|
||||
$searchConditions = array();
|
||||
if (empty($filters['searchall'])) {
|
||||
|
@ -96,62 +96,64 @@ class GalaxyClustersController extends AppController
|
|||
)
|
||||
);
|
||||
return $this->RestResponse->viewData($clusters, $this->response->type());
|
||||
} else {
|
||||
$this->paginate['conditions']['AND'][] = $contextConditions;
|
||||
$this->paginate['conditions']['AND'][] = $searchConditions;
|
||||
$this->paginate['conditions']['AND'][] = $aclConditions;
|
||||
$this->paginate['contain'] = array_merge($this->paginate['contain'], array('Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation'));
|
||||
$clusters = $this->paginate();
|
||||
|
||||
$tagIds = array();
|
||||
foreach ($clusters as $k => $cluster) {
|
||||
$clusters[$k] = $this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters[$k]);
|
||||
$clusters[$k] = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
|
||||
$clusters[$k]['GalaxyCluster']['relation_counts'] = array(
|
||||
'out' => count($clusters[$k]['GalaxyClusterRelation']),
|
||||
'in' => count($clusters[$k]['TargetingClusterRelation']),
|
||||
);
|
||||
|
||||
if (isset($cluster['Tag']['id'])) {
|
||||
$tagIds[] = $cluster['Tag']['id'];
|
||||
$clusters[$k]['GalaxyCluster']['tag_id'] = $cluster['Tag']['id'];
|
||||
}
|
||||
$clusters[$k]['GalaxyCluster']['synonyms'] = array();
|
||||
foreach ($cluster['GalaxyElement'] as $element) {
|
||||
$clusters[$k]['GalaxyCluster']['synonyms'][] = $element['value'];
|
||||
}
|
||||
$clusters[$k]['GalaxyCluster']['event_count'] = 0; // real number is assigned later
|
||||
}
|
||||
|
||||
$eventCountsForTags = $this->GalaxyCluster->Tag->EventTag->countForTags($tagIds, $this->Auth->user());
|
||||
|
||||
$this->loadModel('Sighting');
|
||||
$csvForTags = $this->Sighting->tagsSparkline($tagIds, $this->Auth->user(), '0');
|
||||
foreach ($clusters as $k => $cluster) {
|
||||
if (isset($cluster['GalaxyCluster']['tag_id'])) {
|
||||
if (isset($csvForTags[$cluster['GalaxyCluster']['tag_id']])) {
|
||||
$clusters[$k]['csv'] = $csvForTags[$cluster['GalaxyCluster']['tag_id']];
|
||||
}
|
||||
if (isset($eventCountsForTags[$cluster['GalaxyCluster']['tag_id']])) {
|
||||
$clusters[$k]['GalaxyCluster']['event_count'] = $eventCountsForTags[$cluster['GalaxyCluster']['tag_id']];
|
||||
}
|
||||
}
|
||||
}
|
||||
$customClusterCount = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), [
|
||||
'count' => true,
|
||||
'conditions' => [
|
||||
'AND' => [$searchConditions, $aclConditions],
|
||||
'GalaxyCluster.default' => 0,
|
||||
]
|
||||
]);
|
||||
$this->loadModel('Attribute');
|
||||
$distributionLevels = $this->Attribute->distributionLevels;
|
||||
unset($distributionLevels[5]);
|
||||
$this->set('distributionLevels', $distributionLevels);
|
||||
$this->set('list', $clusters);
|
||||
$this->set('galaxy_id', $galaxyId);
|
||||
$this->set('custom_cluster_count', $customClusterCount);
|
||||
}
|
||||
|
||||
$this->paginate['conditions']['AND'][] = $contextConditions;
|
||||
$this->paginate['conditions']['AND'][] = $searchConditions;
|
||||
$this->paginate['conditions']['AND'][] = $aclConditions;
|
||||
$this->paginate['contain'] = array_merge($this->paginate['contain'], array('Org', 'Orgc', 'SharingGroup', 'GalaxyClusterRelation', 'TargetingClusterRelation'));
|
||||
$clusters = $this->paginate();
|
||||
|
||||
$this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
|
||||
|
||||
$tagIds = array();
|
||||
foreach ($clusters as $k => $cluster) {
|
||||
$clusters[$k] = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $clusters[$k]);
|
||||
$clusters[$k]['GalaxyCluster']['relation_counts'] = array(
|
||||
'out' => count($clusters[$k]['GalaxyClusterRelation']),
|
||||
'in' => count($clusters[$k]['TargetingClusterRelation']),
|
||||
);
|
||||
|
||||
if (isset($cluster['Tag']['id'])) {
|
||||
$tagIds[] = $cluster['Tag']['id'];
|
||||
$clusters[$k]['GalaxyCluster']['tag_id'] = $cluster['Tag']['id'];
|
||||
}
|
||||
$clusters[$k]['GalaxyCluster']['synonyms'] = array();
|
||||
foreach ($cluster['GalaxyElement'] as $element) {
|
||||
$clusters[$k]['GalaxyCluster']['synonyms'][] = $element['value'];
|
||||
}
|
||||
$clusters[$k]['GalaxyCluster']['event_count'] = 0; // real number is assigned later
|
||||
}
|
||||
|
||||
$eventCountsForTags = $this->GalaxyCluster->Tag->EventTag->countForTags($tagIds, $this->Auth->user());
|
||||
|
||||
$this->loadModel('Sighting');
|
||||
$csvForTags = $this->Sighting->tagsSparkline($tagIds, $this->Auth->user(), '0');
|
||||
foreach ($clusters as $k => $cluster) {
|
||||
if (isset($cluster['GalaxyCluster']['tag_id'])) {
|
||||
if (isset($csvForTags[$cluster['GalaxyCluster']['tag_id']])) {
|
||||
$clusters[$k]['csv'] = $csvForTags[$cluster['GalaxyCluster']['tag_id']];
|
||||
}
|
||||
if (isset($eventCountsForTags[$cluster['GalaxyCluster']['tag_id']])) {
|
||||
$clusters[$k]['GalaxyCluster']['event_count'] = $eventCountsForTags[$cluster['GalaxyCluster']['tag_id']];
|
||||
}
|
||||
}
|
||||
}
|
||||
$customClusterCount = $this->GalaxyCluster->fetchGalaxyClusters($this->Auth->user(), [
|
||||
'count' => true,
|
||||
'conditions' => [
|
||||
'AND' => [$searchConditions, $aclConditions],
|
||||
'GalaxyCluster.default' => 0,
|
||||
]
|
||||
]);
|
||||
$this->loadModel('Attribute');
|
||||
$distributionLevels = $this->Attribute->distributionLevels;
|
||||
unset($distributionLevels[5]);
|
||||
$this->set('distributionLevels', $distributionLevels);
|
||||
$this->set('list', $clusters);
|
||||
$this->set('galaxy_id', $galaxyId);
|
||||
$this->set('custom_cluster_count', $customClusterCount);
|
||||
|
||||
if ($this->request->is('ajax')) {
|
||||
$this->layout = 'ajax';
|
||||
$this->render('ajax/index');
|
||||
|
@ -179,7 +181,9 @@ class GalaxyClustersController extends AppController
|
|||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->viewData($cluster, $this->response->type());
|
||||
} else {
|
||||
$cluster = $this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $cluster);
|
||||
$clusters = [$cluster];
|
||||
$this->GalaxyCluster->attachExtendByInfo($this->Auth->user(), $clusters);
|
||||
$cluster = $clusters[0];
|
||||
$cluster = $this->GalaxyCluster->attachExtendFromInfo($this->Auth->user(), $cluster);
|
||||
$this->set('id', $id);
|
||||
$this->set('galaxy', ['Galaxy' => $cluster['GalaxyCluster']['Galaxy']]);
|
||||
|
@ -391,7 +395,9 @@ class GalaxyClustersController extends AppController
|
|||
|
||||
if (empty($cluster['GalaxyCluster']['authors'])) {
|
||||
$cluster['GalaxyCluster']['authors'] = [];
|
||||
} else {
|
||||
} else if (is_array($cluster['GalaxyCluster']['authors'])) {
|
||||
// This is as intended, move on
|
||||
}else {
|
||||
$decoded = json_decode($cluster['GalaxyCluster']['authors'], true);
|
||||
if (is_null($decoded)) { // authors might be comma separated
|
||||
$decoded = array_map('trim', explode(',', $cluster['GalaxyCluster']['authors']));
|
||||
|
@ -703,6 +709,9 @@ class GalaxyClustersController extends AppController
|
|||
{
|
||||
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'delete', $throwErrors=true, $full=false);
|
||||
if ($this->request->is('post')) {
|
||||
if (!empty($this->request->data['hard'])) {
|
||||
$hard = true;
|
||||
}
|
||||
$result = $this->GalaxyCluster->deleteCluster($cluster['GalaxyCluster']['id'], $hard=$hard);
|
||||
$galaxyId = $cluster['GalaxyCluster']['galaxy_id'];
|
||||
if ($result) {
|
||||
|
@ -712,7 +721,7 @@ class GalaxyClustersController extends AppController
|
|||
$hard ? __(' and added to the block list') : ''
|
||||
);
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $this->response->type());
|
||||
return $this->RestResponse->saveSuccessResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $this->response->type(), $message);
|
||||
} else {
|
||||
$this->Flash->success($message);
|
||||
$this->redirect(array('controller' => 'galaxies', 'action' => 'view', $galaxyId));
|
||||
|
@ -720,7 +729,7 @@ class GalaxyClustersController extends AppController
|
|||
} else {
|
||||
$message = __('Galaxy cluster could not be %s deleted.', $hard ? __('hard') : __('soft'));
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $message, $this->response->type());
|
||||
return $this->RestResponse->saveFailResponse('GalaxyCluster', 'delete', $cluster['GalaxyCluster']['id'], $message, $this->response->type(), $message);
|
||||
} else {
|
||||
$this->Flash->error($message);
|
||||
$this->redirect(array('controller' => 'galaxies', 'action' => 'view', $galaxyId));
|
||||
|
@ -967,7 +976,7 @@ class GalaxyClustersController extends AppController
|
|||
if (!$this->request->is('ajax')) {
|
||||
throw new MethodNotAllowedException('This function can only be reached via AJAX.');
|
||||
}
|
||||
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', $throwErrors=true, $full=true);
|
||||
$cluster = $this->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $id, 'view', true, true);
|
||||
$existingRelations = $this->GalaxyCluster->GalaxyClusterRelation->getExistingRelationships();
|
||||
$cluster = $this->GalaxyCluster->attachClusterToRelations($this->Auth->user(), $cluster);
|
||||
|
||||
|
@ -978,12 +987,8 @@ class GalaxyClustersController extends AppController
|
|||
|
||||
$this->set('existingRelations', $existingRelations);
|
||||
$this->set('cluster', $cluster);
|
||||
$relations = $this->GalaxyCluster->GalaxyClusterRelation->fetchRelations($this->Auth->user(), array(
|
||||
'conditions' => array(
|
||||
'GalaxyClusterRelation.galaxy_cluster_uuid' => $cluster['GalaxyCluster']['uuid']
|
||||
),
|
||||
'contain' => array('SharingGroup', 'TargetCluster', 'GalaxyClusterRelationTag' => array('Tag'))
|
||||
));
|
||||
$relations = $cluster['GalaxyCluster']['GalaxyClusterRelation'];
|
||||
$this->set('passedArgs', json_encode([]));
|
||||
$this->set('relations', $relations);
|
||||
$this->set('tree', $tree);
|
||||
$this->loadModel('Attribute');
|
||||
|
|
|
@ -16,14 +16,102 @@ class GalaxyElementsController extends AppController
|
|||
|
||||
public function index($clusterId)
|
||||
{
|
||||
$filters = $this->IndexFilter->harvestParameters(array('context', 'searchall'));
|
||||
$aclConditions = $this->GalaxyElement->buildClusterConditions($this->Auth->user(), $clusterId);
|
||||
$this->paginate['conditions'] = [$aclConditions];
|
||||
if (empty($filters['context'])) {
|
||||
$filters['context'] = 'all';
|
||||
}
|
||||
$searchConditions = array();
|
||||
if (empty($filters['searchall'])) {
|
||||
$filters['searchall'] = '';
|
||||
}
|
||||
if (strlen($filters['searchall']) > 0) {
|
||||
$searchall = '%' . strtolower($filters['searchall']) . '%';
|
||||
$searchConditions = array(
|
||||
'OR' => array(
|
||||
'LOWER(GalaxyElement.key) LIKE' => $searchall,
|
||||
'LOWER(GalaxyElement.value) LIKE' => $searchall,
|
||||
),
|
||||
);
|
||||
}
|
||||
$this->paginate['conditions'] = ['AND' => [$aclConditions, $searchConditions]];
|
||||
$this->paginate['contain'] = ['GalaxyCluster' => ['fields' => ['id', 'distribution', 'org_id']]];
|
||||
$clusters = $this->paginate();
|
||||
$this->set('list', $clusters);
|
||||
$elements = $this->paginate();
|
||||
$this->set('elements', $elements);
|
||||
$this->set('clusterId', $clusterId);
|
||||
$this->set('context', $filters['context']);
|
||||
$this->set('passedArgs', json_encode([
|
||||
'context' => $filters['context'],
|
||||
'searchall' => isset($filters['searchall']) ? $filters['searchall'] : ''
|
||||
]));
|
||||
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $clusterId, array('edit', 'delete'), false, false);
|
||||
$canModify = !empty($cluster['authorized']);
|
||||
$canModify = true;
|
||||
$this->set('canModify', $canModify);
|
||||
if ($filters['context'] == 'JSONView') {
|
||||
$expanded = $this->GalaxyElement->getExpandedJSONFromElements($elements);
|
||||
$this->set('JSONElements', $expanded);
|
||||
}
|
||||
if ($this->request->is('ajax')) {
|
||||
$this->layout = 'ajax';
|
||||
$this->render('ajax/index');
|
||||
}
|
||||
}
|
||||
|
||||
public function delete($elementId)
|
||||
{
|
||||
$element = $this->GalaxyElement->find('first', array('conditions' => array('GalaxyElement.id' => $elementId)));
|
||||
if (empty($element)) {
|
||||
throw new Exception(__('Element not found'));
|
||||
}
|
||||
$this->set('element', $element);
|
||||
$clusterId = $element['GalaxyElement']['galaxy_cluster_id'];
|
||||
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $clusterId, array('edit'), true, false);
|
||||
if ($this->request->is('post')) {
|
||||
$deleteResult = $this->GalaxyElement->delete($elementId);
|
||||
if ($deleteResult) {
|
||||
$this->GalaxyElement->GalaxyCluster->editCluster($this->Auth->user(), $cluster, [], false);
|
||||
$message = __('Galaxy element %s deleted', $elementId);
|
||||
$this->Flash->success($message);
|
||||
} else {
|
||||
$message = __('Could not delete galaxy element');
|
||||
$this->Flash->error($message);
|
||||
}
|
||||
$this->redirect($this->referer());
|
||||
} else {
|
||||
if (!$this->request->is('ajax')) {
|
||||
throw new MethodNotAllowedException(__('This function can only be reached via AJAX.'));
|
||||
} else {
|
||||
$this->layout = 'ajax';
|
||||
$this->set('elementId', $elementId);
|
||||
$this->render('ajax/delete');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function flattenJson($clusterId)
|
||||
{
|
||||
$cluster = $this->GalaxyElement->GalaxyCluster->fetchIfAuthorized($this->Auth->user(), $clusterId, array('edit'), true, false);
|
||||
if ($this->request->is('post') || $this->request->is('put')) {
|
||||
$json = $this->GalaxyElement->jsonDecode($this->request->data['GalaxyElement']['jsonData']);
|
||||
$flattened = Hash::flatten($json);
|
||||
$newElements = [];
|
||||
foreach ($flattened as $k => $v) {
|
||||
$newElements[] = ['key' => $k, 'value' => $v];
|
||||
}
|
||||
$cluster['GalaxyCluster']['GalaxyElement'] = $newElements;
|
||||
$errors = $this->GalaxyElement->GalaxyCluster->editCluster($this->Auth->user(), $cluster, [], false);
|
||||
if (empty($errors)) {
|
||||
return $this->RestResponse->saveSuccessResponse('GalaxyElement', 'flattenJson', $clusterId, false);
|
||||
} else {
|
||||
$message = implode(', ', $errors);
|
||||
return $this->RestResponse->saveFailResponse('GalaxyElement', 'flattenJson', $clusterId, $message, false);
|
||||
}
|
||||
}
|
||||
$this->set('clusterId', $clusterId);
|
||||
if ($this->request->is('ajax')) {
|
||||
$this->layout = 'ajax';
|
||||
$this->render('ajax/flattenJson');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,7 +108,7 @@ class LogsController extends AppController
|
|||
}
|
||||
|
||||
// Shows a minimalistic history for the currently selected event
|
||||
public function event_index($id)
|
||||
public function event_index($id, $org = null)
|
||||
{
|
||||
$this->loadModel('Event');
|
||||
$event = $this->Event->fetchEvent($this->Auth->user(), array(
|
||||
|
@ -183,6 +183,10 @@ class LogsController extends AppController
|
|||
);
|
||||
}
|
||||
|
||||
if ($org) {
|
||||
$conditions['org'] = $org;
|
||||
}
|
||||
|
||||
$this->paginate['fields'] = array('title', 'created', 'model', 'model_id', 'action', 'change', 'org', 'email');
|
||||
$this->paginate['conditions'] = $conditions;
|
||||
|
||||
|
@ -194,7 +198,7 @@ class LogsController extends AppController
|
|||
'fields' => array('User.email')
|
||||
));
|
||||
foreach ($list as $k => $item) {
|
||||
if (!in_array($item['Log']['email'], $orgEmails)) {
|
||||
if (!in_array($item['Log']['email'], $orgEmails, true)) {
|
||||
$list[$k]['Log']['email'] = '';
|
||||
}
|
||||
}
|
||||
|
@ -367,6 +371,8 @@ class LogsController extends AppController
|
|||
'EventTag',
|
||||
'Feed',
|
||||
'DecayingModel',
|
||||
'EventGraph',
|
||||
'EventReport',
|
||||
'MispObject',
|
||||
'Organisation',
|
||||
'Post',
|
||||
|
|
|
@ -41,7 +41,7 @@ class ObjectReferencesController extends AppController
|
|||
'recursive' => -1,
|
||||
'contain' => array(
|
||||
'Event' => array(
|
||||
'fields' => array('Event.id', 'Event.orgc_id', 'Event.user_id')
|
||||
'fields' => array('Event.id', 'Event.orgc_id', 'Event.user_id', 'Event.extends_uuid')
|
||||
)
|
||||
)
|
||||
));
|
||||
|
@ -54,7 +54,7 @@ class ObjectReferencesController extends AppController
|
|||
if (!isset($this->request->data['ObjectReference'])) {
|
||||
$this->request->data['ObjectReference'] = $this->request->data;
|
||||
}
|
||||
list($referenced_id, $referenced_uuid, $referenced_type) = $this->ObjectReference->getReferencedInfo($this->request->data['ObjectReference']['referenced_uuid'], $object);
|
||||
list($referenced_id, $referenced_uuid, $referenced_type) = $this->ObjectReference->getReferencedInfo($this->request->data['ObjectReference']['referenced_uuid'], $object, true, $this->Auth->user());
|
||||
$relationship_type = empty($this->request->data['ObjectReference']['relationship_type']) ? '' : $this->request->data['ObjectReference']['relationship_type'];
|
||||
if (!empty($this->request->data['ObjectReference']['relationship_type_select']) && $this->request->data['ObjectReference']['relationship_type_select'] !== 'custom') {
|
||||
$relationship_type = $this->request->data['ObjectReference']['relationship_type_select'];
|
||||
|
@ -66,6 +66,7 @@ class ObjectReferencesController extends AppController
|
|||
'comment' => !empty($this->request->data['ObjectReference']['comment']) ? $this->request->data['ObjectReference']['comment'] : '',
|
||||
'event_id' => $object['Event']['id'],
|
||||
'object_uuid' => $object['Object']['uuid'],
|
||||
'source_uuid' => $object['Object']['uuid'],
|
||||
'object_id' => $objectId,
|
||||
'referenced_type' => $referenced_type,
|
||||
'uuid' => CakeText::uuid()
|
||||
|
@ -96,8 +97,16 @@ class ObjectReferencesController extends AppController
|
|||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->describe('ObjectReferences', 'add', false, $this->response->type());
|
||||
} else {
|
||||
$event = $this->ObjectReference->Object->Event->find('first', array(
|
||||
'conditions' => array('Event.id' => $object['Event']['id']),
|
||||
$events = $this->ObjectReference->Object->Event->find('all', array(
|
||||
'conditions' => array(
|
||||
'OR' => array(
|
||||
'Event.id' => $object['Event']['id'],
|
||||
'AND' => array(
|
||||
'Event.uuid' => $object['Event']['extends_uuid'],
|
||||
$this->ObjectReference->Object->Event->createEventConditions($this->Auth->user())
|
||||
)
|
||||
),
|
||||
),
|
||||
'recursive' => -1,
|
||||
'fields' => array('Event.id'),
|
||||
'contain' => array(
|
||||
|
@ -115,6 +124,13 @@ class ObjectReferencesController extends AppController
|
|||
)
|
||||
)
|
||||
));
|
||||
if (!empty($events)) {
|
||||
$event = $events[0];
|
||||
}
|
||||
for ($i=1; $i < count($events); $i++) {
|
||||
$event['Attribute'] = array_merge($event['Attribute'], $events[$i]['Attribute']);
|
||||
$event['Object'] = array_merge($event['Object'], $events[$i]['Object']);
|
||||
}
|
||||
$toRearrange = array('Attribute', 'Object');
|
||||
foreach ($toRearrange as $d) {
|
||||
if (!empty($event[$d])) {
|
||||
|
@ -200,5 +216,30 @@ class ObjectReferencesController extends AppController
|
|||
|
||||
public function view($id)
|
||||
{
|
||||
if (Validation::uuid($id)) {
|
||||
$temp = $this->ObjectReference->find('first', array(
|
||||
'recursive' => -1,
|
||||
'fields' => array('ObjectReference.id'),
|
||||
'conditions' => array('ObjectReference.uuid' => $id)
|
||||
));
|
||||
if (empty($temp)) {
|
||||
throw new NotFoundException('Invalid object reference');
|
||||
}
|
||||
$id = $temp['ObjectReference']['id'];
|
||||
} elseif (!is_numeric($id)) {
|
||||
throw new NotFoundException(__('Invalid object reference'));
|
||||
}
|
||||
$objectReference = $this->ObjectReference->find('first', array(
|
||||
'conditions' => array('ObjectReference.id' => $id),
|
||||
'recursive' => -1,
|
||||
));
|
||||
if (empty($objectReference)) {
|
||||
throw new NotFoundException(__('Invalid object reference.'));
|
||||
}
|
||||
$event = $this->ObjectReference->Object->Event->fetchSimpleEvent($this->Auth->user(), $objectReference['ObjectReference']['event_id'], ['contain' => ['Orgc']]);
|
||||
if (!$event) {
|
||||
throw new NotFoundException(__('Invalid event'));
|
||||
}
|
||||
return $this->RestResponse->viewData($objectReference, 'application/json');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -298,4 +298,13 @@ class ObjectTemplatesController extends AppController
|
|||
$this->layout = 'ajax';
|
||||
$this->render('ajax/getToggleField');
|
||||
}
|
||||
|
||||
public function getRaw($uuidOrName)
|
||||
{
|
||||
$template = $this->ObjectTemplate->getRawFromDisk($uuidOrName);
|
||||
if (empty($template)) {
|
||||
throw new NotFoundException(__('Template not found'));
|
||||
}
|
||||
return $this->RestResponse->viewData($template, $this->response->type());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -376,13 +376,14 @@ class ObjectsController extends AppController
|
|||
'ObjectTemplateElement'
|
||||
)
|
||||
));
|
||||
if (empty($template) && !$this->_isRest()) {
|
||||
$this->Flash->error('Object cannot be edited, no valid template found.');
|
||||
if (empty($template) && !$this->_isRest() && !$update_template_available) {
|
||||
$this->Flash->error('Object cannot be edited, no valid template found. ', ['params' => ['url' => sprintf('/objects/edit/%s/1/0', $id), 'urlName' => __('Force update anyway')]]);
|
||||
$this->redirect(array('controller' => 'events', 'action' => 'view', $object['Object']['event_id']));
|
||||
}
|
||||
$templateData = $this->MispObject->resolveUpdatedTemplate($template, $object, $update_template_available);
|
||||
$this->set('updateable_attribute', $templateData['updateable_attribute']);
|
||||
$this->set('not_updateable_attribute', $templateData['not_updateable_attribute']);
|
||||
$this->set('original_template_unkown', $templateData['original_template_unkown']);
|
||||
if (!empty($this->Session->read('object_being_created')) && !empty($this->params['named']['cur_object_tmp_uuid'])) {
|
||||
$revisedObjectData = $this->Session->read('object_being_created');
|
||||
if ($this->params['named']['cur_object_tmp_uuid'] == $revisedObjectData['cur_object_tmp_uuid']) { // ensure that the passed session data is for the correct object
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
App::uses('AppController', 'Controller');
|
||||
App::uses('Xml', 'Utility');
|
||||
App::uses('AttachmentTool', 'Tools');
|
||||
App::uses('SecurityAudit', 'Tools');
|
||||
|
||||
/**
|
||||
* @property Server $Server
|
||||
|
@ -34,8 +35,11 @@ class ServersController extends AppController
|
|||
|
||||
public function beforeFilter()
|
||||
{
|
||||
$this->Auth->allow(['cspReport']); // cspReport must work without authentication
|
||||
|
||||
parent::beforeFilter();
|
||||
$this->Security->unlockedActions[] = 'getApiInfo';
|
||||
$this->Security->unlockedActions[] = 'cspReport';
|
||||
// permit reuse of CSRF tokens on some pages.
|
||||
switch ($this->request->params['action']) {
|
||||
case 'push':
|
||||
|
@ -120,8 +124,7 @@ class ServersController extends AppController
|
|||
}
|
||||
|
||||
$this->loadModel('Event');
|
||||
$threat_levels = $this->Event->ThreatLevel->find('list', ['fields' => ['id', 'name']]);
|
||||
$this->set('threatLevels', $threat_levels);
|
||||
$this->set('threatLevels', $this->Event->ThreatLevel->listThreatLevels());
|
||||
App::uses('CustomPaginationTool', 'Tools');
|
||||
$customPagination = new CustomPaginationTool();
|
||||
$params = $customPagination->createPaginationRules($events, $this->passedArgs, $this->alias);
|
||||
|
@ -178,6 +181,7 @@ class ServersController extends AppController
|
|||
|
||||
$this->loadModel('Event');
|
||||
$params = $this->Event->rearrangeEventForView($event, $this->passedArgs, $all);
|
||||
$this->__removeGalaxyClusterTags($event);
|
||||
$this->params->params['paging'] = array('Server' => $params);
|
||||
$this->set('event', $event);
|
||||
$this->set('server', $server);
|
||||
|
@ -198,11 +202,30 @@ class ServersController extends AppController
|
|||
$this->set($alias, $currentModel->{$variable});
|
||||
}
|
||||
}
|
||||
$threat_levels = $this->Event->ThreatLevel->find('list', ['fields' => ['id', 'name']]);
|
||||
$this->set('threatLevels', $threat_levels);
|
||||
$this->set('threatLevels', $this->Event->ThreatLevel->listThreatLevels());
|
||||
$this->set('title_for_layout', __('Remote event preview'));
|
||||
}
|
||||
|
||||
private function __removeGalaxyClusterTags(array &$event)
|
||||
{
|
||||
$galaxyTagIds = [];
|
||||
foreach ($event['Galaxy'] as $galaxy) {
|
||||
foreach ($galaxy['GalaxyCluster'] as $galaxyCluster) {
|
||||
$galaxyTagIds[$galaxyCluster['tag_id']] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($galaxyTagIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($event['Tag'] as $k => $eventTag) {
|
||||
if (isset($galaxyTagIds[$eventTag['id']])) {
|
||||
unset($event['Tag'][$k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function compareServers()
|
||||
{
|
||||
list($servers, $overlap) = $this->Server->serverEventsOverlap();
|
||||
|
@ -938,253 +961,254 @@ class ServersController extends AppController
|
|||
|
||||
public function serverSettings($tab=false)
|
||||
{
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
throw new MethodNotAllowedException();
|
||||
if (!$this->request->is('get')) {
|
||||
throw new MethodNotAllowedException('Just GET method is allowed.');
|
||||
}
|
||||
if ($this->request->is('Get')) {
|
||||
$tabs = array(
|
||||
'MISP' => array('count' => 0, 'errors' => 0, 'severity' => 5),
|
||||
'Encryption' => array('count' => 0, 'errors' => 0, 'severity' => 5),
|
||||
'Proxy' => array('count' => 0, 'errors' => 0, 'severity' => 5),
|
||||
'Security' => array('count' => 0, 'errors' => 0, 'severity' => 5),
|
||||
'Plugin' => array('count' => 0, 'errors' => 0, 'severity' => 5)
|
||||
);
|
||||
$writeableErrors = array(0 => __('OK'), 1 => __('not found'), 2 => __('is not writeable'));
|
||||
$readableErrors = array(0 => __('OK'), 1 => __('not readable'));
|
||||
$gpgErrors = array(0 => __('OK'), 1 => __('FAIL: settings not set'), 2 => __('FAIL: Failed to load GnuPG'), 3 => __('FAIL: Issues with the key/passphrase'), 4 => __('FAIL: sign failed'));
|
||||
$proxyErrors = array(0 => __('OK'), 1 => __('not configured (so not tested)'), 2 => __('Getting URL via proxy failed'));
|
||||
$zmqErrors = array(0 => __('OK'), 1 => __('not enabled (so not tested)'), 2 => __('Python ZeroMQ library not installed correctly.'), 3 => __('ZeroMQ script not running.'));
|
||||
$stixOperational = array(0 => __('Some of the libraries related to STIX are not installed. Make sure that all libraries listed below are correctly installed.'), 1 => __('OK'));
|
||||
$stixVersion = array(0 => __('Incorrect STIX version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$stix2Version = array(0 => __('Incorrect STIX2 version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$cyboxVersion = array(0 => __('Incorrect CyBox version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$mixboxVersion = array(0 => __('Incorrect mixbox version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$maecVersion = array(0 => __('Incorrect maec version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$pymispVersion = array(0 => __('Incorrect PyMISP version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$sessionErrors = array(0 => __('OK'), 1 => __('High'), 2 => __('Alternative setting used'), 3 => __('Test failed'));
|
||||
$moduleErrors = array(0 => __('OK'), 1 => __('System not enabled'), 2 => __('No modules found'));
|
||||
$tabs = array(
|
||||
'MISP' => array('count' => 0, 'errors' => 0, 'severity' => 5),
|
||||
'Encryption' => array('count' => 0, 'errors' => 0, 'severity' => 5),
|
||||
'Proxy' => array('count' => 0, 'errors' => 0, 'severity' => 5),
|
||||
'Security' => array('count' => 0, 'errors' => 0, 'severity' => 5),
|
||||
'Plugin' => array('count' => 0, 'errors' => 0, 'severity' => 5)
|
||||
);
|
||||
$writeableErrors = array(0 => __('OK'), 1 => __('not found'), 2 => __('is not writeable'));
|
||||
$readableErrors = array(0 => __('OK'), 1 => __('not readable'));
|
||||
$gpgErrors = array(0 => __('OK'), 1 => __('FAIL: settings not set'), 2 => __('FAIL: Failed to load GnuPG'), 3 => __('FAIL: Issues with the key/passphrase'), 4 => __('FAIL: sign failed'));
|
||||
$proxyErrors = array(0 => __('OK'), 1 => __('not configured (so not tested)'), 2 => __('Getting URL via proxy failed'));
|
||||
$zmqErrors = array(0 => __('OK'), 1 => __('not enabled (so not tested)'), 2 => __('Python ZeroMQ library not installed correctly.'), 3 => __('ZeroMQ script not running.'));
|
||||
$stixOperational = array(0 => __('Some of the libraries related to STIX are not installed. Make sure that all libraries listed below are correctly installed.'), 1 => __('OK'));
|
||||
$stixVersion = array(0 => __('Incorrect STIX version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$stix2Version = array(0 => __('Incorrect STIX2 version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$cyboxVersion = array(0 => __('Incorrect CyBox version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$mixboxVersion = array(0 => __('Incorrect mixbox version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$maecVersion = array(0 => __('Incorrect maec version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$pymispVersion = array(0 => __('Incorrect PyMISP version installed, found $current, expecting $expected'), 1 => __('OK'));
|
||||
$sessionErrors = array(0 => __('OK'), 1 => __('High'), 2 => __('Alternative setting used'), 3 => __('Test failed'));
|
||||
$moduleErrors = array(0 => __('OK'), 1 => __('System not enabled'), 2 => __('No modules found'));
|
||||
|
||||
$finalSettings = $this->Server->serverSettingsRead();
|
||||
$issues = array(
|
||||
'errors' => array(
|
||||
0 => array(
|
||||
'value' => 0,
|
||||
'description' => __('MISP will not operate correctly or will be unsecure until these issues are resolved.')
|
||||
),
|
||||
1 => array(
|
||||
'value' => 0,
|
||||
'description' => __('Some of the features of MISP cannot be utilised until these issues are resolved.')
|
||||
),
|
||||
2 => array(
|
||||
'value' => 0,
|
||||
'description' => __('There are some optional tweaks that could be done to improve the looks of your MISP instance.')
|
||||
),
|
||||
$finalSettings = $this->Server->serverSettingsRead();
|
||||
$issues = array(
|
||||
'errors' => array(
|
||||
0 => array(
|
||||
'value' => 0,
|
||||
'description' => __('MISP will not operate correctly or will be unsecure until these issues are resolved.')
|
||||
),
|
||||
'deprecated' => array(),
|
||||
'overallHealth' => 3,
|
||||
);
|
||||
$dumpResults = array();
|
||||
$tempArray = array();
|
||||
foreach ($finalSettings as $k => $result) {
|
||||
if ($result['level'] == 3) {
|
||||
$issues['deprecated']++;
|
||||
1 => array(
|
||||
'value' => 0,
|
||||
'description' => __('Some of the features of MISP cannot be utilised until these issues are resolved.')
|
||||
),
|
||||
2 => array(
|
||||
'value' => 0,
|
||||
'description' => __('There are some optional tweaks that could be done to improve the looks of your MISP instance.')
|
||||
),
|
||||
),
|
||||
'deprecated' => array(),
|
||||
'overallHealth' => 3,
|
||||
);
|
||||
$dumpResults = array();
|
||||
$tempArray = array();
|
||||
foreach ($finalSettings as $k => $result) {
|
||||
if ($result['level'] == 3) {
|
||||
$issues['deprecated']++;
|
||||
}
|
||||
$tabs[$result['tab']]['count']++;
|
||||
if (isset($result['error']) && $result['level'] < 3) {
|
||||
$issues['errors'][$result['level']]['value']++;
|
||||
if ($result['level'] < $issues['overallHealth']) {
|
||||
$issues['overallHealth'] = $result['level'];
|
||||
}
|
||||
$tabs[$result['tab']]['count']++;
|
||||
if (isset($result['error']) && $result['level'] < 3) {
|
||||
$issues['errors'][$result['level']]['value']++;
|
||||
if ($result['level'] < $issues['overallHealth']) {
|
||||
$issues['overallHealth'] = $result['level'];
|
||||
}
|
||||
$tabs[$result['tab']]['errors']++;
|
||||
if ($result['level'] < $tabs[$result['tab']]['severity']) {
|
||||
$tabs[$result['tab']]['severity'] = $result['level'];
|
||||
}
|
||||
}
|
||||
if (isset($result['optionsSource']) && is_callable($result['optionsSource'])) {
|
||||
$result['options'] = $result['optionsSource']();
|
||||
}
|
||||
$dumpResults[] = $result;
|
||||
if ($result['tab'] == $tab) {
|
||||
if (isset($result['subGroup'])) {
|
||||
$tempArray[$result['subGroup']][] = $result;
|
||||
} else {
|
||||
$tempArray['general'][] = $result;
|
||||
}
|
||||
$tabs[$result['tab']]['errors']++;
|
||||
if ($result['level'] < $tabs[$result['tab']]['severity']) {
|
||||
$tabs[$result['tab']]['severity'] = $result['level'];
|
||||
}
|
||||
}
|
||||
$finalSettings = $tempArray;
|
||||
// Diagnostics portion
|
||||
$diagnostic_errors = 0;
|
||||
App::uses('File', 'Utility');
|
||||
App::uses('Folder', 'Utility');
|
||||
if ($tab === 'files') {
|
||||
$files = $this->__manageFiles();
|
||||
$this->set('files', $files);
|
||||
if (isset($result['optionsSource']) && is_callable($result['optionsSource'])) {
|
||||
$result['options'] = $result['optionsSource']();
|
||||
}
|
||||
// Only run this check on the diagnostics tab
|
||||
if ($tab === 'diagnostics' || $tab === 'download' || $this->_isRest()) {
|
||||
$php_ini = php_ini_loaded_file();
|
||||
$this->set('php_ini', $php_ini);
|
||||
|
||||
$attachmentTool = new AttachmentTool();
|
||||
try {
|
||||
$advanced_attachments = $attachmentTool->checkAdvancedExtractionStatus($this->Server->getPythonVersion());
|
||||
} catch (Exception $e) {
|
||||
$this->log($e->getMessage(), LOG_NOTICE);
|
||||
$advanced_attachments = false;
|
||||
$dumpResults[] = $result;
|
||||
if ($result['tab'] == $tab) {
|
||||
if (isset($result['subGroup'])) {
|
||||
$tempArray[$result['subGroup']][] = $result;
|
||||
} else {
|
||||
$tempArray['general'][] = $result;
|
||||
}
|
||||
|
||||
$this->set('advanced_attachments', $advanced_attachments);
|
||||
// check if the current version of MISP is outdated or not
|
||||
$version = $this->__checkVersion();
|
||||
$this->set('version', $version);
|
||||
$gitStatus = $this->Server->getCurrentGitStatus();
|
||||
$this->set('branch', $gitStatus['branch']);
|
||||
$this->set('commit', $gitStatus['commit']);
|
||||
$this->set('latestCommit', $gitStatus['latestCommit']);
|
||||
$phpSettings = array(
|
||||
'max_execution_time' => array(
|
||||
'explanation' => 'The maximum duration that a script can run (does not affect the background workers). A too low number will break long running scripts like comprehensive API exports',
|
||||
'recommended' => 300,
|
||||
'unit' => false
|
||||
),
|
||||
'memory_limit' => array(
|
||||
'explanation' => 'The maximum memory that PHP can consume. It is recommended to raise this number since certain exports can generate a fair bit of memory usage',
|
||||
'recommended' => 2048,
|
||||
'unit' => 'M'
|
||||
),
|
||||
'upload_max_filesize' => array(
|
||||
'explanation' => 'The maximum size that an uploaded file can be. It is recommended to raise this number to allow for the upload of larger samples',
|
||||
'recommended' => 50,
|
||||
'unit' => 'M'
|
||||
),
|
||||
'post_max_size' => array(
|
||||
'explanation' => 'The maximum size of a POSTed message, this has to be at least the same size as the upload_max_filesize setting',
|
||||
'recommended' => 50,
|
||||
'unit' => 'M'
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($phpSettings as $setting => $settingArray) {
|
||||
$phpSettings[$setting]['value'] = ini_get($setting);
|
||||
if ($settingArray['unit']) {
|
||||
$phpSettings[$setting]['value'] = intval(rtrim($phpSettings[$setting]['value'], $phpSettings[$setting]['unit']));
|
||||
} else {
|
||||
$phpSettings[$setting]['value'] = intval($phpSettings[$setting]['value']);
|
||||
}
|
||||
}
|
||||
$this->set('phpSettings', $phpSettings);
|
||||
|
||||
if ($version && (!$version['upToDate'] || $version['upToDate'] == 'older')) {
|
||||
$diagnostic_errors++;
|
||||
}
|
||||
|
||||
// check if the STIX and Cybox libraries are working and the correct version using the test script stixtest.py
|
||||
$stix = $this->Server->stixDiagnostics($diagnostic_errors, $stixVersion, $cyboxVersion, $mixboxVersion, $maecVersion, $stix2Version, $pymispVersion);
|
||||
|
||||
$yaraStatus = $this->Server->yaraDiagnostics($diagnostic_errors);
|
||||
|
||||
// if GnuPG is set up in the settings, try to encrypt a test message
|
||||
$gpgStatus = $this->Server->gpgDiagnostics($diagnostic_errors);
|
||||
|
||||
// if the message queue pub/sub is enabled, check whether the extension works
|
||||
$zmqStatus = $this->Server->zmqDiagnostics($diagnostic_errors);
|
||||
|
||||
// if Proxy is set up in the settings, try to connect to a test URL
|
||||
$proxyStatus = $this->Server->proxyDiagnostics($diagnostic_errors);
|
||||
|
||||
// get the DB diagnostics
|
||||
$dbDiagnostics = $this->Server->dbSpaceUsage();
|
||||
$dbSchemaDiagnostics = $this->Server->dbSchemaDiagnostic();
|
||||
|
||||
$redisInfo = $this->Server->redisInfo();
|
||||
|
||||
$moduleTypes = array('Enrichment', 'Import', 'Export', 'Cortex');
|
||||
foreach ($moduleTypes as $type) {
|
||||
$moduleStatus[$type] = $this->Server->moduleDiagnostics($diagnostic_errors, $type);
|
||||
}
|
||||
|
||||
// check the size of the session table
|
||||
$sessionCount = 0;
|
||||
$sessionStatus = $this->Server->sessionDiagnostics($diagnostic_errors, $sessionCount);
|
||||
$this->set('sessionCount', $sessionCount);
|
||||
|
||||
$this->loadModel('AttachmentScan');
|
||||
try {
|
||||
$attachmentScan = ['status' => true, 'software' => $this->AttachmentScan->diagnostic()];
|
||||
} catch (Exception $e) {
|
||||
$attachmentScan = ['status' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
|
||||
$view = compact('gpgStatus', 'sessionErrors', 'proxyStatus', 'sessionStatus', 'zmqStatus', 'stixVersion', 'cyboxVersion', 'mixboxVersion', 'maecVersion', 'stix2Version', 'pymispVersion', 'moduleStatus', 'yaraStatus', 'gpgErrors', 'proxyErrors', 'zmqErrors', 'stixOperational', 'stix', 'moduleErrors', 'moduleTypes', 'dbDiagnostics', 'dbSchemaDiagnostics', 'redisInfo', 'attachmentScan');
|
||||
} else {
|
||||
$view = [];
|
||||
}
|
||||
// check whether the files are writeable
|
||||
$writeableDirs = $this->Server->writeableDirsDiagnostics($diagnostic_errors);
|
||||
$writeableFiles = $this->Server->writeableFilesDiagnostics($diagnostic_errors);
|
||||
$readableFiles = $this->Server->readableFilesDiagnostics($diagnostic_errors);
|
||||
$extensions = $this->Server->extensionDiagnostics();
|
||||
|
||||
// check if the encoding is not set to utf8
|
||||
$dbEncodingStatus = $this->Server->databaseEncodingDiagnostics($diagnostic_errors);
|
||||
|
||||
$view = array_merge($view, compact('diagnostic_errors', 'tabs', 'tab', 'issues', 'finalSettings', 'writeableErrors', 'readableErrors', 'writeableDirs', 'writeableFiles', 'readableFiles', 'extensions', 'dbEncodingStatus'));
|
||||
$this->set($view);
|
||||
|
||||
$workerIssueCount = 4;
|
||||
$worker_array = array();
|
||||
if (Configure::read('MISP.background_jobs')) {
|
||||
$workerIssueCount = 0;
|
||||
$worker_array = $this->Server->workerDiagnostics($workerIssueCount);
|
||||
}
|
||||
$this->set('worker_array', $worker_array);
|
||||
if ($tab == 'download' || $this->_isRest()) {
|
||||
foreach ($dumpResults as $key => $dr) {
|
||||
unset($dumpResults[$key]['description']);
|
||||
}
|
||||
$dump = array(
|
||||
'version' => $version,
|
||||
'phpSettings' => $phpSettings,
|
||||
'gpgStatus' => $gpgErrors[$gpgStatus['status']],
|
||||
'proxyStatus' => $proxyErrors[$proxyStatus],
|
||||
'zmqStatus' => $zmqStatus,
|
||||
'stix' => $stix,
|
||||
'moduleStatus' => $moduleStatus,
|
||||
'writeableDirs' => $writeableDirs,
|
||||
'writeableFiles' => $writeableFiles,
|
||||
'readableFiles' => $readableFiles,
|
||||
'dbDiagnostics' => $dbDiagnostics,
|
||||
'dbSchemaDiagnostics' => $dbSchemaDiagnostics,
|
||||
'redisInfo' => $redisInfo,
|
||||
'finalSettings' => $dumpResults,
|
||||
'extensions' => $extensions,
|
||||
'workers' => $worker_array
|
||||
);
|
||||
foreach ($dump['finalSettings'] as $k => $v) {
|
||||
if (!empty($v['redacted'])) {
|
||||
$dump['finalSettings'][$k]['value'] = '*****';
|
||||
}
|
||||
}
|
||||
$this->response->body(json_encode($dump, JSON_PRETTY_PRINT));
|
||||
$this->response->type('json');
|
||||
$this->response->download('MISP.report.json');
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
$priorities = array(0 => 'Critical', 1 => 'Recommended', 2 => 'Optional', 3 => 'Deprecated');
|
||||
$this->set('priorities', $priorities);
|
||||
$this->set('workerIssueCount', $workerIssueCount);
|
||||
$priorityErrorColours = array(0 => 'red', 1 => 'yellow', 2 => 'green');
|
||||
$this->set('priorityErrorColours', $priorityErrorColours);
|
||||
$this->set('phpversion', phpversion());
|
||||
$this->set('phpmin', $this->phpmin);
|
||||
$this->set('phprec', $this->phprec);
|
||||
$this->set('phptoonew', $this->phptoonew);
|
||||
$this->set('pythonmin', $this->pythonmin);
|
||||
$this->set('pythonrec', $this->pythonrec);
|
||||
$this->set('pymisp', $this->pymisp);
|
||||
}
|
||||
$finalSettings = $tempArray;
|
||||
// Diagnostics portion
|
||||
$diagnostic_errors = 0;
|
||||
App::uses('File', 'Utility');
|
||||
App::uses('Folder', 'Utility');
|
||||
if ($tab === 'files') {
|
||||
$files = $this->Server->grabFiles();
|
||||
$this->set('files', $files);
|
||||
}
|
||||
// Only run this check on the diagnostics tab
|
||||
if ($tab === 'diagnostics' || $tab === 'download' || $this->_isRest()) {
|
||||
$php_ini = php_ini_loaded_file();
|
||||
$this->set('php_ini', $php_ini);
|
||||
|
||||
$attachmentTool = new AttachmentTool();
|
||||
try {
|
||||
$advanced_attachments = $attachmentTool->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();
|
||||
$this->set('version', $version);
|
||||
$gitStatus = $this->Server->getCurrentGitStatus();
|
||||
$this->set('branch', $gitStatus['branch']);
|
||||
$this->set('commit', $gitStatus['commit']);
|
||||
$this->set('latestCommit', $gitStatus['latestCommit']);
|
||||
|
||||
$phpSettings = array(
|
||||
'max_execution_time' => array(
|
||||
'explanation' => 'The maximum duration that a script can run (does not affect the background workers). A too low number will break long running scripts like comprehensive API exports',
|
||||
'recommended' => 300,
|
||||
'unit' => 'seconds',
|
||||
),
|
||||
'memory_limit' => array(
|
||||
'explanation' => 'The maximum memory that PHP can consume. It is recommended to raise this number since certain exports can generate a fair bit of memory usage',
|
||||
'recommended' => 2048,
|
||||
'unit' => 'MB'
|
||||
),
|
||||
'upload_max_filesize' => array(
|
||||
'explanation' => 'The maximum size that an uploaded file can be. It is recommended to raise this number to allow for the upload of larger samples',
|
||||
'recommended' => 50,
|
||||
'unit' => 'MB'
|
||||
),
|
||||
'post_max_size' => array(
|
||||
'explanation' => 'The maximum size of a POSTed message, this has to be at least the same size as the upload_max_filesize setting',
|
||||
'recommended' => 50,
|
||||
'unit' => 'MB'
|
||||
)
|
||||
);
|
||||
|
||||
foreach ($phpSettings as $setting => $settingArray) {
|
||||
$phpSettings[$setting]['value'] = $this->Server->getIniSetting($setting);
|
||||
if ($phpSettings[$setting]['value'] && $settingArray['unit'] && $settingArray['unit'] === 'MB') {
|
||||
// convert basic unit to M
|
||||
$phpSettings[$setting]['value'] = (int) floor($phpSettings[$setting]['value'] / 1024 / 1024);
|
||||
}
|
||||
}
|
||||
$this->set('phpSettings', $phpSettings);
|
||||
|
||||
if ($version && (!$version['upToDate'] || $version['upToDate'] == 'older')) {
|
||||
$diagnostic_errors++;
|
||||
}
|
||||
|
||||
// check if the STIX and Cybox libraries are working and the correct version using the test script stixtest.py
|
||||
$stix = $this->Server->stixDiagnostics($diagnostic_errors, $stixVersion, $cyboxVersion, $mixboxVersion, $maecVersion, $stix2Version, $pymispVersion);
|
||||
|
||||
$yaraStatus = $this->Server->yaraDiagnostics($diagnostic_errors);
|
||||
|
||||
// if GnuPG is set up in the settings, try to encrypt a test message
|
||||
$gpgStatus = $this->Server->gpgDiagnostics($diagnostic_errors);
|
||||
|
||||
// if the message queue pub/sub is enabled, check whether the extension works
|
||||
$zmqStatus = $this->Server->zmqDiagnostics($diagnostic_errors);
|
||||
|
||||
// if Proxy is set up in the settings, try to connect to a test URL
|
||||
$proxyStatus = $this->Server->proxyDiagnostics($diagnostic_errors);
|
||||
|
||||
// get the DB diagnostics
|
||||
$dbDiagnostics = $this->Server->dbSpaceUsage();
|
||||
$dbSchemaDiagnostics = $this->Server->dbSchemaDiagnostic();
|
||||
|
||||
$redisInfo = $this->Server->redisInfo();
|
||||
|
||||
$moduleTypes = array('Enrichment', 'Import', 'Export', 'Cortex');
|
||||
foreach ($moduleTypes as $type) {
|
||||
$moduleStatus[$type] = $this->Server->moduleDiagnostics($diagnostic_errors, $type);
|
||||
}
|
||||
|
||||
// check the size of the session table
|
||||
$sessionCount = 0;
|
||||
$sessionStatus = $this->Server->sessionDiagnostics($diagnostic_errors, $sessionCount);
|
||||
$this->set('sessionCount', $sessionCount);
|
||||
|
||||
$this->loadModel('AttachmentScan');
|
||||
try {
|
||||
$attachmentScan = ['status' => true, 'software' => $this->AttachmentScan->diagnostic()];
|
||||
} catch (Exception $e) {
|
||||
$attachmentScan = ['status' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
|
||||
$securityAudit = (new SecurityAudit())->run($this->Server);
|
||||
|
||||
$view = compact('gpgStatus', 'sessionErrors', 'proxyStatus', 'sessionStatus', 'zmqStatus', 'stixVersion', 'cyboxVersion', 'mixboxVersion', 'maecVersion', 'stix2Version', 'pymispVersion', 'moduleStatus', 'yaraStatus', 'gpgErrors', 'proxyErrors', 'zmqErrors', 'stixOperational', 'stix', 'moduleErrors', 'moduleTypes', 'dbDiagnostics', 'dbSchemaDiagnostics', 'redisInfo', 'attachmentScan', 'securityAudit');
|
||||
} else {
|
||||
$view = [];
|
||||
}
|
||||
|
||||
// check whether the files are writeable
|
||||
$writeableDirs = $this->Server->writeableDirsDiagnostics($diagnostic_errors);
|
||||
$writeableFiles = $this->Server->writeableFilesDiagnostics($diagnostic_errors);
|
||||
$readableFiles = $this->Server->readableFilesDiagnostics($diagnostic_errors);
|
||||
$extensions = $this->Server->extensionDiagnostics();
|
||||
|
||||
// check if the encoding is not set to utf8
|
||||
$dbEncodingStatus = $this->Server->databaseEncodingDiagnostics($diagnostic_errors);
|
||||
|
||||
$view = array_merge($view, compact('diagnostic_errors', 'tabs', 'tab', 'issues', 'finalSettings', 'writeableErrors', 'readableErrors', 'writeableDirs', 'writeableFiles', 'readableFiles', 'extensions', 'dbEncodingStatus'));
|
||||
$this->set($view);
|
||||
|
||||
$workerIssueCount = 4;
|
||||
$worker_array = array();
|
||||
if (Configure::read('MISP.background_jobs')) {
|
||||
$workerIssueCount = 0;
|
||||
$worker_array = $this->Server->workerDiagnostics($workerIssueCount);
|
||||
}
|
||||
$this->set('worker_array', $worker_array);
|
||||
if ($tab === 'download' || $this->_isRest()) {
|
||||
foreach ($dumpResults as $key => $dr) {
|
||||
unset($dumpResults[$key]['description']);
|
||||
}
|
||||
$dump = array(
|
||||
'version' => $version,
|
||||
'phpSettings' => $phpSettings,
|
||||
'gpgStatus' => $gpgErrors[$gpgStatus['status']],
|
||||
'proxyStatus' => $proxyErrors[$proxyStatus],
|
||||
'zmqStatus' => $zmqStatus,
|
||||
'stix' => $stix,
|
||||
'moduleStatus' => $moduleStatus,
|
||||
'writeableDirs' => $writeableDirs,
|
||||
'writeableFiles' => $writeableFiles,
|
||||
'readableFiles' => $readableFiles,
|
||||
'dbDiagnostics' => $dbDiagnostics,
|
||||
'dbSchemaDiagnostics' => $dbSchemaDiagnostics,
|
||||
'redisInfo' => $redisInfo,
|
||||
'finalSettings' => $dumpResults,
|
||||
'extensions' => $extensions,
|
||||
'workers' => $worker_array
|
||||
);
|
||||
foreach ($dump['finalSettings'] as $k => $v) {
|
||||
if (!empty($v['redacted'])) {
|
||||
$dump['finalSettings'][$k]['value'] = '*****';
|
||||
}
|
||||
}
|
||||
$this->response->body(json_encode($dump, JSON_PRETTY_PRINT));
|
||||
$this->response->type('json');
|
||||
$this->response->download('MISP.report.json');
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
$priorities = array(0 => 'Critical', 1 => 'Recommended', 2 => 'Optional', 3 => 'Deprecated');
|
||||
$this->set('priorities', $priorities);
|
||||
$this->set('workerIssueCount', $workerIssueCount);
|
||||
$priorityErrorColours = array(0 => 'red', 1 => 'yellow', 2 => 'green');
|
||||
$this->set('priorityErrorColours', $priorityErrorColours);
|
||||
$this->set('phpversion', phpversion());
|
||||
$this->set('phpmin', $this->phpmin);
|
||||
$this->set('phprec', $this->phprec);
|
||||
$this->set('phptoonew', $this->phptoonew);
|
||||
$this->set('pythonmin', $this->pythonmin);
|
||||
$this->set('pythonrec', $this->pythonrec);
|
||||
$this->set('pymisp', $this->pymisp);
|
||||
}
|
||||
|
||||
public function startWorker($type)
|
||||
|
@ -1250,9 +1274,6 @@ class ServersController extends AppController
|
|||
|
||||
private function __checkVersion()
|
||||
{
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
throw new MethodNotAllowedException();
|
||||
}
|
||||
App::uses('SyncTool', 'Tools');
|
||||
$syncTool = new SyncTool();
|
||||
try {
|
||||
|
@ -1342,17 +1363,20 @@ class ServersController extends AppController
|
|||
continue;
|
||||
}
|
||||
|
||||
$exception = null;
|
||||
try {
|
||||
$remote_event = $this->Event->downloadEventFromServer($local_event['Event']['uuid'], $server, null, true);
|
||||
$remote_event_id = $remote_event[0]['id'];
|
||||
$remoteEvent = $this->Event->downloadEventFromServer($local_event['Event']['uuid'], $server, null, true);
|
||||
} catch (Exception $e) {
|
||||
$remote_event_id = null;
|
||||
$remoteEvent = null;
|
||||
$exception = $e->getMessage();
|
||||
}
|
||||
$remoteEventId = isset($remoteEvent[0]['id']) ? $remoteEvent[0]['id'] : null;
|
||||
$remote_events[] = array(
|
||||
"server_id" => $server['Server']['id'],
|
||||
"server_name" => $server['Server']['name'],
|
||||
"url" => isset($remote_event_id) ? $server['Server']['url']."/events/view/".$remote_event_id : $server['Server']['url'],
|
||||
"remote_id" => isset($remote_event_id) ? $remote_event_id : false
|
||||
"url" => isset($remoteEventId) ? $server['Server']['url'] . "/events/view/" . $remoteEventId : $server['Server']['url'],
|
||||
"remote_id" => isset($remoteEventId) ? $remoteEventId : false,
|
||||
"exception" => $exception,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1362,10 +1386,8 @@ class ServersController extends AppController
|
|||
$this->set('title_for_layout', __('Event ID translator'));
|
||||
}
|
||||
|
||||
public function getSubmodulesStatus() {
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
throw new MethodNotAllowedException();
|
||||
}
|
||||
public function getSubmodulesStatus()
|
||||
{
|
||||
$this->set('submodules', $this->Server->getSubmodulesGitStatus());
|
||||
$this->render('ajax/submoduleStatus');
|
||||
}
|
||||
|
@ -1384,9 +1406,6 @@ class ServersController extends AppController
|
|||
|
||||
public function serverSettingsEdit($setting_name, $id = false, $forceSave = false)
|
||||
{
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
throw new MethodNotAllowedException();
|
||||
}
|
||||
if (!isset($setting_name)) {
|
||||
throw new MethodNotAllowedException();
|
||||
}
|
||||
|
@ -1518,15 +1537,6 @@ class ServersController extends AppController
|
|||
$this->redirect(array('controller' => 'servers', 'action' => 'serverSettings', 'workers'));
|
||||
}
|
||||
|
||||
private function __manageFiles()
|
||||
{
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
throw new MethodNotAllowedException();
|
||||
}
|
||||
$files = $this->Server->grabFiles();
|
||||
return $files;
|
||||
}
|
||||
|
||||
public function deleteFile($type, $filename)
|
||||
{
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
|
@ -1652,10 +1662,6 @@ class ServersController extends AppController
|
|||
|
||||
public function testConnection($id = false)
|
||||
{
|
||||
if (!$this->Auth->user('Role')['perm_sync'] && !$this->Auth->user('Role')['perm_site_admin']) {
|
||||
throw new MethodNotAllowedException('You don\'t have permission to do that.');
|
||||
}
|
||||
|
||||
$server = $this->Server->find('first', ['conditions' => ['Server.id' => $id]]);
|
||||
if (!$server) {
|
||||
throw new NotFoundException(__('Invalid server'));
|
||||
|
@ -1704,23 +1710,17 @@ class ServersController extends AppController
|
|||
$result['status'] = 8;
|
||||
return new CakeResponse(array('body'=> json_encode($result), 'type' => 'json'));
|
||||
}
|
||||
return new CakeResponse(
|
||||
array(
|
||||
'body'=> json_encode(
|
||||
array(
|
||||
'status' => 1,
|
||||
'local_version' => implode('.', $local_version),
|
||||
'version' => implode('.', $version),
|
||||
'mismatch' => $mismatch,
|
||||
'newer' => $newer,
|
||||
'post' => isset($post) ? $post['status'] : 'too old',
|
||||
'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null,
|
||||
'client_certificate' => $result['client_certificate'],
|
||||
)
|
||||
),
|
||||
'type' => 'json'
|
||||
)
|
||||
);
|
||||
return $this->RestResponse->viewData([
|
||||
'status' => 1,
|
||||
'local_version' => implode('.', $local_version),
|
||||
'version' => implode('.', $version),
|
||||
'mismatch' => $mismatch,
|
||||
'newer' => $newer,
|
||||
'post' => isset($post) ? $post['status'] : 'too old',
|
||||
'response_encoding' => isset($post['content-encoding']) ? $post['content-encoding'] : null,
|
||||
'request_encoding' => isset($result['info']['request_encoding']) ? $result['info']['request_encoding'] : null,
|
||||
'client_certificate' => $result['client_certificate'],
|
||||
], 'json');
|
||||
} else {
|
||||
$result['status'] = 3;
|
||||
}
|
||||
|
@ -1800,17 +1800,15 @@ class ServersController extends AppController
|
|||
|
||||
public function getVersion()
|
||||
{
|
||||
if (!$this->userRole['perm_auth']) {
|
||||
throw new MethodNotAllowedException('This action requires API access.');
|
||||
}
|
||||
$versionArray = $this->Server->checkMISPVersion();
|
||||
$this->set('response', array(
|
||||
$response = [
|
||||
'version' => $versionArray['major'] . '.' . $versionArray['minor'] . '.' . $versionArray['hotfix'],
|
||||
'perm_sync' => $this->userRole['perm_sync'],
|
||||
'perm_sighting' => $this->userRole['perm_sighting'],
|
||||
'perm_galaxy_editor' => $this->userRole['perm_galaxy_editor'],
|
||||
));
|
||||
$this->set('_serialize', 'response');
|
||||
'request_encoding' => $this->CompressedRequestHandler->supportedEncodings(),
|
||||
];
|
||||
return $this->RestResponse->viewData($response, $this->response->type());
|
||||
}
|
||||
|
||||
public function getPyMISPVersion()
|
||||
|
@ -2059,7 +2057,8 @@ class ServersController extends AppController
|
|||
App::uses('HttpSocket', 'Network/Http');
|
||||
$HttpSocket = new HttpSocket($params);
|
||||
$view_data = array();
|
||||
$temp_headers = explode("\n", $request['header']);
|
||||
|
||||
$temp_headers = empty($request['header']) ? [] : explode("\n", $request['header']);
|
||||
$request['header'] = array(
|
||||
'Authorization' => $this->Auth->user('authkey'),
|
||||
'Accept' => 'application/json',
|
||||
|
@ -2443,6 +2442,27 @@ misp.direct_call(relative_path, body)
|
|||
}
|
||||
}
|
||||
|
||||
public function cspReport()
|
||||
{
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException('This action expects a POST request.');
|
||||
}
|
||||
|
||||
$report = $this->Server->jsonDecode($this->request->input());
|
||||
if (!isset($report['csp-report'])) {
|
||||
throw new RuntimeException("Invalid report");
|
||||
}
|
||||
|
||||
$message = 'CSP reported violation';
|
||||
$remoteIp = $this->_remoteIp();
|
||||
if ($remoteIp) {
|
||||
$message .= ' from IP ' . $remoteIp;
|
||||
}
|
||||
$this->log("$message: " . json_encode($report['csp-report'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
|
||||
return new CakeResponse(['statusCodes' => 204]);
|
||||
}
|
||||
|
||||
public function viewDeprecatedFunctionUse()
|
||||
{
|
||||
$data = $this->Deprecation->getDeprecatedAccessList($this->Server);
|
||||
|
|
|
@ -828,6 +828,52 @@ class ShadowAttributesController extends AppController
|
|||
$this->set('_serialize', array('ShadowAttribute'));
|
||||
}
|
||||
|
||||
public function viewPicture($id, $thumbnail=false)
|
||||
{
|
||||
$conditions['ShadowAttribute.id'] = $id;
|
||||
$conditions['ShadowAttribute.type'] = 'attachment';
|
||||
$options = array(
|
||||
'conditions' => $conditions,
|
||||
'includeAllTags' => false,
|
||||
'includeAttributeUuid' => true,
|
||||
'flatten' => true,
|
||||
'deleted' => [0, 1]
|
||||
);
|
||||
|
||||
|
||||
$sa = $this->ShadowAttribute->find('first', array(
|
||||
'recursive' => -1,
|
||||
'contain' => ['Event', 'Attribute'], // required because of conditions
|
||||
'fields' => array(
|
||||
'ShadowAttribute.id', 'ShadowAttribute.old_id', 'ShadowAttribute.event_id', 'ShadowAttribute.type', 'ShadowAttribute.category', 'ShadowAttribute.uuid', 'ShadowAttribute.to_ids', 'ShadowAttribute.value', 'ShadowAttribute.comment', 'ShadowAttribute.org_id', 'ShadowAttribute.first_seen', 'ShadowAttribute.last_seen',
|
||||
),
|
||||
'conditions' => $conditions,
|
||||
));
|
||||
if (empty($sa)) {
|
||||
throw new NotFoundException(__('Invalid proposal.'));
|
||||
}
|
||||
|
||||
if (!$this->ShadowAttribute->Attribute->isImage($sa['ShadowAttribute'])) {
|
||||
throw new NotFoundException("ShadowAttribute is not an image.");
|
||||
}
|
||||
if ($this->_isRest()) {
|
||||
if ($this->ShadowAttribute->typeIsAttachment($sa['ShadowAttribute']['type'])) {
|
||||
$encodedFile = $this->ShadowAttribute->base64EncodeAttachment($sa['ShadowAttribute']);
|
||||
$sa['ShadowAttribute']['data'] = $encodedFile;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->viewData($sa['ShadowAttribute']['data'], $this->response->type());
|
||||
} else {
|
||||
$width = isset($this->request->params['named']['width']) ? $this->request->params['named']['width'] : 200;
|
||||
$height = isset($this->request->params['named']['height']) ? $this->request->params['named']['height'] : 200;
|
||||
$imageData = $this->ShadowAttribute->getPictureData($sa, $thumbnail, $width, $height);
|
||||
$extension = pathinfo($sa['ShadowAttribute']['value'], PATHINFO_EXTENSION);
|
||||
return new CakeResponse(array('body' => $imageData, 'type' => strtolower($extension)));
|
||||
}
|
||||
}
|
||||
|
||||
public function index($eventId = false)
|
||||
{
|
||||
$conditions = array();
|
||||
|
|
|
@ -608,7 +608,7 @@ class SharingGroupsController extends AppController
|
|||
}
|
||||
}
|
||||
}
|
||||
if (false === $addServer) {
|
||||
if (false === $removeServer) {
|
||||
return $this->RestResponse->saveFailResponse('SharingGroup', $this->action, false, 'Server is not in the sharing group.', $this->response->type());
|
||||
}
|
||||
$result = $this->SharingGroup->SharingGroupServer->delete($removeServer);
|
||||
|
|
|
@ -223,7 +223,7 @@ class SightingsController extends AppController
|
|||
}
|
||||
}
|
||||
|
||||
// takes a sighting ID
|
||||
// takes a sighting ID or UUID
|
||||
public function delete($id)
|
||||
{
|
||||
if (!$this->userRole['perm_modify_org']) {
|
||||
|
@ -232,22 +232,22 @@ class SightingsController extends AppController
|
|||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException('This action can only be accessed via a post request.');
|
||||
}
|
||||
$id = $this->Toolbox->findIdByUuid($this->Sighting, $id);
|
||||
$conditions = array('Sighting.id' => $id);
|
||||
$sighting = $this->Sighting->find('first', array('conditions' => $conditions, 'recursive' => -1));
|
||||
$sighting = $this->Sighting->find('first', array(
|
||||
'conditions' => Validation::uuid($id) ? ['Sighting.uuid' => $id] : ['Sighting.id' => $id],
|
||||
'recursive' => -1,
|
||||
'fields' => ['id', 'org_id'],
|
||||
));
|
||||
if (empty($sighting)) {
|
||||
throw new NotFoundException('Invalid sighting.');
|
||||
}
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
if ($sighting['Sighting']['org_id'] != $this->Auth->user('org_id')) {
|
||||
throw new NotFoundException('Invalid sighting.');
|
||||
}
|
||||
if (!$this->_isSiteAdmin() && $sighting['Sighting']['org_id'] != $this->Auth->user('org_id')) {
|
||||
throw new NotFoundException('Invalid sighting.');
|
||||
}
|
||||
$result = $this->Sighting->delete($sighting['Sighting']['id']);
|
||||
if (!$result) {
|
||||
return $this->RestResponse->saveFailResponse('Sighting', 'delete', $id, 'Could not delete the Sighting.');
|
||||
} else {
|
||||
return $this->RestResponse->saveSuccessResponse('Sighting', 'delete', $id, false, 'Sighting successfuly deleted.');
|
||||
return $this->RestResponse->saveSuccessResponse('Sighting', 'delete', $id, false, 'Sighting successfully deleted.');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -41,7 +41,6 @@ class TagsController extends AppController
|
|||
'request' => $this->request,
|
||||
'named_params' => $this->params['named'],
|
||||
'paramArray' => ['favouritesOnly', 'filter', 'searchall', 'name', 'search', 'exclude_statistics'],
|
||||
'ordered_url_params' => @compact($paramArray)
|
||||
);
|
||||
$exception = false;
|
||||
$passedArgsArray = $this->_harvestParameters($filterData, $exception);
|
||||
|
@ -140,9 +139,6 @@ class TagsController extends AppController
|
|||
|
||||
public function add()
|
||||
{
|
||||
if (!$this->_isSiteAdmin() && !$this->userRole['perm_tag_editor']) {
|
||||
throw new NotFoundException('You don\'t have permission to do that.');
|
||||
}
|
||||
if ($this->request->is('post')) {
|
||||
if (!isset($this->request->data['Tag'])) {
|
||||
$this->request->data = array('Tag' => $this->request->data);
|
||||
|
@ -193,31 +189,22 @@ class TagsController extends AppController
|
|||
} elseif ($this->_isRest()) {
|
||||
return $this->RestResponse->describe('Tag', 'add', false, $this->response->type());
|
||||
}
|
||||
$this->loadModel('Organisation');
|
||||
$temp = $this->Organisation->find('all', array(
|
||||
|
||||
$orgs = $this->Tag->Organisation->find('list', array(
|
||||
'conditions' => array('local' => 1),
|
||||
'fields' => array('id', 'name'),
|
||||
'recursive' => -1
|
||||
'order' => 'name',
|
||||
));
|
||||
$orgs = array(0 => 'Unrestricted');
|
||||
if (!empty($temp)) {
|
||||
foreach ($temp as $org) {
|
||||
$orgs[$org['Organisation']['id']] = $org['Organisation']['name'];
|
||||
}
|
||||
}
|
||||
$orgs = [0 => 'Unrestricted'] + $orgs;
|
||||
$this->set('orgs', $orgs);
|
||||
$users = array(0 => 'Unrestricted');
|
||||
|
||||
if ($this->_isSiteAdmin()) {
|
||||
$temp = $this->Organisation->User->find('all', array(
|
||||
$users = $this->Tag->User->find('list', array(
|
||||
'conditions' => array('disabled' => 0),
|
||||
'fields' => array('id', 'email'),
|
||||
'recursive' => -1
|
||||
'order' => 'email',
|
||||
));
|
||||
if (!empty($temp)) {
|
||||
foreach ($temp as $user) {
|
||||
$users[$user['User']['id']] = $user['User']['email'];
|
||||
}
|
||||
}
|
||||
$users = [0 => 'Unrestricted'] + $users;
|
||||
$this->set('users', $users);
|
||||
}
|
||||
}
|
||||
|
@ -248,9 +235,6 @@ class TagsController extends AppController
|
|||
throw new NotFoundException('Invalid tag');
|
||||
}
|
||||
}
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
throw new NotFoundException('You don\'t have permission to do that.');
|
||||
}
|
||||
if ($this->request->is('post') || $this->request->is('put')) {
|
||||
if (!isset($this->request->data['Tag'])) {
|
||||
$this->request->data = array('Tag' => $this->request->data);
|
||||
|
@ -281,41 +265,28 @@ class TagsController extends AppController
|
|||
} elseif ($this->_isRest()) {
|
||||
return $this->RestResponse->describe('Tag', 'edit', false, $this->response->type());
|
||||
}
|
||||
$this->loadModel('Organisation');
|
||||
$temp = $this->Organisation->find('all', array(
|
||||
|
||||
$orgs = $this->Tag->Organisation->find('list', array(
|
||||
'conditions' => array('local' => 1),
|
||||
'fields' => array('id', 'name'),
|
||||
'recursive' => -1
|
||||
'order' => 'name',
|
||||
));
|
||||
$orgs = array(0 => 'Unrestricted');
|
||||
if (!empty($temp)) {
|
||||
foreach ($temp as $org) {
|
||||
$orgs[$org['Organisation']['id']] = $org['Organisation']['name'];
|
||||
}
|
||||
}
|
||||
$orgs = [0 => 'Unrestricted'] + $orgs;
|
||||
$this->set('orgs', $orgs);
|
||||
$users = array(0 => 'Unrestricted');
|
||||
if ($this->_isSiteAdmin()) {
|
||||
$temp = $this->Organisation->User->find('all', array(
|
||||
'conditions' => array('disabled' => 0),
|
||||
'fields' => array('id', 'email'),
|
||||
'recursive' => -1
|
||||
));
|
||||
if (!empty($temp)) {
|
||||
foreach ($temp as $user) {
|
||||
$users[$user['User']['id']] = $user['User']['email'];
|
||||
}
|
||||
}
|
||||
$this->set('users', $users);
|
||||
}
|
||||
|
||||
$users = $this->Tag->User->find('list', array(
|
||||
'conditions' => array('disabled' => 0),
|
||||
'fields' => array('id', 'email'),
|
||||
'order' => 'email',
|
||||
));
|
||||
$users = [0 => 'Unrestricted'] + $users;
|
||||
$this->set('users', $users);
|
||||
|
||||
$this->request->data = $this->Tag->read(null, $id);
|
||||
}
|
||||
|
||||
public function delete($id)
|
||||
{
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
throw new NotFoundException('You don\'t have permission to do that.');
|
||||
}
|
||||
if (!$this->request->is('post')) {
|
||||
throw new MethodNotAllowedException();
|
||||
}
|
||||
|
@ -344,79 +315,33 @@ class TagsController extends AppController
|
|||
|
||||
public function view($id)
|
||||
{
|
||||
if ($this->_isRest()) {
|
||||
$contain = array('EventTag' => array('fields' => 'event_id'));
|
||||
$contain['AttributeTag'] = array('fields' => 'attribute_id');
|
||||
$tag = $this->Tag->find('first', array(
|
||||
'conditions' => array('id' => $id),
|
||||
'recursive' => -1,
|
||||
'contain' => $contain
|
||||
));
|
||||
if (empty($tag)) {
|
||||
throw new MethodNotAllowedException('Invalid Tag');
|
||||
}
|
||||
if (empty($tag['EventTag'])) {
|
||||
$tag['Tag']['count'] = 0;
|
||||
} else {
|
||||
$eventIDs = array();
|
||||
foreach ($tag['EventTag'] as $eventTag) {
|
||||
$eventIDs[] = $eventTag['event_id'];
|
||||
}
|
||||
$conditions = array('Event.id' => $eventIDs);
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
$conditions = array_merge(
|
||||
$conditions,
|
||||
array('OR' => array(
|
||||
array('AND' => array(
|
||||
array('Event.distribution >' => 0),
|
||||
array('Event.published =' => 1)
|
||||
)),
|
||||
array('Event.orgc_id' => $this->Auth->user('org_id'))
|
||||
))
|
||||
);
|
||||
}
|
||||
$events = $this->Tag->EventTag->Event->find('all', array(
|
||||
'fields' => array('Event.id', 'Event.distribution', 'Event.orgc_id'),
|
||||
'conditions' => $conditions
|
||||
));
|
||||
$tag['Tag']['count'] = count($events);
|
||||
}
|
||||
unset($tag['EventTag']);
|
||||
if (empty($tag['AttributeTag'])) {
|
||||
$tag['Tag']['attribute_count'] = 0;
|
||||
} else {
|
||||
$attributeIDs = array();
|
||||
foreach ($tag['AttributeTag'] as $attributeTag) {
|
||||
$attributeIDs[] = $attributeTag['attribute_id'];
|
||||
}
|
||||
$conditions = array('Attribute.id' => $attributeIDs);
|
||||
if (!$this->_isSiteAdmin()) {
|
||||
$conditions = array_merge(
|
||||
$conditions,
|
||||
array('OR' => array(
|
||||
array('AND' => array(
|
||||
array('Attribute.deleted =' => 0),
|
||||
array('Attribute.distribution >' => 0),
|
||||
array('Event.distribution >' => 0),
|
||||
array('Event.published =' => 1)
|
||||
)),
|
||||
array('Event.orgc_id' => $this->Auth->user('org_id'))
|
||||
))
|
||||
);
|
||||
}
|
||||
$attributes = $this->Tag->AttributeTag->Attribute->find('all', array(
|
||||
'fields' => array('Attribute.id', 'Attribute.deleted', 'Attribute.distribution', 'Event.id', 'Event.distribution', 'Event.orgc_id'),
|
||||
'contain' => array('Event' => array('fields' => array('id', 'distribution', 'orgc_id'))),
|
||||
'conditions' => $conditions
|
||||
));
|
||||
$tag['Tag']['attribute_count'] = count($attributes);
|
||||
}
|
||||
unset($tag['AttributeTag']);
|
||||
$this->set('Tag', $tag['Tag']);
|
||||
$this->set('_serialize', 'Tag');
|
||||
} else {
|
||||
if (!$this->_isRest()) {
|
||||
throw new MethodNotAllowedException('This action is only for REST users.');
|
||||
}
|
||||
|
||||
$tag = $this->Tag->find('first', array(
|
||||
'conditions' => array('id' => $id),
|
||||
'recursive' => -1,
|
||||
'contain' => ['AttributeTag' => ['fields' => 'attribute_id']],
|
||||
));
|
||||
if (empty($tag)) {
|
||||
throw new MethodNotAllowedException('Invalid Tag');
|
||||
}
|
||||
|
||||
$tag['Tag']['count'] = $this->Tag->EventTag->countForTag($tag['Tag']['id'], $this->Auth->user());
|
||||
|
||||
if (empty($tag['AttributeTag'])) {
|
||||
$tag['Tag']['attribute_count'] = 0;
|
||||
} else {
|
||||
$attributeIDs = array_column($tag['AttributeTag'], 'attribute_id');
|
||||
$tag['Tag']['attribute_count'] = count($this->Tag->AttributeTag->Attribute->fetchAttributes($this->Auth->user(), [
|
||||
'conditions' => ['Attribute.id' => $attributeIDs],
|
||||
'list' => true,
|
||||
]));
|
||||
}
|
||||
unset($tag['AttributeTag']);
|
||||
|
||||
return $this->RestResponse->viewData($tag['Tag'], $this->response->type());
|
||||
}
|
||||
|
||||
public function showEventTag($id)
|
||||
|
@ -439,7 +364,7 @@ class TagsController extends AppController
|
|||
$event = $this->Tag->EventTag->Event->massageTags($this->Auth->user(), $event, 'Event', false, true);
|
||||
|
||||
$this->set('tags', $event['EventTag']);
|
||||
$this->set('required_taxonomies', $this->Tag->EventTag->Event->getRequiredTaxonomies());
|
||||
$this->set('missingTaxonomies', $this->Tag->EventTag->Event->missingTaxonomies($event));
|
||||
$tagConflicts = $this->Taxonomy->checkIfTagInconsistencies($event['EventTag']);
|
||||
$this->set('tagConflicts', $tagConflicts);
|
||||
$this->set('event', $event);
|
||||
|
|
|
@ -9,17 +9,17 @@ class TaxonomiesController extends AppController
|
|||
public $components = array('Session', 'RequestHandler');
|
||||
|
||||
public $paginate = array(
|
||||
'limit' => 60,
|
||||
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user van view/page.
|
||||
'contain' => array(
|
||||
'TaxonomyPredicate' => array(
|
||||
'fields' => array('TaxonomyPredicate.id'),
|
||||
'TaxonomyEntry' => array('fields' => array('TaxonomyEntry.id'))
|
||||
)
|
||||
),
|
||||
'order' => array(
|
||||
'Taxonomy.id' => 'DESC'
|
||||
),
|
||||
'limit' => 60,
|
||||
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user van view/page.
|
||||
'contain' => array(
|
||||
'TaxonomyPredicate' => array(
|
||||
'fields' => array('TaxonomyPredicate.id', 'TaxonomyPredicate.value'),
|
||||
'TaxonomyEntry' => array('fields' => array('TaxonomyEntry.id', 'TaxonomyEntry.value'))
|
||||
)
|
||||
),
|
||||
'order' => array(
|
||||
'Taxonomy.id' => 'DESC'
|
||||
),
|
||||
);
|
||||
|
||||
public function index()
|
||||
|
@ -46,25 +46,15 @@ class TaxonomiesController extends AppController
|
|||
} else {
|
||||
$taxonomies = $this->paginate();
|
||||
}
|
||||
$this->loadModel('Tag');
|
||||
foreach ($taxonomies as $key => $taxonomy) {
|
||||
$total = 0;
|
||||
foreach ($taxonomy['TaxonomyPredicate'] as $predicate) {
|
||||
$total += empty($predicate['TaxonomyEntry']) ? 1 : count($predicate['TaxonomyEntry']);
|
||||
}
|
||||
$taxonomies[$key]['total_count'] = $total;
|
||||
$taxonomies[$key]['current_count'] = $this->Tag->find('count', array(
|
||||
'conditions' => array('lower(Tag.name) LIKE ' => strtolower($taxonomy['Taxonomy']['namespace']) . ':%', 'hide_tag' => 0),
|
||||
'recursive' => -1,
|
||||
));
|
||||
unset($taxonomies[$key]['TaxonomyPredicate']);
|
||||
}
|
||||
|
||||
$taxonomies = $this->__tagCount($taxonomies);
|
||||
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->viewData($taxonomies, $this->response->type());
|
||||
} else {
|
||||
$this->set('taxonomies', $taxonomies);
|
||||
$this->set('passedArgsArray', $this->passedArgs);
|
||||
}
|
||||
|
||||
$this->set('taxonomies', $taxonomies);
|
||||
$this->set('passedArgsArray', $this->passedArgs);
|
||||
}
|
||||
|
||||
public function view($id)
|
||||
|
@ -111,14 +101,15 @@ class TaxonomiesController extends AppController
|
|||
$params = $customPagination->applyRulesOnArray($taxonomy['entries'], $params, 'taxonomies');
|
||||
if ($this->_isRest()) {
|
||||
return $this->RestResponse->viewData($taxonomy, $this->response->type());
|
||||
} else {
|
||||
$this->set('entries', $taxonomy['entries']);
|
||||
$this->set('urlparams', $urlparams);
|
||||
$this->set('passedArgs', json_encode($passedArgs));
|
||||
$this->set('passedArgsArray', $passedArgs);
|
||||
$this->set('taxonomy', $taxonomy['Taxonomy']);
|
||||
$this->set('id', $id);
|
||||
}
|
||||
|
||||
$this->set('entries', $taxonomy['entries']);
|
||||
$this->set('urlparams', $urlparams);
|
||||
$this->set('passedArgs', json_encode($passedArgs));
|
||||
$this->set('passedArgsArray', $passedArgs);
|
||||
$this->set('taxonomy', $taxonomy['Taxonomy']);
|
||||
$this->set('id', $id);
|
||||
$this->set('title_for_layout', __('%s Taxonomy Library', h(strtoupper($taxonomy['Taxonomy']['namespace']))));
|
||||
}
|
||||
|
||||
public function enable($id)
|
||||
|
@ -469,6 +460,53 @@ class TaxonomiesController extends AppController
|
|||
$this->render('ajax/toggle_required');
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach tag counts.
|
||||
* @param array $taxonomies
|
||||
* @return array
|
||||
*/
|
||||
private function __tagCount(array $taxonomies)
|
||||
{
|
||||
$tags = [];
|
||||
foreach ($taxonomies as $taxonomyPos => $taxonomy) {
|
||||
$total = 0;
|
||||
foreach ($taxonomy['TaxonomyPredicate'] as $predicate) {
|
||||
if (isset($predicate['TaxonomyEntry']) && !empty($predicate['TaxonomyEntry'])) {
|
||||
foreach ($predicate['TaxonomyEntry'] as $entry) {
|
||||
$tag = mb_strtolower($taxonomy['Taxonomy']['namespace'] . ':' . $predicate['value'] . '="' . $entry['value'] . '"');
|
||||
$tags[$tag] = $taxonomyPos;
|
||||
$total++;
|
||||
}
|
||||
} else {
|
||||
$tag = mb_strtolower($taxonomy['Taxonomy']['namespace'] . ':' . $predicate['value']);
|
||||
$tags[$tag] = $taxonomyPos;
|
||||
$total++;
|
||||
}
|
||||
}
|
||||
$taxonomies[$taxonomyPos]['total_count'] = $total;
|
||||
$taxonomies[$taxonomyPos]['current_count'] = 0;
|
||||
unset($taxonomies[$taxonomyPos]['TaxonomyPredicate']);
|
||||
}
|
||||
|
||||
$this->loadModel('Tag');
|
||||
$existingTags = $this->Tag->find('column', [
|
||||
'fields' => ['Tag.name'],
|
||||
'conditions' => [
|
||||
'lower(Tag.name)' => array_keys($tags),
|
||||
'hide_tag' => 0
|
||||
],
|
||||
]);
|
||||
|
||||
foreach ($existingTags as $existingTag) {
|
||||
$existingTag = mb_strtolower($existingTag);
|
||||
if (isset($tags[$existingTag])) {
|
||||
$taxonomies[$tags[$existingTag]]['current_count']++;
|
||||
}
|
||||
}
|
||||
|
||||
return $taxonomies;
|
||||
}
|
||||
|
||||
private function __search($value)
|
||||
{
|
||||
$value = mb_strtolower(trim($value));
|
||||
|
|
|
@ -922,23 +922,13 @@ class UsersController extends AppController
|
|||
continue;
|
||||
}
|
||||
if ($field != 'password') {
|
||||
array_push($fields, $field);
|
||||
}
|
||||
}
|
||||
$fieldsOldValues = array();
|
||||
foreach ($fields as $field) {
|
||||
if ($field == 'enable_password') {
|
||||
continue;
|
||||
}
|
||||
if ($field != 'confirm_password') {
|
||||
$fieldsOldValues[$field] = $this->User->field($field);
|
||||
} else {
|
||||
$fieldsOldValues[$field] = $this->User->field('password');
|
||||
$fields[] = $field;
|
||||
}
|
||||
}
|
||||
if (
|
||||
isset($this->request->data['User']['enable_password']) && $this->request->data['User']['enable_password'] != '0' &&
|
||||
isset($this->request->data['User']['password']) && "" != $this->request->data['User']['password']
|
||||
(!empty($this->request->data['User']['enable_password']) || $this->_isRest()) &&
|
||||
!empty($this->request->data['User']['password']) &&
|
||||
$this->__canChangePassword()
|
||||
) {
|
||||
$fields[] = 'password';
|
||||
if ($this->_isRest() && !isset($this->request->data['User']['confirm_password'])) {
|
||||
|
@ -958,6 +948,12 @@ class UsersController extends AppController
|
|||
}
|
||||
}
|
||||
$fields[] = 'date_modified'; // time will be inserted in `beforeSave` action
|
||||
|
||||
$fieldsOldValues = $this->User->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => ['id' => $id],
|
||||
])['User'];
|
||||
|
||||
if ($this->User->save($this->request->data, true, $fields)) {
|
||||
// newValues to array
|
||||
$fieldsNewValues = array();
|
||||
|
@ -967,7 +963,7 @@ class UsersController extends AppController
|
|||
}
|
||||
if ($field !== 'confirm_password') {
|
||||
$newValue = $this->data['User'][$field];
|
||||
if (gettype($newValue) == 'array') {
|
||||
if (is_array($newValue)) {
|
||||
$newValueStr = '';
|
||||
$cP = 0;
|
||||
foreach ($newValue as $newValuePart) {
|
||||
|
@ -989,6 +985,9 @@ class UsersController extends AppController
|
|||
// compare
|
||||
$fieldsResult = array();
|
||||
foreach ($fields as $field) {
|
||||
if ($field === 'date_modified') {
|
||||
continue;
|
||||
}
|
||||
if (isset($fieldsOldValues[$field]) && $fieldsOldValues[$field] != $fieldsNewValues[$field]) {
|
||||
if ($field != 'confirm_password' && $field != 'enable_password') {
|
||||
$fieldsResult[$field] = array($fieldsOldValues[$field], $fieldsNewValues[$field]);
|
||||
|
@ -1138,7 +1137,7 @@ class UsersController extends AppController
|
|||
}
|
||||
if ($this->request->is('post') && Configure::read('Security.email_otp_enabled')) {
|
||||
$user = $this->Auth->identify($this->request, $this->response);
|
||||
if ($user) {
|
||||
if ($user && !$user['disabled']) {
|
||||
$this->Session->write('email_otp_user', $user);
|
||||
return $this->redirect('email_otp');
|
||||
}
|
||||
|
@ -1295,24 +1294,22 @@ class UsersController extends AppController
|
|||
|
||||
public function resetauthkey($id = null, $alert = false)
|
||||
{
|
||||
if (!$this->_isAdmin() && Configure::read('MISP.disableUserSelfManagement')) {
|
||||
throw new MethodNotAllowedException('User self-management has been disabled on this instance.');
|
||||
}
|
||||
if (!$this->request->is('post') && !$this->request->is('put')) {
|
||||
throw new MethodNotAllowedException(__('This functionality is only accessible via POST requests.'));
|
||||
}
|
||||
if ($id == 'me') {
|
||||
if ($id === 'me') {
|
||||
$id = $this->Auth->user('id');
|
||||
// Reset just current auth key
|
||||
$keyId = isset($this->Auth->user()['authkey_id']) ? $this->Auth->user()['authkey_id'] : null;
|
||||
} else {
|
||||
$keyId = null;
|
||||
}
|
||||
if (!$this->userRole['perm_auth']) {
|
||||
throw new MethodNotAllowedException(__('Invalid action.'));
|
||||
}
|
||||
$newkey = $this->User->resetauthkey($this->Auth->user(), $id, $alert);
|
||||
$newkey = $this->User->resetauthkey($this->Auth->user(), $id, $alert, $keyId);
|
||||
if ($newkey === false) {
|
||||
throw new MethodNotAllowedException(__('Invalid user.'));
|
||||
}
|
||||
if (!$this->_isRest()) {
|
||||
$this->Flash->success(__('New authkey generated.', true));
|
||||
$this->Flash->success(__('New authkey generated.'));
|
||||
$this->redirect($this->referer());
|
||||
} else {
|
||||
return $this->RestResponse->saveSuccessResponse('User', 'resetauthkey', $id, $this->response->type(), 'Authkey updated: ' . $newkey);
|
||||
|
@ -1693,7 +1690,7 @@ class UsersController extends AppController
|
|||
|
||||
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) {
|
||||
if (!empty($stored_otp) && trim($this->request->data['User']['otp']) == $stored_otp) {
|
||||
// we invalidate the previously generated OTP
|
||||
$redis->del('misp:otp:' . $user_id);
|
||||
// We login the user with CakePHP
|
||||
|
|
|
@ -9,20 +9,20 @@ class WarninglistsController extends AppController
|
|||
public $components = array('Session', 'RequestHandler');
|
||||
|
||||
public $paginate = array(
|
||||
'limit' => 60,
|
||||
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user can view/page.
|
||||
'contain' => array(
|
||||
'WarninglistType'
|
||||
),
|
||||
'order' => array(
|
||||
'Warninglist.id' => 'DESC'
|
||||
),
|
||||
'limit' => 60,
|
||||
'maxLimit' => 9999, // LATER we will bump here on a problem once we have more than 9999 events <- no we won't, this is the max a user can view/page.
|
||||
'contain' => array(
|
||||
'WarninglistType'
|
||||
),
|
||||
'order' => array(
|
||||
'Warninglist.id' => 'DESC'
|
||||
),
|
||||
'recursive' => -1,
|
||||
);
|
||||
|
||||
public function index()
|
||||
{
|
||||
$filters = $this->IndexFilter->harvestParameters(['value']);
|
||||
$this->paginate['recursive'] = -1;
|
||||
$filters = $this->IndexFilter->harvestParameters(['value', 'enabled']);
|
||||
if (!empty($filters['value'])) {
|
||||
$this->paginate['conditions'] = [
|
||||
'OR' => [
|
||||
|
@ -32,20 +32,20 @@ class WarninglistsController extends AppController
|
|||
]
|
||||
];
|
||||
}
|
||||
if (isset($filters['enabled'])) {
|
||||
$this->paginate['conditions'][] = ['Warninglist.enabled' => $filters['enabled']];
|
||||
}
|
||||
$warninglists = $this->paginate();
|
||||
foreach ($warninglists as &$warninglist) {
|
||||
$warninglist['Warninglist']['valid_attributes'] = array();
|
||||
foreach ($warninglist['WarninglistType'] as $type) {
|
||||
$warninglist['Warninglist']['valid_attributes'][] = $type['type'];
|
||||
}
|
||||
$warninglist['Warninglist']['valid_attributes'] = implode(', ', $warninglist['Warninglist']['valid_attributes']);
|
||||
$validAttributes = array_column($warninglist['WarninglistType'], 'type');
|
||||
$warninglist['Warninglist']['valid_attributes'] = implode(', ', $validAttributes);
|
||||
unset($warninglist['WarninglistType']);
|
||||
}
|
||||
if ($this->_isRest()) {
|
||||
$this->set('Warninglists', $warninglists);
|
||||
$this->set('_serialize', array('Warninglists'));
|
||||
return $this->RestResponse->viewData(['Warninglists' => $warninglists], $this->response->type());
|
||||
} else {
|
||||
$this->set('warninglists', $warninglists);
|
||||
$this->set('passedArgsArray', $filters);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,7 +55,7 @@ class CsseCovidTrendsWidget
|
|||
'date' => (empty($options['timeframe']) ? 10 : $options['timeframe']) . 'd'
|
||||
);
|
||||
$eventIds = $this->Event->filterEventIds($user, $params);
|
||||
$eventIds = array_reverse(array_values($eventIds));
|
||||
$eventIds = array_reverse($eventIds);
|
||||
$data = array();
|
||||
if (empty($options['type'])) {
|
||||
$options['type'] = 'confirmed';
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
class EventStreamWidget
|
||||
{
|
||||
public $title = 'Event Stream';
|
||||
public $render = 'Index';
|
||||
public $width = 4;
|
||||
public $height = 2;
|
||||
public $params = [
|
||||
'tags' => 'A list of tagnames to filter on. Comma separated list, prepend each tag with an exclamation mark to negate it.',
|
||||
'orgs' => 'A list of organisation names to filter on. Comma separated list, prepend each tag with an exclamation mark to negate it.',
|
||||
'published' => 'Boolean flag to filter on published events only',
|
||||
'limit' => 'How many events should be listed? Defaults to 5',
|
||||
'fields' => 'A list of fields that should be displayed. Valid fields: id, orgc, info, tags, threat_level, analysis, date. Default field selection ["id", "orgc", "info"]'
|
||||
];
|
||||
public $description = 'Monitor incoming events based on your own filters.';
|
||||
public $cacheLifetime = false;
|
||||
public $autoRefreshDelay = 5;
|
||||
private $__default_fields = ['id', 'orgc', 'info'];
|
||||
|
||||
public function handler($user, $options = array())
|
||||
{
|
||||
$this->Event = ClassRegistry::init('Event');
|
||||
$params = [
|
||||
'metadata' => 1,
|
||||
'limit' => 5,
|
||||
'page' => 1,
|
||||
'order' => 'Event.id DESC'
|
||||
];
|
||||
$field_options = [
|
||||
'id' => [
|
||||
'name' => '#',
|
||||
'url' => Configure::read('MISP.baseurl') . '/events/view',
|
||||
'element' => 'links',
|
||||
'data_path' => 'Event.id',
|
||||
'url_params_data_paths' => 'Event.id'
|
||||
],
|
||||
'orgc' => [
|
||||
'name' => 'Org',
|
||||
'data_path' => 'Orgc',
|
||||
'element' => 'org'
|
||||
],
|
||||
'info' => [
|
||||
'name' => 'Info',
|
||||
'data_path' => 'Event.info',
|
||||
],
|
||||
'tags' => [
|
||||
'name' => 'Tags',
|
||||
'data_path' => 'EventTag',
|
||||
'element' => 'tags',
|
||||
'scope' => 'feeds'
|
||||
],
|
||||
'threat_level' => [
|
||||
'name' => 'Threat Level',
|
||||
'data_path' => 'ThreatLevel.name'
|
||||
],
|
||||
'analysis' => [
|
||||
'name' => 'Analysis',
|
||||
'data_path' => 'Event.analysis',
|
||||
'element' => 'array_lookup_field',
|
||||
'arrayData' => [__('Initial'), __('Ongoing'), __('Complete')]
|
||||
],
|
||||
'date' => [
|
||||
'name' => 'Date',
|
||||
'data_path' => 'Event.date'
|
||||
],
|
||||
];
|
||||
$fields = [];
|
||||
if (empty($options['fields'])) {
|
||||
$options['fields'] = $this->__default_fields;
|
||||
}
|
||||
foreach ($options['fields'] as $field) {
|
||||
if (!empty($field_options[$field])) {
|
||||
$fields[] = $field_options[$field];
|
||||
}
|
||||
}
|
||||
foreach (['published', 'limit', 'tags', 'orgs'] as $field) {
|
||||
if (!empty($options[$field])) {
|
||||
$params[$field] = $options[$field];
|
||||
}
|
||||
}
|
||||
$data = $this->Event->fetchEvent($user, $params);
|
||||
return [
|
||||
'data' => $data,
|
||||
'fields' => $fields
|
||||
];
|
||||
}
|
||||
}
|
|
@ -7,41 +7,43 @@ class MispSystemResourceWidget
|
|||
public $width = 3;
|
||||
public $height = 3;
|
||||
public $params = array(
|
||||
'treshold' => 'Treshold for disk space'
|
||||
'threshold' => 'Threshold for disk space'
|
||||
);
|
||||
public $description = 'Basic widget showing some system server statistics.';
|
||||
public $cacheLifetime = false;
|
||||
public $autoRefreshDelay = 30;
|
||||
public $placeholder =
|
||||
'{
|
||||
"treshold": "85"
|
||||
"threshold": "85"
|
||||
}';
|
||||
|
||||
public function handler($user, $options = array())
|
||||
public function handler(array $user, $options = array())
|
||||
{
|
||||
// Keep BC with typo value
|
||||
$threshold = isset($options['threshold']) ? $options['threshold'] : (isset($options['treshold']) ? $options['treshold'] : 85);
|
||||
|
||||
$drive = round((1 - disk_free_space(getcwd())/disk_total_space(getcwd()))*100,2);
|
||||
$cwd = getcwd();
|
||||
$drive = round((1 - disk_free_space($cwd)/disk_total_space($cwd))*100,2);
|
||||
$driveFree = $drive . "%";
|
||||
$driveFreeClass = "";
|
||||
if ($drive > intval($options['treshold'])) {
|
||||
$driveFree = $drive . "% - [Above Treshhold]";
|
||||
if ($drive > intval($threshold)) {
|
||||
$driveFree = $drive . "% - [Above Threshold]";
|
||||
$driveFreeClass = "red";
|
||||
}
|
||||
|
||||
$sysload = sys_getloadavg();
|
||||
|
||||
preg_match('#MemFree:[\s\t]+([\d]+)\s+kB#', file_get_contents('/proc/meminfo'), $matches);
|
||||
$meminfo = file_get_contents('/proc/meminfo');
|
||||
preg_match('#MemFree:[\s\t]+([\d]+)\s+kB#', $meminfo, $matches);
|
||||
$memoryFree = $matches[1];
|
||||
preg_match('#MemTotal:[\s\t]+([\d]+)\s+kB#', file_get_contents('/proc/meminfo'), $matches);
|
||||
preg_match('#MemTotal:[\s\t]+([\d]+)\s+kB#', $meminfo, $matches);
|
||||
$memoryTotal = $matches[1];
|
||||
|
||||
$data = array(
|
||||
array( 'title' => __('User'), 'value' => $user['email']),
|
||||
array( 'title' => __('System'), 'value' => php_uname()),
|
||||
array( 'title' => __('Disk usage'), 'value' => h($driveFree), 'class' => $driveFreeClass),
|
||||
array( 'title' => __('Load'), 'value' => h($sysload[0] . " - " . $sysload[1] . " - " . $sysload[2])),
|
||||
array( 'title' => __('Memory'), 'value' => h(round($memoryFree/1024,2) . "M free (" . round((1 - $memoryFree/$memoryTotal)*100,2) . "% used)")),
|
||||
);
|
||||
array( 'title' => __('User'), 'value' => $user['email']),
|
||||
array( 'title' => __('System'), 'value' => php_uname()),
|
||||
array( 'title' => __('Disk usage'), 'value' => h($driveFree), 'class' => $driveFreeClass),
|
||||
array( 'title' => __('Load'), 'value' => h(implode(" - ", sys_getloadavg()))),
|
||||
array( 'title' => __('Memory'), 'value' => h(round($memoryFree / 1024,2) . " MB free (" . round((1 - $memoryFree/$memoryTotal)*100,2) . " % used)")),
|
||||
);
|
||||
return $data;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,36 +20,41 @@ class TrendingTagsWidget
|
|||
"include": ["misp-galaxy:", "my-internal-taxonomy"]
|
||||
}';
|
||||
public $description = 'Widget showing the trending tags over the past x seconds, along with the possibility to include/exclude tags.';
|
||||
public $cacheLifetime = 600;
|
||||
|
||||
public function handler($user, $options = array())
|
||||
{
|
||||
$this->Event = ClassRegistry::init('Event');
|
||||
$params = array(
|
||||
'metadata' => 1,
|
||||
'timestamp' => time() - (empty($options['time_window']) ? 8640000 : $options['time_window'])
|
||||
);
|
||||
/** @var Event $eventModel */
|
||||
$eventModel = ClassRegistry::init('Event');
|
||||
$threshold = empty($options['threshold']) ? 10 : $options['threshold'];
|
||||
$eventIds = $this->Event->filterEventIds($user, $params);
|
||||
$params['eventid'] = $eventIds;
|
||||
$events = array();
|
||||
$params = [
|
||||
'timestamp' => time() - (empty($options['time_window']) ? 8640000 : $options['time_window']),
|
||||
];
|
||||
$eventIds = $eventModel->filterEventIds($user, $params);
|
||||
|
||||
$tags = [];
|
||||
$tagColours = [];
|
||||
if (!empty($eventIds)) {
|
||||
$events = $this->Event->fetchEvent($user, $params);
|
||||
}
|
||||
$tags = array();
|
||||
$tagColours = array();
|
||||
foreach ($events as $event) {
|
||||
foreach ($event['EventTag'] as $et) {
|
||||
if ($this->checkTag($options, $et['Tag']['name'])) {
|
||||
if (empty($tags[$et['Tag']['name']])) {
|
||||
$tags[$et['Tag']['name']] = 1;
|
||||
$tagColours[$et['Tag']['name']] = $et['Tag']['colour'];
|
||||
} else {
|
||||
$tags[$et['Tag']['name']] += 1;
|
||||
}
|
||||
$eventTags = $eventModel->EventTag->find('all', [
|
||||
'conditions' => ['EventTag.event_id' => $eventIds],
|
||||
'contain' => ['Tag' => ['fields' => ['name', 'colour']]],
|
||||
'recursive' => -1,
|
||||
'fields' => ['id'],
|
||||
]);
|
||||
|
||||
foreach ($eventTags as $eventTag) {
|
||||
$tagName = $eventTag['Tag']['name'];
|
||||
if (isset($tags[$tagName])) {
|
||||
$tags[$tagName]++;
|
||||
} else if ($this->checkTag($options, $tagName)) {
|
||||
$tags[$tagName] = 1;
|
||||
$tagColours[$tagName] = $eventTag['Tag']['colour'];
|
||||
}
|
||||
}
|
||||
|
||||
arsort($tags);
|
||||
}
|
||||
arsort($tags);
|
||||
|
||||
$data['data'] = array_slice($tags, 0, $threshold);
|
||||
$data['colours'] = $tagColours;
|
||||
return $data;
|
||||
|
|
|
@ -6,6 +6,7 @@ class CsvExport
|
|||
public $default_fields = array('uuid', 'event_id', 'category', 'type', 'value', 'comment', 'to_ids', 'timestamp', 'object_relation', 'attribute_tag');
|
||||
public $default_obj_fields = array('object_uuid', 'object_name', 'object_meta-category');
|
||||
public $requested_fields = array();
|
||||
public $decaying_fields = array('decay_score_score', 'decay_score_decayed');
|
||||
public $non_restrictive_export = true;
|
||||
|
||||
public function handler($data, $options = array())
|
||||
|
@ -22,6 +23,9 @@ class CsvExport
|
|||
|
||||
public function modify_params($user, $params)
|
||||
{
|
||||
if (!empty($params['includeDecayScore'])) {
|
||||
$this->enable_decaying();
|
||||
}
|
||||
if (empty($params['contain'])) {
|
||||
$params['contain'] = array();
|
||||
}
|
||||
|
@ -36,6 +40,11 @@ class CsvExport
|
|||
return $params;
|
||||
}
|
||||
|
||||
public function enable_decaying()
|
||||
{
|
||||
$this->default_fields = array_merge($this->default_fields, $this->decaying_fields);
|
||||
}
|
||||
|
||||
private function __attributesHandler($attribute, $options)
|
||||
{
|
||||
$attribute = $this->__addMetadataToAttributeAtomic($attribute);
|
||||
|
@ -44,6 +53,17 @@ class CsvExport
|
|||
$attribute['object_name'] = $attribute['Object']['name'];
|
||||
$attribute['object_meta-category'] = $attribute['Object']['meta-category'];
|
||||
}
|
||||
if (!empty($attribute['decay_score'])) {
|
||||
$all_scores = Hash::extract($attribute, 'decay_score.{n}.score');
|
||||
$all_decayed = Hash::extract($attribute, 'decay_score.{n}.decayed');
|
||||
$avg_score = array_sum($all_scores)/count($all_scores);
|
||||
$avg_decayed = count(array_intersect([true], $all_decayed)) > 0;
|
||||
$attribute['decay_score_score'] = $avg_score;
|
||||
$attribute['decay_score_decayed'] = $avg_decayed;
|
||||
} else {
|
||||
$attribute['decay_score_score'] = 0;
|
||||
$attribute['decay_score_decayed'] = false;
|
||||
}
|
||||
return $this->__addLine($attribute, $options);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
class CidrTool
|
||||
{
|
||||
/** @var array */
|
||||
private $ipv4 = [];
|
||||
|
||||
/**
|
||||
* Minimum netmask for IPv4 in list. 33 because maximum netmask is 32..
|
||||
* @var int
|
||||
*/
|
||||
private $minimumIpv4Mask = 33;
|
||||
|
||||
/** @var array */
|
||||
private $ipv6 = [];
|
||||
|
||||
public function __construct(array $list)
|
||||
{
|
||||
$this->filterInputList($list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $value IPv4 or IPv6 address or range
|
||||
* @return false|string
|
||||
*/
|
||||
public function contains($value)
|
||||
{
|
||||
$valueMask = null;
|
||||
if (strpos($value, '/') !== false) {
|
||||
list($value, $valueMask) = explode('/', $value);
|
||||
}
|
||||
|
||||
$match = false;
|
||||
if (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
// This code converts IP address to all possible CIDRs that can contains given IP address
|
||||
// and then check if given hash table contains that CIDR.
|
||||
$ip = ip2long($value);
|
||||
// Start from 1, because doesn't make sense to check 0.0.0.0/0 match
|
||||
for ($bits = $this->minimumIpv4Mask; $bits <= 32; $bits++) {
|
||||
$mask = -1 << (32 - $bits);
|
||||
$needle = long2ip($ip & $mask) . "/$bits";
|
||||
if (isset($this->ipv4[$needle])) {
|
||||
$match = $needle;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} elseif (filter_var($value, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
|
||||
$value = unpack('n*', inet_pton($value));
|
||||
foreach ($this->ipv6 as $netmask => $lv) {
|
||||
foreach ($lv as $l) {
|
||||
if ($this->ipv6InCidr($value, $l, $netmask)) {
|
||||
$match = inet_ntop($l) . "/$netmask";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($match && $valueMask) {
|
||||
$matchMask = explode('/', $match)[1];
|
||||
if ($valueMask < $matchMask) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return $match;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cidr
|
||||
* @return bool
|
||||
*/
|
||||
public static function validate($cidr)
|
||||
{
|
||||
$parts = explode('/', $cidr, 2);
|
||||
$ipBytes = inet_pton($parts[0]);
|
||||
if ($ipBytes === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$maximumNetmask = strlen($ipBytes) === 4 ? 32 : 128;
|
||||
if (isset($parts[1]) && ($parts[1] > $maximumNetmask || $parts[1] < 0)) {
|
||||
return false; // Netmask part of CIDR is invalid
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Using solution from https://github.com/symfony/symfony/blob/master/src/Symfony/Component/HttpFoundation/IpUtils.php
|
||||
*
|
||||
* @param array $ip
|
||||
* @param string $cidr
|
||||
* @param int $netmask
|
||||
* @return bool
|
||||
*/
|
||||
private function ipv6InCidr($ip, $cidr, $netmask)
|
||||
{
|
||||
$bytesAddr = unpack('n*', $cidr);
|
||||
for ($i = 1, $ceil = ceil($netmask / 16); $i <= $ceil; ++$i) {
|
||||
$left = $netmask - 16 * ($i - 1);
|
||||
$left = ($left <= 16) ? $left : 16;
|
||||
$mask = ~(0xffff >> $left) & 0xffff;
|
||||
if (($bytesAddr[$i] & $mask) != ($ip[$i] & $mask)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter out invalid IPv4 or IPv4 CIDR and append maximum netmask if no netmask is given.
|
||||
* @param array $list
|
||||
*/
|
||||
private function filterInputList(array $list)
|
||||
{
|
||||
foreach ($list as $v) {
|
||||
$parts = explode('/', $v, 2);
|
||||
$ipBytes = inet_pton($parts[0]);
|
||||
if ($ipBytes === false) {
|
||||
continue; // IP address part of CIDR is invalid
|
||||
}
|
||||
$maximumNetmask = strlen($ipBytes) === 4 ? 32 : 128;
|
||||
|
||||
if (isset($parts[1]) && ($parts[1] > $maximumNetmask || $parts[1] < 0)) {
|
||||
// Netmask part of CIDR is invalid
|
||||
continue;
|
||||
}
|
||||
|
||||
$mask = isset($parts[1]) ? $parts[1] : $maximumNetmask;
|
||||
if ($maximumNetmask === 32) {
|
||||
if ($mask < $this->minimumIpv4Mask) {
|
||||
$this->minimumIpv4Mask = (int)$mask;
|
||||
}
|
||||
if (!isset($parts[1])) {
|
||||
$v = "$v/$maximumNetmask"; // If CIDR doesnt contains '/', we will consider CIDR as /32
|
||||
}
|
||||
$this->ipv4[$v] = true;
|
||||
} else {
|
||||
$this->ipv6[$mask][] = $ipBytes;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,14 @@
|
|||
<?php
|
||||
class ClusterRelationsGraphTool
|
||||
{
|
||||
private $GalaxyCluster = false;
|
||||
private $user = false;
|
||||
private $GalaxyCluster;
|
||||
private $user;
|
||||
private $lookup = array();
|
||||
|
||||
public function construct($user, $GalaxyCluster)
|
||||
public function __construct(array $user, GalaxyCluster $GalaxyCluster)
|
||||
{
|
||||
$this->GalaxyCluster = $GalaxyCluster;
|
||||
$this->user = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -75,7 +74,7 @@
|
|||
'conditions' => array(
|
||||
'referenced_galaxy_cluster_uuid' => $cluster['GalaxyCluster']['uuid']
|
||||
),
|
||||
'contain' => array('Org', 'Orgc', 'SharingGroup'),
|
||||
'contain' => array('SharingGroup'),
|
||||
));
|
||||
if (!empty($referencingRelations)) {
|
||||
foreach ($referencingRelations as $relation) {
|
||||
|
|
|
@ -66,7 +66,11 @@ class ColourPaletteTool
|
|||
// pass the element's id from the list along to get a colour for a single item
|
||||
public function generatePaletteFromString($string, $items, $onlySpecific = false)
|
||||
{
|
||||
$hue = $this->__stringToNumber($string);
|
||||
if (Validation::uuid($string)) {
|
||||
$hue = $this->__uuidToNumber($string);
|
||||
} else {
|
||||
$hue = $this->__stringToNumber($string);
|
||||
}
|
||||
$saturation = 1;
|
||||
$steps = 80 / $items;
|
||||
$results = array();
|
||||
|
@ -91,4 +95,12 @@ class ColourPaletteTool
|
|||
}
|
||||
return $number % 100 / 100;
|
||||
}
|
||||
|
||||
private function __uuidToNumber($string)
|
||||
{
|
||||
$part = explode('-', $string)[4];
|
||||
$number = hexdec($part);
|
||||
$max = hexdec('ffffffffffff');
|
||||
return round($number / $max, 2);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,15 +10,10 @@ class ComplexTypeTool
|
|||
'types' => array('link', 'url')
|
||||
),
|
||||
array(
|
||||
'from' => '/(\[\.\]|\[dot\]|\(dot\)|\\\\\.)/',
|
||||
'from' => '/(\[\.\]|\[dot\]|\(dot\))/',
|
||||
'to' => '.',
|
||||
'types' => array('link', 'url', 'ip-dst', 'ip-src', 'domain|ip', 'domain', 'hostname')
|
||||
),
|
||||
array(
|
||||
'from' => '/\.+/',
|
||||
'to' => '.',
|
||||
'types' => array('ip-dst', 'ip-src', 'domain|ip', 'domain', 'hostname')
|
||||
),
|
||||
array(
|
||||
'from' => '/\[hxxp:\/\/\]/',
|
||||
'to' => 'http://',
|
||||
|
@ -41,7 +36,7 @@ class ComplexTypeTool
|
|||
public static function refangValue($value, $type)
|
||||
{
|
||||
foreach (self::$__refangRegexTable as $regex) {
|
||||
if (in_array($type, $regex['types'])) {
|
||||
if (in_array($type, $regex['types'], true)) {
|
||||
$value = preg_replace($regex['from'], $regex['to'], $value);
|
||||
}
|
||||
}
|
||||
|
@ -131,16 +126,6 @@ class ComplexTypeTool
|
|||
return array('type' => 'other', 'value' => $input);
|
||||
}
|
||||
|
||||
private function __returnOddElements($array)
|
||||
{
|
||||
foreach ($array as $k => $v) {
|
||||
if ($k % 2 != 1) {
|
||||
unset($array[$k]);
|
||||
}
|
||||
}
|
||||
return array_values($array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a CSV file with the given settings
|
||||
* All lines starting with # are stripped
|
||||
|
@ -203,23 +188,21 @@ class ComplexTypeTool
|
|||
|
||||
public function checkFreeText($input, $settings = array())
|
||||
{
|
||||
$charactersToTrim = '\'",() ' . "\t\n\r\0\x0B"; // custom + default PHP trim
|
||||
$charactersToTrim = '\'".,() ' . "\t\n\r\0\x0B"; // custom + default PHP trim
|
||||
$input = str_replace("\xc2\xa0", ' ', $input); // non breaking space to normal space
|
||||
$input = preg_replace('/\p{C}+/u', ' ', $input);
|
||||
$iocArray = preg_split("/\r\n|\n|\r|\s|\s+|,|\<|\>|;/", $input);
|
||||
$quotedText = explode('"', $input);
|
||||
foreach ($quotedText as $k => $temp) {
|
||||
$temp = trim($temp);
|
||||
if (empty($temp)) {
|
||||
unset($quotedText[$k]);
|
||||
} else {
|
||||
$quotedText[$k] = $temp;
|
||||
}
|
||||
}
|
||||
$iocArray = array_merge($iocArray, $this->__returnOddElements($quotedText));
|
||||
$resultArray = array();
|
||||
|
||||
preg_match_all('/\"([^\"]*)\"/', $input, $matches);
|
||||
foreach ($matches[1] as $match) {
|
||||
if ($match !== '') {
|
||||
$iocArray[] = $match;
|
||||
}
|
||||
}
|
||||
|
||||
$resultArray = [];
|
||||
foreach ($iocArray as $ioc) {
|
||||
$ioc = str_replace("\xc2\xa0", '', $ioc); // remove non breaking space
|
||||
$ioc = trim($ioc, $charactersToTrim);
|
||||
$ioc = preg_replace('/\p{C}+/u', '', $ioc);
|
||||
if (empty($ioc)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -258,6 +241,16 @@ class ComplexTypeTool
|
|||
*/
|
||||
private function __resolveType($raw_input)
|
||||
{
|
||||
// Check if value is clean IP without doing expensive operations.
|
||||
if (filter_var($raw_input, FILTER_VALIDATE_IP)) {
|
||||
return [
|
||||
'types' => ['ip-dst', 'ip-src', 'ip-src/ip-dst'],
|
||||
'to_ids' => true,
|
||||
'default_type' => 'ip-dst',
|
||||
'value' => $raw_input,
|
||||
];
|
||||
}
|
||||
|
||||
$input = array('raw' => $raw_input);
|
||||
|
||||
// Check hashes before refang and port extracting, it is not necessary for hashes. This speedups parsing
|
||||
|
|
|
@ -4,13 +4,14 @@
|
|||
private $__lookupTables = array();
|
||||
private $__related_events = array();
|
||||
private $__related_attributes = array();
|
||||
private $__eventModel = false;
|
||||
/** @var Event */
|
||||
private $__eventModel;
|
||||
private $__taxonomyModel = false;
|
||||
private $__galaxyClusterModel = false;
|
||||
private $__user = false;
|
||||
private $__json = array();
|
||||
|
||||
public function construct($eventModel, $taxonomyModel, $galaxyClusterModel, $user, $json)
|
||||
public function construct(Event $eventModel, $taxonomyModel, $galaxyClusterModel, $user, $json)
|
||||
{
|
||||
$this->__eventModel = $eventModel;
|
||||
$this->__taxonomyModel = $taxonomyModel;
|
||||
|
@ -99,11 +100,11 @@
|
|||
{
|
||||
foreach ($objects as $k => $object) {
|
||||
$include = $full;
|
||||
if (!$include) {
|
||||
if (!$include && isset($object['Attribute'])) {
|
||||
foreach ($object['Attribute'] as $attribute) {
|
||||
if (isset($this->__related_attributes[$attribute['id']])) {
|
||||
$include = true;
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -116,6 +116,7 @@ class DistributionGraphTool
|
|||
'noShadowAttributes' => true,
|
||||
'noEventReports' => true,
|
||||
'noSightings' => true,
|
||||
'excludeGalaxy' => true,
|
||||
'includeEventCorrelations' => false,
|
||||
'extended' => $this->__extended_view,
|
||||
));
|
||||
|
|
|
@ -40,6 +40,9 @@ class ElasticSearchClient
|
|||
// Format timestamp
|
||||
$time = strftime("%Y-%m-%d %H:%M:%S", strtotime($document["Log"]["created"]));
|
||||
$document["Log"]["created"] = $time;
|
||||
if (empty($document["Log"]["ip"])) {
|
||||
$document["Log"]["ip"] = null;
|
||||
}
|
||||
$params = array(
|
||||
'index' => $index,
|
||||
'type' => $document_type,
|
||||
|
|
|
@ -21,12 +21,16 @@
|
|||
'fields' => array('Tag.id', 'Tag.name'),
|
||||
'sort' => array('lower(Tag.name) asc'),
|
||||
));
|
||||
$this->__extendedEventUUIDMapping = array();
|
||||
$this->__extended_view = $extended_view;
|
||||
$this->__lookupTables = array(
|
||||
'analysisLevels' => $this->__eventModel->analysisLevels,
|
||||
'distributionLevels' => $this->__eventModel->Attribute->distributionLevels
|
||||
);
|
||||
$this->__authorized_JSON_key = array('event_id', 'distribution', 'category', 'type', 'value', 'comment', 'uuid', 'to_ids', 'timestamp', 'id');
|
||||
|
||||
App::uses('ColourPaletteTool', 'Tools');
|
||||
$this->__paletteTool = new ColourPaletteTool();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -77,6 +81,11 @@
|
|||
if (!($check1 && $check2)) {
|
||||
unset($event['Object'][$i]);
|
||||
}
|
||||
foreach($obj['ObjectReference'] as $j => $rel) {
|
||||
if ($rel['deleted']) {
|
||||
unset($event['Object'][$i]['ObjectReference'][$j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($event['Attribute'] as $i => $attr) {
|
||||
$check1 = $this->__satisfy_val_filtering($attr, false);
|
||||
|
@ -261,8 +270,10 @@
|
|||
'node_type' => 'attribute',
|
||||
);
|
||||
array_push($this->__json['items'], $toPush);
|
||||
$this->__extendedEventUUIDMapping[$toPush['event_id']] = '';
|
||||
}
|
||||
|
||||
$templatesCount = [];
|
||||
foreach ($object as $obj) {
|
||||
$toPush = array(
|
||||
'id' => sprintf('o-%s', $obj['id']),
|
||||
|
@ -283,8 +294,13 @@
|
|||
$this->__json['existing_object_relation'][$attr['object_relation']] = 0; // set-alike
|
||||
}
|
||||
}
|
||||
if (empty($templatesCount[$obj['template_uuid']])) {
|
||||
$templatesCount[$obj['template_uuid']] = 0;
|
||||
}
|
||||
$templatesCount[$obj['template_uuid']]++;
|
||||
|
||||
array_push($this->__json['items'], $toPush);
|
||||
$this->__extendedEventUUIDMapping[$toPush['event_id']] = '';
|
||||
|
||||
foreach ($obj['ObjectReference'] as $rel) {
|
||||
$toPush = array(
|
||||
|
@ -299,6 +315,12 @@
|
|||
array_push($this->__json['relations'], $toPush);
|
||||
}
|
||||
}
|
||||
$this->__json['items'] = $this->addObjectColors($this->__json['items'], $templatesCount);
|
||||
|
||||
if ($this->__extended_view) {
|
||||
$this->fetchEventUUIDFromId();
|
||||
$this->__json['extended_event_uuid_mapping'] = $this->__extendedEventUUIDMapping;
|
||||
}
|
||||
|
||||
return $this->__json;
|
||||
}
|
||||
|
@ -519,7 +541,7 @@
|
|||
public function get_reference_data($uuid)
|
||||
{
|
||||
$objectReference = $this->__refModel->ObjectReference->find('all', array(
|
||||
'conditions' => array('ObjectReference.uuid' => $uuid),
|
||||
'conditions' => array('ObjectReference.uuid' => $uuid, 'ObjectReference.deleted' => false),
|
||||
'recursive' => -1,
|
||||
//'fields' => array('ObjectReference.id', 'relationship_type', 'comment', 'referenced_uuid')
|
||||
));
|
||||
|
@ -542,4 +564,27 @@
|
|||
}
|
||||
return $templates;
|
||||
}
|
||||
|
||||
public function fetchEventUUIDFromId()
|
||||
{
|
||||
$eventUUIDs = $this->__eventModel->find('list', [
|
||||
'conditions' => ['id' => array_keys($this->__extendedEventUUIDMapping)],
|
||||
'fields' => ['uuid']
|
||||
]);
|
||||
$this->__extendedEventUUIDMapping = $eventUUIDs;
|
||||
}
|
||||
|
||||
private function addObjectColors($items, $templatesCount)
|
||||
{
|
||||
$colours = [];
|
||||
foreach ($templatesCount as $templateUUID => $count) {
|
||||
$colours[$templateUUID] = $this->__paletteTool->generatePaletteFromString($templateUUID, $count);
|
||||
}
|
||||
foreach ($items as $i => $item) {
|
||||
if ($item['node_type'] == 'object') {
|
||||
$items[$i]['color'] = array_shift($colours[$item['template_uuid']]);
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,10 @@ class HttpSocketResponseExtended extends HttpSocketResponse
|
|||
{
|
||||
parent::parseResponse($message);
|
||||
|
||||
if ($this->body === '') {
|
||||
return; // skip decoding body if is empty
|
||||
}
|
||||
|
||||
$contentEncoding = $this->getHeader('Content-Encoding');
|
||||
if ($contentEncoding === 'gzip' && function_exists('gzdecode')) {
|
||||
$this->body = gzdecode($this->body);
|
||||
|
@ -107,7 +111,7 @@ class HttpSocketExtended extends HttpSocket
|
|||
}
|
||||
// Convert connection timeout to SocketException
|
||||
if (!empty($this->lastError)) {
|
||||
throw new SocketException($this->lastError['msg']);
|
||||
throw new SocketException($this->lastError['str']);
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
|
|
@ -105,18 +105,19 @@ class JSONConverterTool
|
|||
}
|
||||
|
||||
/**
|
||||
* Event to JSON stream convertor.
|
||||
* Event to JSON convertor, but that is intended for machine to machine communication
|
||||
* @param array $event
|
||||
* @return Generator<string>
|
||||
*/
|
||||
public function streamConvert(array $event)
|
||||
{
|
||||
$event = $this->convert($event, false, true);
|
||||
|
||||
// Fast and inaccurate way how to check if event is too big for to convert in one call. This can be changed in future.
|
||||
$isBigEvent = (isset($event['Event']['Attribute']) ? count($event['Event']['Attribute']) : 0) +
|
||||
(isset($event['Event']['Object']) ? count($event['Event']['Object']) : 0) > 100;
|
||||
if (!$isBigEvent) {
|
||||
yield json_encode($event, JSON_PRETTY_PRINT);
|
||||
yield json_encode($event, JSON_UNESCAPED_UNICODE);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -127,11 +128,11 @@ class JSONConverterTool
|
|||
yield ($firstKey === $key ? '' : ',') . json_encode($key) . ":[";
|
||||
$firstInnerKey = key($value);
|
||||
foreach ($value as $i => $attribute) {
|
||||
yield ($firstInnerKey === $i ? '' : ',') . json_encode($attribute);
|
||||
yield ($firstInnerKey === $i ? '' : ',') . json_encode($attribute, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
yield "]";
|
||||
} else {
|
||||
yield ($firstKey === $key ? '' : ',') . json_encode($key) . ":" . json_encode($value);
|
||||
yield ($firstKey === $key ? '' : ',') . json_encode($key) . ":" . json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
}
|
||||
if (isset($event['errors'])) {
|
||||
|
|
|
@ -273,6 +273,7 @@ class PubSubTool
|
|||
'redis_password' => '',
|
||||
'redis_database' => '1',
|
||||
'redis_namespace' => 'mispq',
|
||||
'host' => '127.0.0.1',
|
||||
'port' => '50000',
|
||||
'username' => null,
|
||||
'password' => null,
|
||||
|
|
|
@ -0,0 +1,522 @@
|
|||
<?php
|
||||
App::uses('SyncTool', 'Tools');
|
||||
|
||||
class SecurityAudit
|
||||
{
|
||||
const STRONG_PASSWORD_LENGTH = 17;
|
||||
|
||||
/**
|
||||
* @param Server $server
|
||||
* @return array
|
||||
*/
|
||||
public function run(Server $server)
|
||||
{
|
||||
$output = [];
|
||||
|
||||
foreach (['config.php', 'database.php', 'email.php'] as $configFile) {
|
||||
$perms = fileperms(CONFIG . $configFile);
|
||||
if ($perms & 0x0004) {
|
||||
$output['File permissions'][] = ['error', __('%s config file is readable for any user.', $configFile)];
|
||||
}
|
||||
}
|
||||
|
||||
$redisPassword = Configure::read('MISP.redis_password');
|
||||
if (empty($redisPassword)) {
|
||||
$output['Redis'][] = ['error', __('Redis password not set.')];
|
||||
} else if (strlen($redisPassword) < 32) { // for Redis, password should be stronger
|
||||
$output['Redis'][] = [
|
||||
'warning',
|
||||
__('Redis password is too short, should be at least 32 chars long.'),
|
||||
'https://redis.io/topics/security#authentication-feature',
|
||||
];
|
||||
}
|
||||
|
||||
$databasePassword = ConnectionManager::getDataSource('default')->config['password'];
|
||||
if (empty($databasePassword)) {
|
||||
$output['Database'][] = ['error', __('Database password not set.')];
|
||||
} else if (strlen($databasePassword) < self::STRONG_PASSWORD_LENGTH) {
|
||||
$output['Database'][] = ['warning', __('Database password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)];
|
||||
}
|
||||
|
||||
$passwordPolicyLength = Configure::read('Security.password_policy_length') ?: $server->serverSettings['Security']['password_policy_length']['value'];
|
||||
if ($passwordPolicyLength < 8) {
|
||||
$output['Password'][] = ['error', __('Minimum password length is set to %s, it is highly advised to increase it.', $passwordPolicyLength)];
|
||||
} elseif ($passwordPolicyLength < 12) {
|
||||
$output['Password'][] = ['warning', __('Minimum password length is set to %s, consider raising to at least 12 characters.', $passwordPolicyLength)];
|
||||
}
|
||||
|
||||
if (empty(Configure::read('Security.require_password_confirmation'))) {
|
||||
$output['Password'][] = [
|
||||
'warning',
|
||||
__('Password confirmation is not enabled. %s', $server->serverSettings['Security']['require_password_confirmation']['description']),
|
||||
];
|
||||
}
|
||||
if (!empty(Configure::read('Security.auth')) && !Configure::read('Security.auth_enforced')) {
|
||||
$output['Login'][] = [
|
||||
'hint',
|
||||
__('External authentication is enabled, but local accounts will still work. You can disable the ability to log in via local accounts by setting `Security.auth_enforced` to `true`.'),
|
||||
];
|
||||
}
|
||||
|
||||
if (empty(Configure::read('Security.disable_browser_cache'))) {
|
||||
$output['Browser'][] = [
|
||||
'warning',
|
||||
__('Browser cache is enabled. An attacker could obtain sensitive data from the user cache. You can disable the cache by setting `Security.disable_browser_cache` to `true`.'),
|
||||
];
|
||||
}
|
||||
if (empty(Configure::read('Security.check_sec_fetch_site_header'))) {
|
||||
$output['Browser'][] = [
|
||||
'warning',
|
||||
__('The MISP server is not checking `Sec-Fetch` HTTP headers. This is a protection mechanism against CSRF used by modern browsers. You can enable this check by setting `Security.check_sec_fetch_site_header` to `true`.'),
|
||||
];
|
||||
}
|
||||
if (empty(Configure::read('Security.csp_enforce'))) {
|
||||
$output['Browser'][] = [
|
||||
'warning',
|
||||
__('Content security policies (CSP) are not enforced. Consider enabling them by setting `Security.csp_enforce` to `true`.'),
|
||||
'https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP',
|
||||
];
|
||||
}
|
||||
if (!env('HTTPS') && strpos(Configure::read('MISP.baseurl'), 'https://') === 0) {
|
||||
$output['Browser'][] = [
|
||||
'error',
|
||||
__('MISP base URL is set to https://, but MISP thinks that the connection is insecure. This usually happens when a server is running behind a reverse proxy. By setting `Security.force_https` to `true`, session cookies will be set as Secure and CSP headers will upgrade insecure requests.'),
|
||||
];
|
||||
}
|
||||
$sessionConfig = Configure::read('Session');
|
||||
if (isset($sessionConfig['ini']['session.cookie_secure']) && !$sessionConfig['ini']['session.cookie_secure']) {
|
||||
$output['Browser'][] = ['error', __('Setting session cookies as not secure is never a good idea.')];
|
||||
}
|
||||
|
||||
if (empty(Configure::read('Security.advanced_authkeys'))) {
|
||||
$output['Auth Key'][] = ['warning', __('Consider enabling Advanced Auth Keys, they provide increased security by only storing the API key hashes.')];
|
||||
}
|
||||
if (Configure::read('Security.allow_unsafe_apikey_named_param')) {
|
||||
$output['Auth Key'][] = ['error', __('It is possible to pass API keys via the URL, meaning that the keys can be logged by proxies.')];
|
||||
}
|
||||
if (empty(Configure::read('Security.do_not_log_authkeys'))) {
|
||||
$output['Auth Key'][] = ['warning', __('Auth Key logging is not disabled. Auth Keys in cleartext can be visible in the Audit log.')];
|
||||
}
|
||||
|
||||
$salt = Configure::read('Security.salt');
|
||||
if (empty($salt)) {
|
||||
$output['Security salt'][] = ['error', __('Salt is not set.')];
|
||||
} else if (strlen($salt) < 32) {
|
||||
$output['Security salt'][] = ['warning', __('Salt is too short, should contain at least 32 characters.')];
|
||||
} else if ($salt === "Rooraenietu8Eeyo<Qu2eeNfterd-dd+") {
|
||||
$output['Security salt'][] = ['error', __('Salt is set to the default value.')];
|
||||
}
|
||||
|
||||
if (empty(Configure::read('MISP.log_client_ip'))) {
|
||||
$output['Logging'][] = ['warning', __('Logging client IP in audit log is disabled. Logging IP address can help to solve potential security breaches.')];
|
||||
}
|
||||
if (empty(Configure::read('MISP.log_user_ips'))) {
|
||||
$output['Logging'][] = ['warning', __('Logging client IP in Redis is disabled. Logging IP addresses can help investigate potential security breaches.')];
|
||||
}
|
||||
if (Configure::read('MISP.log_user_ips') && Configure::read('Security.advanced_authkeys') && empty(Configure::read('MISP.log_user_ips_authkeys'))) {
|
||||
$output['Logging'][] = [
|
||||
'hint',
|
||||
__('You can enable the logging of advanced authkeys by setting `MISP.log_user_ips_authkeys` to `true`.'),
|
||||
];
|
||||
}
|
||||
if (empty(Configure::read('Security.username_in_response_header'))) {
|
||||
$output['Logging'][] = [
|
||||
'hint',
|
||||
__('Passing user information to response headers is disabled. This can be useful for logging user info at the reverse proxy level. You can enable it by setting `Security.username_in_response_header` to `true`.'),
|
||||
];
|
||||
}
|
||||
|
||||
if (empty(Configure::read('MISP.attachment_scan_module'))) {
|
||||
$output['Attachment scanning'][] = ['hint', __('No module for scanning attachments for viruses is currently defined.')];
|
||||
}
|
||||
|
||||
if (Configure::read('debug')) {
|
||||
$output['Debug'][] = ['error', __('Debug mode is enabled for all users.')];
|
||||
}
|
||||
|
||||
if (Configure::read('Proxy.host')) {
|
||||
$proxyPassword = Configure::read('Proxy.password');
|
||||
if (empty($proxyPassword)) {
|
||||
$output['Proxy'][] = ['error', __('Proxy password is empty.')];
|
||||
} else if (strlen($proxyPassword) < self::STRONG_PASSWORD_LENGTH) {
|
||||
$output['Proxy'][] = ['warning', __('Proxy password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)];
|
||||
}
|
||||
}
|
||||
|
||||
if (Configure::read('Security.rest_client_enable_arbitrary_urls')) {
|
||||
$output['REST client'][] = [
|
||||
'hint',
|
||||
__('Users can use the REST client to query any remote URL. This is generally not a good idea if your instance is public.')
|
||||
];
|
||||
}
|
||||
|
||||
if (Configure::read('Plugins.ZeroMQ_enable')) {
|
||||
$zeroMqPassword = Configure::read('Plugins.ZeroMQ_password');
|
||||
if (empty($zeroMqPassword)) {
|
||||
$output['ZeroMQ'][] = ['error', __('ZeroMQ password is not set.')];
|
||||
} else if (strlen($zeroMqPassword) < self::STRONG_PASSWORD_LENGTH) {
|
||||
$output['ZeroMQ'][] = ['warning', __('ZeroMQ password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)];
|
||||
}
|
||||
|
||||
$redisPassword = Configure::read('Plugins.ZeroMQ_redis_password');
|
||||
if (empty($redisPassword)) {
|
||||
$output['ZeroMQ'][] = ['error', __('Redis password is not set.')];
|
||||
} else if (strlen($redisPassword) < 32) { // for Redis, password should be stronger
|
||||
$output['ZeroMQ'][] = [
|
||||
'warning',
|
||||
__('Redis password is too short, should be at least 32 chars long.'),
|
||||
'https://redis.io/topics/security#authentication-feature',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$this->email($output);
|
||||
|
||||
/*
|
||||
* These settings are dangerous and break both the transparency and potential introduce sync issues
|
||||
if (!Configure::read('Security.hide_organisation_index_from_users')) {
|
||||
$output['MISP'][] = [
|
||||
'hint',
|
||||
__('Any user can see list of all organisations. You can disable that by setting `Security.hide_organisation_index_from_users` to `true`. %s', $server->serverSettings['Security']['hide_organisation_index_from_users']['description']),
|
||||
];
|
||||
}
|
||||
if (!Configure::read('Security.hide_organisations_in_sharing_groups')) {
|
||||
$output['MISP'][] = [
|
||||
'hint',
|
||||
__('Any user can see list of all organisations in sharing group that user can see. You can disable that by setting `Security.hide_organisations_in_sharing_groups` to `true`. %s', $server->serverSettings['Security']['hide_organisations_in_sharing_groups']['description']),
|
||||
];
|
||||
}
|
||||
*/
|
||||
|
||||
$this->feeds($output);
|
||||
$this->remoteServers($output);
|
||||
|
||||
try {
|
||||
$cakeVersion = $this->getCakeVersion();
|
||||
if (version_compare($cakeVersion, '2.10.21', '<')) {
|
||||
$output['Dependencies'][] = ['warning', __('CakePHP version %s is outdated.', $cakeVersion)];
|
||||
}
|
||||
} catch (RuntimeException $e) {}
|
||||
|
||||
if (version_compare(PHP_VERSION, '7.3.0', '<')) {
|
||||
$output['PHP'][] = [
|
||||
'warning',
|
||||
__('PHP version %s is not supported anymore. It can be still supported by your distribution. ', PHP_VERSION),
|
||||
'https://www.php.net/supported-versions.php'
|
||||
];
|
||||
} else if (version_compare(PHP_VERSION, '7.4.0', '<')) {
|
||||
$output['PHP'][] = [
|
||||
'hint',
|
||||
__('PHP version 7.3 will not be supported after 6 Dec 2021. Even beyond that date, it can be still supported by your distribution.'),
|
||||
'https://www.php.net/supported-versions.php'
|
||||
];
|
||||
}
|
||||
if (ini_get('session.use_strict_mode') != 1) {
|
||||
$output['PHP'][] = [
|
||||
'warning',
|
||||
__('Session strict mode is disabled.'),
|
||||
'https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode',
|
||||
];
|
||||
}
|
||||
if (empty(ini_get('session.cookie_httponly'))) {
|
||||
$output['PHP'][] = ['error', __('Session cookie is not set as HTTP only. Session cookie can be accessed from JavaScript.')];
|
||||
}
|
||||
if (!in_array(strtolower(ini_get('session.cookie_samesite')), ['strict', 'lax'])) {
|
||||
$output['PHP'][] = [
|
||||
'error',
|
||||
__('Session cookie SameSite parameter is not defined or set to None.'),
|
||||
'https://developer.mozilla.org/en-us/docs/Web/HTTP/Headers/Set-Cookie/SameSite',
|
||||
];
|
||||
}
|
||||
$sidLength = ini_get('session.sid_length');
|
||||
if ($sidLength !== false && $sidLength < 32) {
|
||||
$output['PHP'][] = [
|
||||
'warning',
|
||||
__('Session ID length is set to %s, at least 32 is recommended.', $sidLength),
|
||||
'https://www.php.net/manual/en/session.configuration.php#ini.session.sid-length',
|
||||
];
|
||||
}
|
||||
$sidBits = ini_get('session.sid_bits_per_character');
|
||||
if ($sidBits !== false && $sidBits <= 4) {
|
||||
$output['PHP'][] = [
|
||||
'warning',
|
||||
__('Session ID bit per character is set to %s, at least 5 is recommended.', $sidBits),
|
||||
'https://www.php.net/manual/en/session.configuration.php#ini.session.sid-bits-per-character',
|
||||
];
|
||||
}
|
||||
|
||||
$this->system($server, $output);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function feeds(array &$output)
|
||||
{
|
||||
/** @var Feed $feed */
|
||||
$feed = ClassRegistry::init('Feed');
|
||||
$enabledFeeds = $feed->find('list', [
|
||||
'conditions' => [
|
||||
'input_source' => 'network',
|
||||
'OR' => [
|
||||
'enabled' => true,
|
||||
'caching_enabled' => true,
|
||||
]
|
||||
],
|
||||
'fields' => ['name', 'url'],
|
||||
]);
|
||||
foreach ($enabledFeeds as $feedName => $feedUrl) {
|
||||
if (substr($feedUrl, 0, strlen('http://')) === 'http://') {
|
||||
$output['Feeds'][] = ['warning', __('Feed %s uses insecure (HTTP) connection.', $feedName)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function remoteServers(array &$output)
|
||||
{
|
||||
/** @var Server $server */
|
||||
$server = ClassRegistry::init('Server');
|
||||
$enabledServers = $server->find('all', [
|
||||
'conditions' => ['OR' => [
|
||||
'push' => true,
|
||||
'pull' => true,
|
||||
'push_sightings' => true,
|
||||
'caching_enabled' => true,
|
||||
]],
|
||||
'fields' => ['id', 'name', 'url', 'self_signed', 'cert_file', 'client_cert_file'],
|
||||
]);
|
||||
foreach ($enabledServers as $enabledServer) {
|
||||
if (substr($enabledServer['Server']['url'], 0, strlen('http://')) === 'http://') {
|
||||
$output['Remote servers'][] = ['warning', __('Server %s uses insecure (HTTP) connection.', $enabledServer['Server']['name'])];
|
||||
} else if ($enabledServer['Server']['self_signed']) {
|
||||
$output['Remote servers'][] = ['warning', __('Server %s uses self signed certificate. This is considered insecure.', $enabledServer['Server']['name'])];
|
||||
}
|
||||
|
||||
try {
|
||||
$parsed = SyncTool::getServerClientCertificateInfo($enabledServer);
|
||||
if (isset($parsed['public_key_size_ok']) && !$parsed['public_key_size_ok']) {
|
||||
$algo = $parsed['public_key_type'] . " " . $parsed['public_key_size'];
|
||||
$output['Remote servers'][] = ['warning', __('Server %s uses weak client certificate (%s).', $enabledServer['Server']['name'], $algo)];
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
|
||||
try {
|
||||
$parsed = SyncTool::getServerCaCertificateInfo($enabledServer);
|
||||
if (isset($parsed['public_key_size_ok']) && !$parsed['public_key_size_ok']) {
|
||||
$algo = $parsed['public_key_type'] . " " . $parsed['public_key_size'];
|
||||
$output['Remote servers'][] = ['warning', __('Server %s uses weak CA certificate (%s).', $enabledServer['Server']['name'], $algo)];
|
||||
}
|
||||
} catch (Exception $e) {}
|
||||
}
|
||||
}
|
||||
|
||||
private function email(array &$output)
|
||||
{
|
||||
$canSignPgp = Configure::read('GnuPG.sign');
|
||||
$canSignSmime = Configure::read('SMIME.enabled') &&
|
||||
!empty(Configure::read('SMIME.cert_public_sign')) &&
|
||||
!empty(Configure::read('SMIME.key_sign'));
|
||||
|
||||
if (!$canSignPgp && !$canSignSmime) {
|
||||
$output['Email'][] = [
|
||||
'warning',
|
||||
__('Email signing (PGP or S/MIME) is not enabled.')
|
||||
];
|
||||
}
|
||||
|
||||
if ($canSignPgp) {
|
||||
$gpgKeyPassword = Configure::read('GnuPG.password');
|
||||
if (empty($gpgKeyPassword)) {
|
||||
$output['Email'][] = ['error', __('PGP private key password is empty.')];
|
||||
} else if (strlen($gpgKeyPassword) < self::STRONG_PASSWORD_LENGTH) {
|
||||
$output['Email'][] = ['warning', __('PGP private key password is too short, should be at least %s chars long.', self::STRONG_PASSWORD_LENGTH)];
|
||||
}
|
||||
}
|
||||
|
||||
if (!Configure::read('GnuPG.bodyonlyencrypted')) {
|
||||
$output['Email'][] = [
|
||||
'hint',
|
||||
__('Full email body with all event information will be sent, even without encryption.')
|
||||
];
|
||||
}
|
||||
|
||||
if ($canSignPgp && !Configure::read('GnuPG.obscure_subject')) {
|
||||
$output['Email'][] = [
|
||||
'hint',
|
||||
__('Even for encrypted emails, the email subject will be sent unencrypted. You can change that behaviour by setting `GnuPG.obscure_subject` to `true`.'),
|
||||
];
|
||||
}
|
||||
|
||||
App::uses('CakeEmail', 'Network/Email');
|
||||
$email = new CakeEmail();
|
||||
$emailConfig = $email->config();
|
||||
if ($emailConfig['transport'] === 'Smtp' && $emailConfig['port'] == 25 && !$emailConfig['tls']) {
|
||||
$output['Email'][] = [
|
||||
'warning',
|
||||
__('STARTTLS is not enabled.'),
|
||||
'https://en.wikipedia.org/wiki/Opportunistic_TLS',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
private function system(Server $server, array &$output)
|
||||
{
|
||||
$kernelBuildTime = $this->getKernelBuild();
|
||||
if ($kernelBuildTime) {
|
||||
$diff = (new DateTime())->diff($kernelBuildTime);
|
||||
$diffDays = $diff->format('a');
|
||||
if ($diffDays > 300) {
|
||||
$output['System'][] = [
|
||||
'warning',
|
||||
__('Kernel build time was s days ago. This usually means that the system kernel is not updated.', $diffDays),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// uptime
|
||||
try {
|
||||
$since = $this->execute(['uptime', '-s']);
|
||||
$since = new DateTime($since);
|
||||
$diff = (new DateTime())->diff($since);
|
||||
$diffDays = $diff->format('a');
|
||||
if ($diffDays > 100) {
|
||||
$output['System'][] = [
|
||||
'warning',
|
||||
__('Uptime of this server is %s days. This usually means that the system kernel is outdated.', $diffDays),
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
// Python version
|
||||
try {
|
||||
$pythonVersion = $this->execute([$server->getPythonVersion(), '-V']);
|
||||
$parts = explode(' ', $pythonVersion);
|
||||
if ($parts[0] !== 'Python') {
|
||||
throw new Exception("Invalid python version response: $pythonVersion");
|
||||
}
|
||||
|
||||
if (version_compare($parts[1], '3.6', '<')) {
|
||||
$output['System'][] = [
|
||||
'warning',
|
||||
__('You are using Python %s. This version is not supported anymore, but it can be still supported by your distribution.', $parts[1]),
|
||||
'https://endoflife.date/python',
|
||||
];
|
||||
} else if (version_compare($parts[1], '3.7', '<')) {
|
||||
$output['System'][] = [
|
||||
'hint',
|
||||
__('You are using Python %s. This version will not be supported beyond 23 Dec 2021, but it can be that it is still supported by your distribution.', $parts[1]),
|
||||
'https://endoflife.date/python',
|
||||
];
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
}
|
||||
|
||||
$ubuntuVersion = $this->getUbuntuVersion();
|
||||
if ($ubuntuVersion) {
|
||||
if (in_array($ubuntuVersion, ['14.04', '19.10'])) {
|
||||
$output['System'][] = [
|
||||
'warning',
|
||||
__('You are using Ubuntu %s. This version doesn\'t receive security support anymore.', $ubuntuVersion),
|
||||
'https://endoflife.date/ubuntu',
|
||||
];
|
||||
} else if (in_array($ubuntuVersion, ['16.04'])) {
|
||||
$output['System'][] = [
|
||||
'hint',
|
||||
__('You are using Ubuntu %s. This version will be not supported after 02 Apr 2021.', $ubuntuVersion),
|
||||
'https://endoflife.date/ubuntu',
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getKernelBuild()
|
||||
{
|
||||
if (!php_uname('s') !== 'Linux') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$version = php_uname('v');
|
||||
if (substr($version, 0, 7) !== '#1 SMP ') {
|
||||
return false;
|
||||
}
|
||||
$buildTime = substr($version, 7);
|
||||
return new DateTime($buildTime);
|
||||
}
|
||||
|
||||
private function getUbuntuVersion()
|
||||
{
|
||||
if (!php_uname('s') !== 'Linux') {
|
||||
return false;
|
||||
}
|
||||
if (!is_readable('/etc/os-release')) {
|
||||
return false;
|
||||
}
|
||||
$content = file_get_contents('/etc/os-release');
|
||||
if ($content === false) {
|
||||
return false;
|
||||
}
|
||||
$parsed = parse_ini_string($content);
|
||||
if ($parsed === false) {
|
||||
return false;
|
||||
}
|
||||
if (!isset($parsed['NAME'])) {
|
||||
return false;
|
||||
}
|
||||
if ($parsed['NAME'] !== 'Ubuntu') {
|
||||
return false;
|
||||
}
|
||||
if (!isset($parsed['VERSION_ID'])) {
|
||||
return false;
|
||||
}
|
||||
return $parsed['VERSION_ID'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getCakeVersion()
|
||||
{
|
||||
$filePath = APP . 'Lib/cakephp/lib/Cake/VERSION.txt';
|
||||
$version = file_get_contents($filePath);
|
||||
if (!$version) {
|
||||
throw new RuntimeException("Could not open CakePHP version file '$filePath'.");
|
||||
}
|
||||
foreach (explode("\n", $version) as $line) {
|
||||
if ($line[0] === '/') {
|
||||
continue;
|
||||
}
|
||||
return trim($line);
|
||||
}
|
||||
throw new RuntimeException("CakePHP version not found in file '$filePath'.");
|
||||
}
|
||||
|
||||
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 not 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;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,36 @@ App::uses('CakeEmail', 'Network/Email');
|
|||
|
||||
class SendEmailException extends Exception {}
|
||||
|
||||
class CakeEmailBody
|
||||
{
|
||||
/** @var string|null */
|
||||
public $html;
|
||||
|
||||
/** @var string|null */
|
||||
public $text;
|
||||
|
||||
public function __construct($text = null, $html = null)
|
||||
{
|
||||
$this->html = $html;
|
||||
$this->text = $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function format()
|
||||
{
|
||||
if (!empty($this->html) && !empty($this->text)) {
|
||||
return 'both';
|
||||
}
|
||||
|
||||
if (!empty($this->html)) {
|
||||
return 'html';
|
||||
}
|
||||
return 'text';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class CakeEmailExtended
|
||||
*
|
||||
|
@ -14,7 +44,7 @@ class SendEmailException extends Exception {}
|
|||
class CakeEmailExtended extends CakeEmail
|
||||
{
|
||||
/**
|
||||
* @var MimeMultipart|MessagePart
|
||||
* @var MimeMultipart|MessagePart|CakeEmailBody
|
||||
*/
|
||||
private $body;
|
||||
|
||||
|
@ -30,7 +60,7 @@ class CakeEmailExtended extends CakeEmail
|
|||
$headers['Content-Type'] = $this->body->getContentType();
|
||||
} else if ($this->body instanceof MessagePart) {
|
||||
$headers = array_merge($headers, $this->body->getHeaders());
|
||||
} else {
|
||||
} else if ($this->_emailFormat !== 'both') { // generate correct content-type header for 'text' or 'html' format
|
||||
$headers['Content-Type'] = 'multipart/mixed; boundary="' . $this->boundary() . '"';
|
||||
}
|
||||
|
||||
|
@ -71,18 +101,40 @@ class CakeEmailExtended extends CakeEmail
|
|||
return $this->body->render();
|
||||
} else if ($this->body instanceof MessagePart) {
|
||||
return $this->body->render(false);
|
||||
} else if ($this->body instanceof CakeEmailBody) {
|
||||
return $this->_render([]); // @see _renderTemplates
|
||||
}
|
||||
|
||||
return $this->_render($this->_wrap($this->body));
|
||||
throw new InvalidArgumentException("Expected that body is instance of MimeMultipart, MessagePart or CakeEmailBody, " . gettype($this->body) . " given.");
|
||||
}
|
||||
|
||||
// This is hack how to force CakeEmail to always generate multipart message.
|
||||
protected function _renderTemplates($content)
|
||||
{
|
||||
if (!$this->body instanceof CakeEmailBody) {
|
||||
throw new InvalidArgumentException("Expected instance of CakeEmailBody, " . gettype($this->body) . " given.");
|
||||
}
|
||||
|
||||
$this->_boundary = md5(uniqid());
|
||||
$output = parent::_renderTemplates($content);
|
||||
$output[''] = '';
|
||||
return $output;
|
||||
|
||||
$rendered = [];
|
||||
if (!empty($this->body->text)) {
|
||||
$rendered['text'] = $this->body->text;
|
||||
}
|
||||
if (!empty($this->body->html)) {
|
||||
$rendered['html'] = $this->body->html;
|
||||
}
|
||||
|
||||
foreach ($rendered as $type => $content) {
|
||||
$content = str_replace(array("\r\n", "\r"), "\n", $content);
|
||||
$content = $this->_encodeString($content, $this->charset);
|
||||
$content = $this->_wrap($content);
|
||||
$content = implode("\n", $content);
|
||||
$rendered[$type] = rtrim($content, "\n");
|
||||
}
|
||||
|
||||
// This is hack how to force CakeEmail to always generate multipart message.
|
||||
$rendered[''] = '';
|
||||
return $rendered;
|
||||
}
|
||||
|
||||
protected function _render($content)
|
||||
|
@ -101,7 +153,7 @@ class CakeEmailExtended extends CakeEmail
|
|||
if ($content !== null) {
|
||||
throw new InvalidArgumentException("Content must be null for CakeEmailExtended.");
|
||||
}
|
||||
return parent::send($this->body);
|
||||
return parent::send();
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
|
@ -252,11 +304,12 @@ class MessagePart
|
|||
|
||||
class SendEmail
|
||||
{
|
||||
/**
|
||||
* @var CryptGpgExtended
|
||||
*/
|
||||
/** @var CryptGpgExtended */
|
||||
private $gpg;
|
||||
|
||||
/** @var string|null */
|
||||
private $transport;
|
||||
|
||||
/**
|
||||
* @param CryptGpgExtended|null $gpg
|
||||
*/
|
||||
|
@ -271,6 +324,14 @@ class SendEmail
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $transport
|
||||
*/
|
||||
public function setTransport($transport)
|
||||
{
|
||||
$this->transport = $transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $params
|
||||
* @return array|bool
|
||||
|
@ -285,7 +346,8 @@ class SendEmail
|
|||
}
|
||||
}
|
||||
|
||||
$params['body'] = str_replace('\n', PHP_EOL, $params['body']); // TODO: Why this?
|
||||
$body = str_replace('\n', PHP_EOL, $params['body']); // TODO: Why this?
|
||||
$body = new CakeEmailBody($body);
|
||||
|
||||
$attachments = array();
|
||||
if (!empty($params['requestor_gpgkey'])) {
|
||||
|
@ -306,10 +368,14 @@ class SendEmail
|
|||
$email->returnPath(Configure::read('MISP.email'));
|
||||
$email->to($params['to']);
|
||||
$email->subject($params['subject']);
|
||||
$email->emailFormat('text');
|
||||
$email->body($params['body']);
|
||||
$email->emailFormat($body->format());
|
||||
$email->body($body);
|
||||
$email->attachments($attachments);
|
||||
|
||||
if ($this->transport) {
|
||||
$email->transport($this->transport);
|
||||
}
|
||||
|
||||
$mock = false;
|
||||
if (!empty(Configure::read('MISP.disable_emailing')) || !empty($params['mock'])) {
|
||||
$email->transport('Debug');
|
||||
|
@ -352,16 +418,20 @@ class SendEmail
|
|||
/**
|
||||
* @param array $user
|
||||
* @param string $subject
|
||||
* @param string $body
|
||||
* @param string|null $bodyWithoutEncryption
|
||||
* @param SendEmailTemplate|string $body
|
||||
* @param string|false $bodyWithoutEncryption
|
||||
* @param array $replyToUser
|
||||
* @return bool True if e-mail is encrypted, false if not.
|
||||
* @return array
|
||||
* @throws Crypt_GPG_BadPassphraseException
|
||||
* @throws Crypt_GPG_Exception
|
||||
* @throws SendEmailException
|
||||
*/
|
||||
public function sendToUser(array $user, $subject, $body, $bodyWithoutEncryption = null, array $replyToUser = array())
|
||||
public function sendToUser(array $user, $subject, $body, $bodyWithoutEncryption = false, array $replyToUser = array())
|
||||
{
|
||||
if ($body instanceof SendEmailTemplate && $bodyWithoutEncryption !== false) {
|
||||
throw new InvalidArgumentException("When body is instance of SendEmailTemplate, \$bodyWithoutEncryption must be false.");
|
||||
}
|
||||
|
||||
if (Configure::read('MISP.disable_emailing')) {
|
||||
throw new SendEmailException('Emailing is currently disabled on this instance.');
|
||||
}
|
||||
|
@ -378,14 +448,36 @@ class SendEmail
|
|||
throw new SendEmailException('Encrypted messages are enforced and the message could not be encrypted for this user as no valid encryption key was found.');
|
||||
}
|
||||
|
||||
// If 'bodyonlyencrypted' is enabled and the user has no encryption key, use the alternate body (if it exists)
|
||||
if (Configure::read('GnuPG.bodyonlyencrypted') && !$canEncryptSmime && !$canEncryptGpg && $bodyWithoutEncryption) {
|
||||
$body = $bodyWithoutEncryption;
|
||||
// If 'GnuPG.bodyonlyencrypted' is enabled and the user has no encryption key, use the alternate body
|
||||
$hideDetails = Configure::read('GnuPG.bodyonlyencrypted') && !$canEncryptSmime && !$canEncryptGpg;
|
||||
|
||||
if ($body instanceof SendEmailTemplate) {
|
||||
$body->set('canEncryptSmime', $canEncryptSmime);
|
||||
$body->set('canEncryptGpg', $canEncryptGpg);
|
||||
$bodyContent = $body->render($hideDetails);
|
||||
$subject = $body->subject() ?: $subject; // Get generated subject from template
|
||||
} else {
|
||||
if ($hideDetails && $bodyWithoutEncryption) {
|
||||
$body = $bodyWithoutEncryption;
|
||||
}
|
||||
$bodyContent = new CakeEmailBody($body);
|
||||
}
|
||||
|
||||
$body = str_replace('\n', PHP_EOL, $body); // TODO: Why this?
|
||||
$email = $this->create($user, $subject, $bodyContent, [], $replyToUser);
|
||||
|
||||
$email = $this->create($user, $subject, $body, array(), $replyToUser);
|
||||
if ($this->transport) {
|
||||
$email->transport($this->transport);
|
||||
}
|
||||
|
||||
// Generate `In-Reply-To` and `References` headers to group emails
|
||||
if ($body instanceof SendEmailTemplate && $body->referenceId()) {
|
||||
$reference = sha1($body->referenceId() . '|' . Configure::read('MISP.uuid'));
|
||||
$reference = "<$reference@{$email->domain()}>";
|
||||
$email->addHeaders([
|
||||
'In-Reply-To' => $reference,
|
||||
'References' => $reference,
|
||||
]);
|
||||
}
|
||||
|
||||
$signed = false;
|
||||
if (Configure::read('GnuPG.sign')) {
|
||||
|
@ -454,8 +546,11 @@ class SendEmail
|
|||
}
|
||||
|
||||
try {
|
||||
$email->send();
|
||||
return $encrypted;
|
||||
return [
|
||||
'contents' => $email->send(),
|
||||
'encrypted' => $encrypted,
|
||||
'subject' => $subject,
|
||||
];
|
||||
} catch (Exception $e) {
|
||||
throw new SendEmailException('The message could not be sent.', 0, $e);
|
||||
}
|
||||
|
@ -499,15 +594,23 @@ class SendEmail
|
|||
/**
|
||||
* @param array $user User model
|
||||
* @param string $subject
|
||||
* @param string $body
|
||||
* @param CakeEmailBody $body
|
||||
* @param array $attachments
|
||||
* @param array $replyToUser User model
|
||||
* @return CakeEmailExtended
|
||||
*/
|
||||
private function create(array $user, $subject, $body, array $attachments = array(), array $replyToUser = array())
|
||||
private function create(array $user, $subject, CakeEmailBody $body, array $attachments = array(), array $replyToUser = array())
|
||||
{
|
||||
$email = new CakeEmailExtended();
|
||||
|
||||
$fromEmail = Configure::read('MISP.email');
|
||||
|
||||
// Set correct domain when sending email from CLI
|
||||
$fromEmailParts = explode('@', $fromEmail, 2);
|
||||
if (isset($fromEmailParts[1])) {
|
||||
$email->domain($fromEmailParts[1]);
|
||||
}
|
||||
|
||||
// We must generate message ID by own, because CakeEmail returns different message ID for every call of
|
||||
// getHeaders() method.
|
||||
$email->messageId($this->generateMessageId($email));
|
||||
|
@ -530,11 +633,11 @@ class SendEmail
|
|||
$email->replyTo(Configure::read('MISP.email_reply_to'));
|
||||
}
|
||||
|
||||
$email->from(Configure::read('MISP.email'));
|
||||
$email->returnPath(Configure::read('MISP.email')); // TODO?
|
||||
$email->from($fromEmail, Configure::read('MISP.email_from_name'));
|
||||
$email->returnPath($fromEmail); // TODO?
|
||||
$email->to($user['User']['email']);
|
||||
$email->subject($subject);
|
||||
$email->emailFormat('text');
|
||||
$email->emailFormat($body->format());
|
||||
$email->body($body);
|
||||
|
||||
foreach ($attachments as $key => $value) {
|
||||
|
@ -558,14 +661,15 @@ class SendEmail
|
|||
|
||||
$messagePart = new MessagePart();
|
||||
$messagePart->addHeader('Content-Type', array(
|
||||
'multipart/mixed',
|
||||
$email->emailFormat() === 'both' ? 'multipart/alternative' : 'multipart/mixed',
|
||||
'boundary="' . $email->boundary() . '"',
|
||||
'protected-headers="v1"',
|
||||
));
|
||||
|
||||
// Protect User-Facing Headers according to https://tools.ietf.org/id/draft-autocrypt-lamps-protected-headers-01.html
|
||||
// Protect User-Facing Headers and Structural Headers according to
|
||||
// https://tools.ietf.org/id/draft-autocrypt-lamps-protected-headers-02.html
|
||||
$originalHeaders = $email->getHeaders(array('subject', 'from', 'to'));
|
||||
$protectedHeaders = array('From', 'To', 'Date', 'Message-ID', 'Subject', 'Reply-To');
|
||||
$protectedHeaders = ['From', 'To', 'Date', 'Message-ID', 'Subject', 'Reply-To', 'In-Reply-To', 'References'];
|
||||
foreach ($protectedHeaders as $header) {
|
||||
if (isset($originalHeaders[$header])) {
|
||||
$messagePart->addHeader($header, $originalHeaders[$header]);
|
||||
|
@ -654,7 +758,7 @@ class SendEmail
|
|||
|
||||
$messagePart = new MessagePart();
|
||||
$messagePart->addHeader('Content-Type', array(
|
||||
'multipart/mixed',
|
||||
$email->emailFormat() === 'both' ? 'multipart/alternative' : 'multipart/mixed',
|
||||
'boundary="' . $email->boundary() . '"',
|
||||
));
|
||||
$messagePart->setPayload($renderedEmail);
|
||||
|
@ -729,14 +833,14 @@ class SendEmail
|
|||
}
|
||||
|
||||
list($inputFile, $outputFile) = $this->createInputOutputFiles($body);
|
||||
$result = openssl_pkcs7_sign($inputFile->pwd(), $outputFile->pwd(), $certPublicSign, $keySign, array(), 0);
|
||||
$result = openssl_pkcs7_sign($inputFile->pwd(), $outputFile->pwd(), $certPublicSign, $keySign, array(), PKCS7_DETACHED);
|
||||
$inputFile->delete();
|
||||
|
||||
if ($result) {
|
||||
$data = $outputFile->read();
|
||||
$outputFile->delete();
|
||||
$parts = explode("\n\n", $data);
|
||||
return $parts[1] . "\n";
|
||||
return $parts[4] . "\n";
|
||||
|
||||
} else {
|
||||
$outputFile->delete();
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
class SendEmailTemplate
|
||||
{
|
||||
/** @var array */
|
||||
private $viewVars = [];
|
||||
|
||||
/** @var string */
|
||||
private $viewName;
|
||||
|
||||
/** @var string|null */
|
||||
private $referenceId;
|
||||
|
||||
/** @var string|null */
|
||||
private $subject;
|
||||
|
||||
public function __construct($viewName)
|
||||
{
|
||||
$this->viewName = $viewName;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value will be used for grouping emails in mail client.
|
||||
* @param string|null $referenceId
|
||||
* @return string
|
||||
*/
|
||||
public function referenceId($referenceId = null)
|
||||
{
|
||||
if ($referenceId === null) {
|
||||
return $this->referenceId;
|
||||
}
|
||||
$this->referenceId = $referenceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subject from template. Must be called after render method.
|
||||
* @param string|null $subject
|
||||
* @return string
|
||||
*/
|
||||
public function subject($subject = null)
|
||||
{
|
||||
if ($subject === null) {
|
||||
return $this->subject;
|
||||
}
|
||||
$this->subject = $subject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set template variable.
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function set($key, $value)
|
||||
{
|
||||
$this->viewVars[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $hideDetails True when GnuPG.bodyonlyencrypted is enabled and e-mail cannot be send in encrypted form
|
||||
* @return CakeEmailBody
|
||||
* @throws CakeException
|
||||
*/
|
||||
public function render($hideDetails = false)
|
||||
{
|
||||
$View = new View();
|
||||
$View->autoLayout = false;
|
||||
$View->helpers = ['TextColour'];
|
||||
$View->loadHelpers();
|
||||
|
||||
$View->set($this->viewVars);
|
||||
$View->set('hideDetails', $hideDetails);
|
||||
|
||||
$View->viewPath = $View->layoutPath = 'Emails' . DS . 'html';
|
||||
try {
|
||||
$html = $View->render($this->viewName);
|
||||
} catch (MissingViewException $e) {
|
||||
$html = null; // HTMl template is optional
|
||||
}
|
||||
|
||||
$View->viewPath = $View->layoutPath = 'Emails' . DS . 'text';
|
||||
$View->hasRendered = false;
|
||||
$text = $View->render($this->viewName);
|
||||
|
||||
// Template can change default subject.
|
||||
$this->subject = $View->get('subject');
|
||||
|
||||
return new CakeEmailBody($text, $html);
|
||||
}
|
||||
}
|
|
@ -92,6 +92,35 @@ class SyncTool
|
|||
return self::getClientCertificateInfo($certificateContent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $server
|
||||
* @return array|void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getServerCaCertificateInfo(array $server)
|
||||
{
|
||||
if (!$server['Server']['cert_file']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$caCertificate = new File(APP . "files" . DS . "certs" . DS . $server['Server']['id'] . '.pem');
|
||||
if (!$caCertificate->exists()) {
|
||||
throw new Exception("Certificate file '{$caCertificate->pwd()}' doesn't exists.");
|
||||
}
|
||||
|
||||
$certificateContent = $caCertificate->read();
|
||||
if ($certificateContent === false) {
|
||||
throw new Exception("Could not read '{$caCertificate->pwd()}' file with certificate.");
|
||||
}
|
||||
|
||||
$certificate = openssl_x509_read($certificateContent);
|
||||
if (!$certificate) {
|
||||
throw new Exception("Couldn't read certificate: " . openssl_error_string());
|
||||
}
|
||||
|
||||
return self::parseCertificate($certificate);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $certificateContent PEM encoded certificate and private key.
|
||||
* @return array
|
||||
|
@ -101,11 +130,11 @@ class SyncTool
|
|||
{
|
||||
$certificate = openssl_x509_read($certificateContent);
|
||||
if (!$certificate) {
|
||||
throw new Exception("Could't parse certificate: " . openssl_error_string());
|
||||
throw new Exception("Couldn't read certificate: " . openssl_error_string());
|
||||
}
|
||||
$privateKey = openssl_pkey_get_private($certificateContent);
|
||||
if (!$privateKey) {
|
||||
throw new Exception("Could't get private key from certificate: " . openssl_error_string());
|
||||
throw new Exception("Couldn't get private key from certificate: " . openssl_error_string());
|
||||
}
|
||||
$verify = openssl_x509_check_private_key($certificate, $privateKey);
|
||||
if (!$verify) {
|
||||
|
@ -123,7 +152,7 @@ class SyncTool
|
|||
{
|
||||
$parsed = openssl_x509_parse($certificate);
|
||||
if (!$parsed) {
|
||||
throw new Exception("Could't get parse X.509 certificate: " . openssl_error_string());
|
||||
throw new Exception("Couldn't get parse X.509 certificate: " . openssl_error_string());
|
||||
}
|
||||
$currentTime = new DateTime();
|
||||
$output = [
|
||||
|
|
|
@ -14,7 +14,7 @@ class TmpFileTool
|
|||
public function __construct($maxInMemory = null)
|
||||
{
|
||||
if ($maxInMemory === null) {
|
||||
$maxInMemory = 2 * 1024 * 1024;
|
||||
$maxInMemory = 5 * 1024 * 1024;
|
||||
}
|
||||
$this->tmpfile = fopen("php://temp/maxmemory:$maxInMemory", "w+");
|
||||
if ($this->tmpfile === false) {
|
||||
|
|
|
@ -198,13 +198,18 @@ class XMLConverterTool
|
|||
$field = str_replace($this->__toEscape, $this->__escapeWith, $field);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $input
|
||||
* @param false $mispVersion
|
||||
* @return Generator
|
||||
*/
|
||||
public function frameCollection($input, $mispVersion = false)
|
||||
{
|
||||
$result = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . '<response>' . PHP_EOL;
|
||||
$result .= $input;
|
||||
yield '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL . '<response>' . PHP_EOL;
|
||||
yield $input . PHP_EOL;
|
||||
if ($mispVersion) {
|
||||
$result .= '<xml_version>' . $mispVersion . '</xml_version>';
|
||||
yield '<xml_version>' . $mispVersion . '</xml_version>';
|
||||
}
|
||||
return $result . '</response>' . PHP_EOL;
|
||||
yield '</response>' . PHP_EOL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit d0c51b37422d0d2c99be74045d4439a674259308
|
||||
Subproject commit cf14e6546ec44e3369e3531add11fdb946656280
|
|
@ -13,20 +13,27 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"
|
||||
|
||||
#: View/Errors/error400.ctp:20
|
||||
#: View/Errors/error500.ctp:19
|
||||
#: View/Errors/missing_connection.ctp:4
|
||||
#: View/Errors/error400.ctp:7
|
||||
#: View/Errors/error500.ctp:4
|
||||
#: View/Errors/missing_connection.ctp:5
|
||||
#: View/Errors/missing_datasource_config.ctp:4
|
||||
#: View/Errors/pdo_error.ctp:4
|
||||
msgid "Error"
|
||||
msgstr ""
|
||||
|
||||
#: View/Errors/error400.ctp:22
|
||||
#: View/Errors/error400.ctp:11
|
||||
msgid "The requested address %s was not found on this server."
|
||||
msgstr ""
|
||||
|
||||
#: View/Errors/error500.ctp:20
|
||||
#: View/Errors/missing_connection.ctp:5
|
||||
#: View/Errors/error400.ctp:14
|
||||
msgid "You don't have permission to access %s."
|
||||
msgstr ""
|
||||
|
||||
#: View/Errors/error500.ctp:5
|
||||
msgid "An Internal Error Has Occurred. Please try your action again. If the problem persists, please contact administrator."
|
||||
msgstr ""
|
||||
|
||||
#: View/Errors/missing_connection.ctp:6
|
||||
#: View/Errors/missing_datasource_config.ctp:5
|
||||
#: View/Errors/pdo_error.ctp:5
|
||||
msgid "An Internal Error Has Occurred."
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,7 +1,7 @@
|
|||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: misp\n"
|
||||
"PO-Revision-Date: 2020-04-24 02:08\n"
|
||||
"PO-Revision-Date: 2021-03-26 04:21\n"
|
||||
"Last-Translator: NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: French\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
|
@ -9,8 +9,10 @@ msgstr ""
|
|||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
"X-Crowdin-Project: misp\n"
|
||||
"X-Crowdin-Project-ID: 306440\n"
|
||||
"X-Crowdin-Language: fr\n"
|
||||
"X-Crowdin-File: default.pot\n"
|
||||
"X-Crowdin-File-ID: 1\n"
|
||||
"Language: fr_FR\n"
|
||||
|
||||
#: Console/Command/AdminShell.php:72
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -43,4 +43,21 @@ class AdminSetting extends AppModel
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function updatesDone($blocking = false)
|
||||
{
|
||||
if ($blocking) {
|
||||
$continue = false;
|
||||
while ($continue == false) {
|
||||
$db_version = $this->find('first', array('conditions' => array('setting' => 'db_version')));
|
||||
$continue = empty($this->findUpgrades($db_version['AdminSetting']['value']));
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
$db_version = $this->find('first', array('conditions' => array('setting' => 'db_version')));
|
||||
return empty($this->findUpgrades($db_version['AdminSetting']['value']));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,8 @@ class AppModel extends Model
|
|||
|
||||
public $inserted_ids = array();
|
||||
|
||||
private $__redisConnection = null;
|
||||
/** @var null|Redis */
|
||||
private static $__redisConnection = null;
|
||||
|
||||
private $__profiler = array();
|
||||
|
||||
|
@ -88,7 +89,7 @@ class AppModel extends Model
|
|||
45 => false, 46 => false, 47 => false, 48 => false, 49 => false, 50 => false,
|
||||
51 => false, 52 => false, 53 => false, 54 => false, 55 => false, 56 => false,
|
||||
57 => false, 58 => false, 59 => false, 60 => false, 61 => false, 62 => false,
|
||||
63 => true, 64 => false, 65 => false
|
||||
63 => true, 64 => false, 65 => false, 66 => false, 67 => false,
|
||||
);
|
||||
|
||||
public $advanced_updates_description = array(
|
||||
|
@ -1567,6 +1568,13 @@ class AppModel extends Model
|
|||
INDEX `value` (`value`(255))
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;";
|
||||
break;
|
||||
case 66:
|
||||
$sqlArray[] = "ALTER TABLE `galaxy_clusters` MODIFY COLUMN `tag_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '';";
|
||||
$indexArray[] = ['event_reports', 'event_id'];
|
||||
break;
|
||||
case 67:
|
||||
$sqlArray[] = "ALTER TABLE `auth_keys` ADD `allowed_ips` text DEFAULT NULL;";
|
||||
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;';
|
||||
|
@ -1820,18 +1828,19 @@ class AppModel extends Model
|
|||
}
|
||||
}
|
||||
|
||||
private function __addIndex($table, $field, $length = false)
|
||||
private function __addIndex($table, $field, $length = null, $unique = false)
|
||||
{
|
||||
$dataSourceConfig = ConnectionManager::getDataSource('default')->config;
|
||||
$dataSource = $dataSourceConfig['datasource'];
|
||||
$this->Log = ClassRegistry::init('Log');
|
||||
$index = $unique ? 'UNIQUE INDEX' : 'INDEX';
|
||||
if ($dataSource == 'Database/Postgres') {
|
||||
$addIndex = "CREATE INDEX idx_" . $table . "_" . $field . " ON " . $table . " (" . $field . ");";
|
||||
$addIndex = "CREATE $index idx_" . $table . "_" . $field . " ON " . $table . " (" . $field . ");";
|
||||
} else {
|
||||
if (!$length) {
|
||||
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`);";
|
||||
$addIndex = "ALTER TABLE `" . $table . "` ADD $index `" . $field . "` (`" . $field . "`);";
|
||||
} else {
|
||||
$addIndex = "ALTER TABLE `" . $table . "` ADD INDEX `" . $field . "` (`" . $field . "`(" . $length . "));";
|
||||
$addIndex = "ALTER TABLE `" . $table . "` ADD $index `" . $field . "` (`" . $field . "`(" . $length . "));";
|
||||
}
|
||||
}
|
||||
$result = true;
|
||||
|
@ -1840,7 +1849,7 @@ class AppModel extends Model
|
|||
try {
|
||||
$this->query($addIndex);
|
||||
} catch (Exception $e) {
|
||||
$duplicate = (strpos($e->getMessage(), '1061') !== false);
|
||||
$duplicate = strpos($e->getMessage(), '1061') !== false;
|
||||
$errorMessage = $e->getMessage();
|
||||
$result = false;
|
||||
}
|
||||
|
@ -2003,7 +2012,7 @@ class AppModel extends Model
|
|||
}
|
||||
}
|
||||
$db_version = $db_version[0];
|
||||
$updates = $this->__findUpgrades($db_version['AdminSetting']['value']);
|
||||
$updates = $this->findUpgrades($db_version['AdminSetting']['value']);
|
||||
if ($processId) {
|
||||
$job = $this->Job->find('first', array(
|
||||
'conditions' => array('Job.id' => $processId)
|
||||
|
@ -2348,7 +2357,7 @@ class AppModel extends Model
|
|||
}
|
||||
}
|
||||
|
||||
private function __findUpgrades($db_version)
|
||||
public function findUpgrades($db_version)
|
||||
{
|
||||
$updates = array();
|
||||
if (strpos($db_version, '.')) {
|
||||
|
@ -2477,8 +2486,8 @@ class AppModel extends Model
|
|||
*/
|
||||
public function setupRedisWithException()
|
||||
{
|
||||
if ($this->__redisConnection) {
|
||||
return $this->__redisConnection;
|
||||
if (self::$__redisConnection) {
|
||||
return self::$__redisConnection;
|
||||
}
|
||||
|
||||
if (!class_exists('Redis')) {
|
||||
|
@ -2503,7 +2512,7 @@ class AppModel extends Model
|
|||
throw new Exception("Could not select Redis database $database: {$redis->getLastError()}");
|
||||
}
|
||||
|
||||
$this->__redisConnection = $redis;
|
||||
self::$__redisConnection = $redis;
|
||||
return $redis;
|
||||
}
|
||||
|
||||
|
@ -2718,26 +2727,26 @@ class AppModel extends Model
|
|||
|
||||
/**
|
||||
* @param array $server
|
||||
* @param string $model
|
||||
* @return array[]
|
||||
* @throws JsonException
|
||||
*/
|
||||
protected function setupSyncRequest(array $server, $model = 'Server')
|
||||
{
|
||||
$version = implode('.', $this->checkMISPVersion());
|
||||
$commit = $this->checkMIPSCommit();
|
||||
$request = array(
|
||||
'header' => array(
|
||||
'Authorization' => $server[$model]['authkey'],
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
'MISP-version' => $version,
|
||||
'User-Agent' => 'MISP ' . $version . (empty($commit) ? '' : ' - #' . $commit),
|
||||
)
|
||||
);
|
||||
|
||||
$commit = $this->checkMIPSCommit();
|
||||
if ($commit) {
|
||||
$request['header']['commit'] = $commit;
|
||||
}
|
||||
$request['header']['User-Agent'] = 'MISP ' . $version . (empty($commit) ? '' : ' - #' . $commit);
|
||||
return $request;
|
||||
}
|
||||
|
||||
|
@ -3027,6 +3036,34 @@ class AppModel extends Model
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimised version of CakePHP _findList method when just one or two fields are set from same model
|
||||
* @param string $state
|
||||
* @param array $query
|
||||
* @param array $results
|
||||
* @return array
|
||||
*/
|
||||
protected function _findList($state, $query, $results = [])
|
||||
{
|
||||
if ($state === 'before') {
|
||||
return parent::_findList($state, $query, $results);
|
||||
}
|
||||
|
||||
if (empty($results)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($query['list']['groupPath'] === null) {
|
||||
$keyPath = explode('.', $query['list']['keyPath']);
|
||||
$valuePath = explode('.', $query['list']['valuePath']);
|
||||
if ($keyPath[1] === $valuePath[1]) { // same model
|
||||
return array_column(array_column($results, $keyPath[1]), $valuePath[2], $keyPath[2]);
|
||||
}
|
||||
}
|
||||
|
||||
return parent::_findList($state, $query, $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find method that allows to fetch just one column from database.
|
||||
* @param $state
|
||||
|
|
|
@ -12,6 +12,7 @@ App::uses('ComplexTypeTool', 'Tools');
|
|||
/**
|
||||
* @property Event $Event
|
||||
* @property AttributeTag $AttributeTag
|
||||
* @property Sighting $Sighting
|
||||
* @property-read array $typeDefinitions
|
||||
* @property-read array $categoryDefinitions
|
||||
*/
|
||||
|
@ -62,6 +63,7 @@ class Attribute extends AppModel
|
|||
|
||||
public $shortDist = array(0 => 'Organisation', 1 => 'Community', 2 => 'Connected', 3 => 'All', 4 => ' Sharing Group', 5 => 'Inherit');
|
||||
|
||||
private $exclusions = null;
|
||||
|
||||
public function __construct($id = false, $table = null, $ds = null)
|
||||
{
|
||||
|
@ -202,17 +204,17 @@ class Attribute extends AppModel
|
|||
'stringNotEmpty' => array(
|
||||
'rule' => array('stringNotEmpty')
|
||||
),
|
||||
'validComposite' => array(
|
||||
'rule' => array('validComposite'),
|
||||
'message' => 'Composite type found but the value not in the composite (value1|value2) format.'
|
||||
),
|
||||
'userdefined' => array(
|
||||
'rule' => array('validateAttributeValue'),
|
||||
'message' => 'Value not in the right type/format. Please double check the value or select type "other".'
|
||||
),
|
||||
'uniqueValue' => array(
|
||||
'rule' => array('valueIsUnique'),
|
||||
'message' => 'A similar attribute already exists for this event.'
|
||||
),
|
||||
'validComposite' => array(
|
||||
'rule' => array('validComposite'),
|
||||
'message' => 'Composite type found but the value not in the composite (value1|value2) format.'
|
||||
'rule' => array('valueIsUnique'),
|
||||
'message' => 'A similar attribute already exists for this event.'
|
||||
),
|
||||
'maxTextLength' => array(
|
||||
'rule' => array('maxTextLength')
|
||||
|
@ -602,7 +604,7 @@ class Attribute extends AppModel
|
|||
public function validComposite($fields)
|
||||
{
|
||||
$compositeTypes = $this->getCompositeTypes();
|
||||
if (in_array($this->data['Attribute']['type'], $compositeTypes)) {
|
||||
if (in_array($this->data['Attribute']['type'], $compositeTypes, true)) {
|
||||
if (substr_count($fields['value'], '|') !== 1) {
|
||||
return false;
|
||||
}
|
||||
|
@ -1031,6 +1033,7 @@ class Attribute extends AppModel
|
|||
case 'first-name':
|
||||
case 'middle-name':
|
||||
case 'last-name':
|
||||
case 'full-name':
|
||||
$returnValue = true;
|
||||
break;
|
||||
case 'link':
|
||||
|
@ -1095,6 +1098,8 @@ class Attribute extends AppModel
|
|||
case 'github-repository':
|
||||
case 'github-organisation':
|
||||
case 'twitter-id':
|
||||
case 'dkim':
|
||||
case 'dkim-signature':
|
||||
case 'favicon-mmh3':
|
||||
case 'chrome-extension-id':
|
||||
case 'mobile-application-id':
|
||||
|
@ -1400,6 +1405,7 @@ class Attribute extends AppModel
|
|||
// prepare the conditions
|
||||
$conditions = array(
|
||||
'Attribute.event_id !=' => $attribute['event_id'],
|
||||
'Attribute.deleted !=' => 1,
|
||||
);
|
||||
|
||||
// prevent issues with empty fields
|
||||
|
@ -1575,7 +1581,7 @@ class Attribute extends AppModel
|
|||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
private function resizeImage($data, $maxWidth, $maxHeight)
|
||||
public function resizeImage($data, $maxWidth, $maxHeight)
|
||||
{
|
||||
$image = imagecreatefromstring($data);
|
||||
if ($image === false) {
|
||||
|
@ -1764,15 +1770,12 @@ class Attribute extends AppModel
|
|||
if (!empty($a['value2'])) {
|
||||
$value .= '|' . $a['value2'];
|
||||
}
|
||||
if (empty($this->exclusions)) {
|
||||
if ($this->exclusions === null) {
|
||||
try {
|
||||
$redis = $this->setupRedisWithException();
|
||||
} catch (Exception $e) {
|
||||
$redisFail = true;
|
||||
}
|
||||
if (empty($redisFail)) {
|
||||
$this->Correlation = ClassRegistry::init('Correlation');
|
||||
$this->exclusions = $redis->sMembers('misp:correlation_exclusions');
|
||||
} catch (Exception $e) {
|
||||
$this->exclusions = [];
|
||||
}
|
||||
}
|
||||
foreach ($this->exclusions as $exclusion) {
|
||||
|
@ -1809,11 +1812,11 @@ class Attribute extends AppModel
|
|||
if (!empty($a['disable_correlation']) || Configure::read('MISP.completely_disable_correlation')) {
|
||||
return true;
|
||||
}
|
||||
if ($this->__preventExcludedCorrelations($a)) {
|
||||
// Don't do any correlation if the type is a non correlating type
|
||||
if (in_array($a['type'], $this->nonCorrelatingTypes, true)) {
|
||||
return true;
|
||||
}
|
||||
// Don't do any correlation if the type is a non correlating type
|
||||
if (in_array($a['type'], $this->nonCorrelatingTypes)) {
|
||||
if ($this->__preventExcludedCorrelations($a)) {
|
||||
return true;
|
||||
}
|
||||
if (!$event) {
|
||||
|
@ -2542,7 +2545,6 @@ class Attribute extends AppModel
|
|||
'recursive' => -1, // int
|
||||
'fields' => array('Attribute.id', 'Attribute.event_id', 'Attribute.type', 'Attribute.category', 'Attribute.comment', 'Attribute.to_ids', 'Attribute.value', 'Attribute.value' . $valueField),
|
||||
'contain' => array('Event' => array('fields' => array('Event.id', 'Event.threat_level_id', 'Event.orgc_id', 'Event.uuid'))),
|
||||
'group' => array('Attribute.type', 'Attribute.value' . $valueField), // fields to GROUP BY
|
||||
'enforceWarninglist' => $enforceWarninglist,
|
||||
'flatten' => 1
|
||||
)
|
||||
|
@ -2944,9 +2946,7 @@ class Attribute extends AppModel
|
|||
if (!empty($attribute)) {
|
||||
if (!empty($attribute['AttributeTag'])) {
|
||||
foreach ($attribute['AttributeTag'] as $at) {
|
||||
if ($at['Tag']['exportable']) {
|
||||
$attribute['Attribute']['Tag'][] = $at['Tag'];
|
||||
}
|
||||
$attribute['Attribute']['Tag'][] = $at['Tag'];
|
||||
}
|
||||
}
|
||||
unset($attribute['AttributeTag']);
|
||||
|
@ -3092,7 +3092,7 @@ class Attribute extends AppModel
|
|||
$params['conditions']['AND'][] = $options['conditions'];
|
||||
}
|
||||
if (empty($options['flatten'])) {
|
||||
$params['conditions']['AND'][] = array('(Attribute.object_id + 0)' => 0);
|
||||
$params['conditions']['AND'][] = array('Attribute.object_id' => 0);
|
||||
}
|
||||
if (isset($options['order'])) {
|
||||
$params['order'] = $options['order'];
|
||||
|
@ -3127,7 +3127,7 @@ class Attribute extends AppModel
|
|||
$options['includeEventTags'] = true;
|
||||
}
|
||||
if (!$user['Role']['perm_sync'] || !isset($options['deleted']) || !$options['deleted']) {
|
||||
$params['conditions']['AND']['(Attribute.deleted + 0)'] = 0;
|
||||
$params['conditions']['AND']['Attribute.deleted'] = 0;
|
||||
} else {
|
||||
if ($options['deleted'] === "only") {
|
||||
$options['deleted'] = 1;
|
||||
|
@ -3979,7 +3979,7 @@ class Attribute extends AppModel
|
|||
}
|
||||
}
|
||||
if (!empty($attribute['Sighting'])) {
|
||||
$this->Sighting->captureSighting($attribute['Sighting'], $this->id, $eventId, $user);
|
||||
$this->Sighting->captureSightings($attribute['Sighting'], $this->id, $eventId, $user);
|
||||
}
|
||||
}
|
||||
if (!empty($this->validationErrors)) {
|
||||
|
@ -4102,6 +4102,10 @@ class Attribute extends AppModel
|
|||
));
|
||||
return $this->validationErrors;
|
||||
} else {
|
||||
if (isset($attribute['Sighting']) && !empty($attribute['Sighting'])) {
|
||||
$this->Sighting = ClassRegistry::init('Sighting');
|
||||
$this->Sighting->captureSightings($attribute['Sighting'], $this->id, $eventId, $user);
|
||||
}
|
||||
if ($user['Role']['perm_tagger']) {
|
||||
/*
|
||||
We should uncomment the line below in the future once we have tag soft-delete
|
||||
|
@ -4249,7 +4253,7 @@ class Attribute extends AppModel
|
|||
'tags' => array('function' => 'set_filter_tags', 'pop' => true),
|
||||
'uuid' => array('function' => 'set_filter_uuid'),
|
||||
'deleted' => array('function' => 'set_filter_deleted'),
|
||||
'timestamp' => array('function' => 'set_filter_timestamp'),
|
||||
'timestamp' => array('function' => 'set_filter_timestamp', 'pop' => true),
|
||||
'attribute_timestamp' => array('function' => 'set_filter_timestamp'),
|
||||
'first_seen' => array('function' => 'set_filter_seen'),
|
||||
'last_seen' => array('function' => 'set_filter_seen'),
|
||||
|
@ -4629,7 +4633,7 @@ class Attribute extends AppModel
|
|||
),
|
||||
'Network activity' => array(
|
||||
'desc' => __('Information about network traffic generated by the malware'),
|
||||
'types' => array('ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'port', 'hostname', 'domain', 'domain|ip', 'mac-address', 'mac-eui-64', 'email', 'email-dst', 'email-src', 'eppn', 'url', 'uri', 'user-agent', 'http-method', 'AS', 'snort', 'pattern-in-file', 'filename-pattern','stix2-pattern', 'pattern-in-traffic', 'attachment', 'comment', 'text', 'x509-fingerprint-md5', 'x509-fingerprint-sha1', 'x509-fingerprint-sha256', 'ja3-fingerprint-md5', 'jarm-fingerprint', 'hassh-md5', 'hasshserver-md5', 'other', 'hex', 'cookie', 'hostname|port', 'bro', 'zeek', 'anonymised', 'community-id', 'email-subject', 'favicon-mmh3')
|
||||
'types' => array('ip-src', 'ip-dst', 'ip-dst|port', 'ip-src|port', 'port', 'hostname', 'domain', 'domain|ip', 'mac-address', 'mac-eui-64', 'email', 'email-dst', 'email-src', 'eppn', 'url', 'uri', 'user-agent', 'http-method', 'AS', 'snort', 'pattern-in-file', 'filename-pattern','stix2-pattern', 'pattern-in-traffic', 'attachment', 'comment', 'text', 'x509-fingerprint-md5', 'x509-fingerprint-sha1', 'x509-fingerprint-sha256', 'ja3-fingerprint-md5', 'jarm-fingerprint', 'hassh-md5', 'hasshserver-md5', 'other', 'hex', 'cookie', 'hostname|port', 'bro', 'zeek', 'anonymised', 'community-id', 'email-subject', 'favicon-mmh3', 'dkim', 'dkim-signature')
|
||||
),
|
||||
'Payload type' => array(
|
||||
'desc' => __('Information about the final payload(s)'),
|
||||
|
@ -4661,7 +4665,7 @@ class Attribute extends AppModel
|
|||
),
|
||||
'Person' => array(
|
||||
'desc' => __('A human being - natural person'),
|
||||
'types' => array('first-name', 'middle-name', 'last-name', 'date-of-birth', 'place-of-birth', 'gender', 'passport-number', 'passport-country', 'passport-expiration', 'redress-number', 'nationality', 'visa-number', 'issue-date-of-the-visa', 'primary-residence', 'country-of-residence', 'special-service-request', 'frequent-flyer-number', 'travel-details', 'payment-details', 'place-port-of-original-embarkation', 'place-port-of-clearance', 'place-port-of-onward-foreign-destination', 'passenger-name-record-locator-number', 'comment', 'text', 'other', 'phone-number', 'identity-card-number', 'anonymised', 'email', 'pgp-public-key', 'pgp-private-key')
|
||||
'types' => array('first-name', 'middle-name', 'last-name', 'full-name', 'date-of-birth', 'place-of-birth', 'gender', 'passport-number', 'passport-country', 'passport-expiration', 'redress-number', 'nationality', 'visa-number', 'issue-date-of-the-visa', 'primary-residence', 'country-of-residence', 'special-service-request', 'frequent-flyer-number', 'travel-details', 'payment-details', 'place-port-of-original-embarkation', 'place-port-of-clearance', 'place-port-of-onward-foreign-destination', 'passenger-name-record-locator-number', 'comment', 'text', 'other', 'phone-number', 'identity-card-number', 'anonymised', 'email', 'pgp-public-key', 'pgp-private-key')
|
||||
),
|
||||
'Other' => array(
|
||||
'desc' => __('Attributes that are not part of any other category or are meant to be used as a component in MISP objects in the future'),
|
||||
|
@ -4838,9 +4842,12 @@ class Attribute extends AppModel
|
|||
'github-organisation' => array('desc' => __('A github organisation'), 'default_category' => 'Social network', 'to_ids' => 0),
|
||||
'jabber-id' => array('desc' => __('Jabber ID'), 'default_category' => 'Social network', 'to_ids' => 0),
|
||||
'twitter-id' => array('desc' => __('Twitter ID'), 'default_category' => 'Social network', 'to_ids' => 0),
|
||||
'dkim' => array('desc' => __('DKIM public key'), 'default_category' => 'Network activity', 'to_ids' => 0),
|
||||
'dkim-signature'=> array('desc' => __('DKIM signature'), 'default_category' => 'Network activity', 'to_ids' => 0),
|
||||
'first-name' => array('desc' => __('First name of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'middle-name' => array('desc' => __('Middle name of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'last-name' => array('desc' => __('Last name of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'full-name' => array('desc' => __('Full name of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'date-of-birth' => array('desc' => __('Date of birth of a natural person (in YYYY-MM-DD format)'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'place-of-birth' => array('desc' => __('Place of birth of a natural person'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'gender' => array('desc' => __('The gender of a natural person (Male, Female, Other, Prefer not to say)'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
|
@ -4859,7 +4866,7 @@ class Attribute extends AppModel
|
|||
//'remarks' => array('desc' => '', 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'travel-details' => array('desc' => __('Travel details'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'payment-details' => array('desc' => __('Payment details'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'place-port-of-original-embarkation' => array('desc' => __('The orignal port of embarkation'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'place-port-of-original-embarkation' => array('desc' => __('The original port of embarkation'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'place-port-of-clearance' => array('desc' => __('The port of clearance'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'place-port-of-onward-foreign-destination' => array('desc' => __('A Port where the passenger is transiting to'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
'passenger-name-record-locator-number' => array('desc' => __('The Passenger Name Record Locator is a key under which the reservation for a trip is stored in the system. The PNR contains, among other data, the name, flight segments and address of the passenger. It is defined by a combination of five or six letters and numbers.'), 'default_category' => 'Person', 'to_ids' => 0),
|
||||
|
|
|
@ -3,6 +3,7 @@ App::uses('AppModel', 'Model');
|
|||
|
||||
/**
|
||||
* @property Tag $Tag
|
||||
* @property Attribute $Attribute
|
||||
*/
|
||||
class AttributeTag extends AppModel
|
||||
{
|
||||
|
@ -87,7 +88,7 @@ class AttributeTag extends AppModel
|
|||
{
|
||||
$this->delete($id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* handleAttributeTags
|
||||
*
|
||||
|
@ -117,27 +118,43 @@ class AttributeTag extends AppModel
|
|||
}
|
||||
}
|
||||
|
||||
public function handleAttributeTag($attribute_id, $event_id, $tag)
|
||||
public function handleAttributeTag($attribute_id, $event_id, array $tag)
|
||||
{
|
||||
if (empty($tag['deleted'])) {
|
||||
$this->attachTagToAttribute($attribute_id, $event_id, $tag['id']);
|
||||
$local = isset($tag['local']) ? $tag['local'] : false;
|
||||
$this->attachTagToAttribute($attribute_id, $event_id, $tag['id'], $local);
|
||||
} else {
|
||||
$this->detachTagFromAttribute($attribute_id, $event_id, $tag['id']);
|
||||
}
|
||||
}
|
||||
|
||||
public function attachTagToAttribute($attribute_id, $event_id, $tag_id)
|
||||
/**
|
||||
* @param int $attribute_id
|
||||
* @param int $event_id
|
||||
* @param int $tag_id
|
||||
* @param bool $local
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function attachTagToAttribute($attribute_id, $event_id, $tag_id, $local = false)
|
||||
{
|
||||
$existingAssociation = $this->find('first', array(
|
||||
'recursive' => -1,
|
||||
'fields' => ['id'],
|
||||
'conditions' => array(
|
||||
'tag_id' => $tag_id,
|
||||
'attribute_id' => $attribute_id
|
||||
)
|
||||
));
|
||||
if (empty($existingAssociation)) {
|
||||
$data = [
|
||||
'attribute_id' => $attribute_id,
|
||||
'event_id' => $event_id,
|
||||
'tag_id' => $tag_id,
|
||||
'local' => $local ? 1 : 0,
|
||||
];
|
||||
$this->create();
|
||||
if (!$this->save(array('attribute_id' => $attribute_id, 'event_id' => $event_id, 'tag_id' => $tag_id))) {
|
||||
if (!$this->save($data)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -148,6 +165,7 @@ class AttributeTag extends AppModel
|
|||
{
|
||||
$existingAssociation = $this->find('first', array(
|
||||
'recursive' => -1,
|
||||
'fields' => ['id'],
|
||||
'conditions' => array(
|
||||
'tag_id' => $tag_id,
|
||||
'event_id' => $event_id,
|
||||
|
@ -230,9 +248,12 @@ class AttributeTag extends AppModel
|
|||
}
|
||||
} else {
|
||||
$allowed_tag_lookup_table = array_flip($allowedTags);
|
||||
$attributes = $this->Attribute->fetchAttributes($user, array('conditions' => array(
|
||||
'Attribute.event_id' => $eventId
|
||||
)));
|
||||
$attributes = $this->Attribute->fetchAttributes($user, array(
|
||||
'conditions' => array(
|
||||
'Attribute.event_id' => $eventId
|
||||
),
|
||||
'flatten' => 1
|
||||
));
|
||||
$scores = array('scores' => array(), 'maxScore' => 0);
|
||||
foreach ($attributes as $attribute) {
|
||||
foreach ($attribute['AttributeTag'] as $tag) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
App::uses('RandomTool', 'Tools');
|
||||
App::uses('CidrTool', 'Tools');
|
||||
|
||||
/**
|
||||
* @property User $User
|
||||
|
@ -26,7 +27,6 @@ class AuthKey extends AppModel
|
|||
// massage the data before we send it off for validation before saving anything
|
||||
public function beforeValidate($options = array())
|
||||
{
|
||||
//parent::beforeValidate();
|
||||
if (empty($this->data['AuthKey']['id'])) {
|
||||
if (empty($this->data['AuthKey']['uuid'])) {
|
||||
$this->data['AuthKey']['uuid'] = CakeText::uuid();
|
||||
|
@ -42,22 +42,66 @@ class AuthKey extends AppModel
|
|||
$this->data['AuthKey']['authkey_end'] = substr($authkey, -4);
|
||||
$this->data['AuthKey']['authkey_raw'] = $authkey;
|
||||
$this->authkey_raw = $authkey;
|
||||
}
|
||||
|
||||
$validity = Configure::read('Security.advanced_authkeys_validity');
|
||||
if (empty($this->data['AuthKey']['expiration'])) {
|
||||
$this->data['AuthKey']['expiration'] = $validity ? strtotime("+$validity days") : 0;
|
||||
if (!empty($this->data['AuthKey']['allowed_ips'])) {
|
||||
if (is_string($this->data['AuthKey']['allowed_ips'])) {
|
||||
$this->data['AuthKey']['allowed_ips'] = trim($this->data['AuthKey']['allowed_ips']);
|
||||
if (empty($this->data['AuthKey']['allowed_ips'])) {
|
||||
$this->data['AuthKey']['allowed_ips'] = [];
|
||||
} else {
|
||||
$this->data['AuthKey']['allowed_ips'] = explode("\n", $this->data['AuthKey']['allowed_ips']);
|
||||
$this->data['AuthKey']['allowed_ips'] = array_map('trim', $this->data['AuthKey']['allowed_ips']);
|
||||
}
|
||||
}
|
||||
if (!is_array($this->data['AuthKey']['allowed_ips'])) {
|
||||
$this->invalidate('allowed_ips', 'Allowed IPs must be array');
|
||||
}
|
||||
foreach ($this->data['AuthKey']['allowed_ips'] as $cidr) {
|
||||
if (!CidrTool::validate($cidr)) {
|
||||
$this->invalidate('allowed_ips', "$cidr is not valid IP range");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$creationTime = isset($this->data['AuthKey']['created']) ? $this->data['AuthKey']['created'] : time();
|
||||
$validity = Configure::read('Security.advanced_authkeys_validity');
|
||||
if (empty($this->data['AuthKey']['expiration'])) {
|
||||
$this->data['AuthKey']['expiration'] = $validity ? strtotime("+$validity days", $creationTime) : 0;
|
||||
} else {
|
||||
$expiration = is_numeric($this->data['AuthKey']['expiration']) ?
|
||||
(int)$this->data['AuthKey']['expiration'] :
|
||||
strtotime($this->data['AuthKey']['expiration']);
|
||||
|
||||
if ($expiration === false) {
|
||||
$this->invalidate('expiration', __('Expiration must be in YYYY-MM-DD format.'));
|
||||
}
|
||||
if ($validity && $expiration > strtotime("+$validity days", $creationTime)) {
|
||||
$this->invalidate('expiration', __('Maximal key validity is %s days.', $validity));
|
||||
}
|
||||
$this->data['AuthKey']['expiration'] = $expiration;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function afterFind($results, $primary = false)
|
||||
{
|
||||
foreach ($results as $key => $val) {
|
||||
if (isset($val['AuthKey']['allowed_ips'])) {
|
||||
$results[$key]['AuthKey']['allowed_ips'] = $this->jsonDecode($val['AuthKey']['allowed_ips']);
|
||||
}
|
||||
}
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function beforeSave($options = array())
|
||||
{
|
||||
if (isset($this->data['AuthKey']['allowed_ips'])) {
|
||||
if (empty($this->data['AuthKey']['allowed_ips'])) {
|
||||
$this->data['AuthKey']['allowed_ips'] = null;
|
||||
} else {
|
||||
$expiration = is_numeric($this->data['AuthKey']['expiration']) ?
|
||||
(int)$this->data['AuthKey']['expiration'] :
|
||||
strtotime($this->data['AuthKey']['expiration']);
|
||||
|
||||
if ($expiration === false) {
|
||||
$this->invalidate('expiration', __('Expiration must be in YYYY-MM-DD format.'));
|
||||
}
|
||||
if ($validity && $expiration > strtotime("+$validity days")) {
|
||||
$this->invalidate('expiration', __('Maximal key validity is %s days.', $validity));
|
||||
}
|
||||
$this->data['AuthKey']['expiration'] = $expiration;
|
||||
$this->data['AuthKey']['allowed_ips'] = json_encode($this->data['AuthKey']['allowed_ips']);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
@ -71,9 +115,9 @@ class AuthKey extends AppModel
|
|||
{
|
||||
$start = substr($authkey, 0, 4);
|
||||
$end = substr($authkey, -4);
|
||||
$existing_authkeys = $this->find('all', [
|
||||
$possibleAuthkeys = $this->find('all', [
|
||||
'recursive' => -1,
|
||||
'fields' => ['id', 'authkey', 'user_id', 'expiration'],
|
||||
'fields' => ['id', 'authkey', 'user_id', 'expiration', 'allowed_ips'],
|
||||
'conditions' => [
|
||||
'OR' => [
|
||||
'expiration >' => time(),
|
||||
|
@ -84,12 +128,13 @@ class AuthKey extends AppModel
|
|||
]
|
||||
]);
|
||||
$passwordHasher = $this->getHasher();
|
||||
foreach ($existing_authkeys as $existing_authkey) {
|
||||
if ($passwordHasher->check($authkey, $existing_authkey['AuthKey']['authkey'])) {
|
||||
$user = $this->User->getAuthUser($existing_authkey['AuthKey']['user_id']);
|
||||
foreach ($possibleAuthkeys as $possibleAuthkey) {
|
||||
if ($passwordHasher->check($authkey, $possibleAuthkey['AuthKey']['authkey'])) {
|
||||
$user = $this->User->getAuthUser($possibleAuthkey['AuthKey']['user_id']);
|
||||
if ($user) {
|
||||
$user['authkey_id'] = $existing_authkey['AuthKey']['id'];
|
||||
$user['authkey_expiration'] = $existing_authkey['AuthKey']['expiration'];
|
||||
$user['authkey_id'] = $possibleAuthkey['AuthKey']['id'];
|
||||
$user['authkey_expiration'] = $possibleAuthkey['AuthKey']['expiration'];
|
||||
$user['allowed_ips'] = $possibleAuthkey['AuthKey']['allowed_ips'];
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
@ -97,26 +142,67 @@ class AuthKey extends AppModel
|
|||
return false;
|
||||
}
|
||||
|
||||
public function resetauthkey($id)
|
||||
/**
|
||||
* @param int $userId
|
||||
* @param int|null $keyId
|
||||
* @return false|string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function resetAuthKey($userId, $keyId = null)
|
||||
{
|
||||
$existing_authkeys = $this->find('all', [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'user_id' => $id
|
||||
]
|
||||
]);
|
||||
foreach ($existing_authkeys as $key) {
|
||||
$key['AuthKey']['expiration'] = time();
|
||||
$this->save($key);
|
||||
$time = time();
|
||||
|
||||
if ($keyId) {
|
||||
$currentAuthkey = $this->find('first', [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'id' => $keyId,
|
||||
'user_id' => $userId,
|
||||
],
|
||||
]);
|
||||
if (empty($currentAuthkey)) {
|
||||
throw new RuntimeException("Key with ID $keyId for user with ID $userId not found.");
|
||||
}
|
||||
$currentAuthkey['AuthKey']['expiration'] = $time;
|
||||
if (!$this->save($currentAuthkey)) {
|
||||
throw new RuntimeException("Key with ID $keyId could not be saved.");
|
||||
}
|
||||
$comment = __("Created by resetting auth key %s\n%s", $keyId, $currentAuthkey['AuthKey']['comment']);
|
||||
$allowedIps = isset($currentAuthkey['AuthKey']['allowed_ips']) ? $currentAuthkey['AuthKey']['allowed_ips'] : [];
|
||||
return $this->createnewkey($userId, $comment, $allowedIps);
|
||||
} else {
|
||||
$existingAuthkeys = $this->find('all', [
|
||||
'recursive' => -1,
|
||||
'conditions' => [
|
||||
'OR' => [
|
||||
'expiration >' => $time,
|
||||
'expiration' => 0
|
||||
],
|
||||
'user_id' => $userId
|
||||
]
|
||||
]);
|
||||
foreach ($existingAuthkeys as $key) {
|
||||
$key['AuthKey']['expiration'] = $time;
|
||||
$this->save($key);
|
||||
}
|
||||
return $this->createnewkey($userId);
|
||||
}
|
||||
return $this->createnewkey($id);
|
||||
}
|
||||
|
||||
public function createnewkey($id)
|
||||
/**
|
||||
* @param int $userId
|
||||
* @param string $comment
|
||||
* @param array $allowedIps
|
||||
* @return false|string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function createnewkey($userId, $comment = '', array $allowedIps = [])
|
||||
{
|
||||
$newKey = [
|
||||
'authkey' => (new RandomTool())->random_str(true, 40),
|
||||
'user_id' => $id
|
||||
'user_id' => $userId,
|
||||
'comment' => $comment,
|
||||
'allowed_ips' => empty($allowedIps) ? null : $allowedIps,
|
||||
];
|
||||
$this->create();
|
||||
if ($this->save($newKey)) {
|
||||
|
@ -175,6 +261,18 @@ class AuthKey extends AppModel
|
|||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* When key is modified, update `date_modified` for user that was assigned to that key, so session data
|
||||
* will be realoaded.
|
||||
* @see AppController::_refreshAuth
|
||||
*/
|
||||
public function afterSave($created, $options = array())
|
||||
{
|
||||
parent::afterSave($created, $options);
|
||||
$userId = $this->data['AuthKey']['user_id'];
|
||||
$this->User->updateAll(['date_modified' => time()], ['User.id' => $userId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* When key is deleted, update after `date_modified` for user that was assigned to that key, so session data
|
||||
* will be realoaded and canceled.
|
||||
|
|
|
@ -34,9 +34,8 @@ class CorrelationExclusion extends AppModel
|
|||
return false;
|
||||
}
|
||||
$redis->del($this->key);
|
||||
$exclusions = $this->find('list', [
|
||||
'recursive' => -1,
|
||||
'fields' => ['id', 'value']
|
||||
$exclusions = $this->find('column', [
|
||||
'fields' => ['value']
|
||||
]);
|
||||
$redis->sAddArray($this->key, $exclusions);
|
||||
}
|
||||
|
@ -85,6 +84,7 @@ class CorrelationExclusion extends AppModel
|
|||
$this->Job = ClassRegistry::init('Job');
|
||||
$this->Job->id = $jobId;
|
||||
}
|
||||
$total = count($exclusions);
|
||||
foreach ($exclusions as $exclusion_chunk) {
|
||||
$i = 0;
|
||||
foreach ($exclusion_chunk as $exclusion) {
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -53,4 +53,18 @@ class EventGraph extends AppModel
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getPictureData($eventGraph)
|
||||
{
|
||||
$b64 = str_replace('data:image/png;base64,', '', $eventGraph['EventGraph']['preview_img']);
|
||||
$imageDecoded = base64_decode($b64);
|
||||
$source = imagecreatefromstring($imageDecoded);
|
||||
imagesavealpha($source, true);
|
||||
ob_start();
|
||||
imagepng($source, null, 9);
|
||||
$imageData = ob_get_contents();
|
||||
ob_end_clean();
|
||||
imagedestroy($source);
|
||||
return $imageData;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,65 +1,143 @@
|
|||
<?php
|
||||
App::uses('AppModel', 'Model');
|
||||
|
||||
// Table `event_locks` is not used anymore
|
||||
class EventLock extends AppModel
|
||||
{
|
||||
public $useTable = 'event_locks';
|
||||
// In seconds
|
||||
const DEFAULT_TTL = 900,
|
||||
PREFIX = 'misp:event_lock';
|
||||
|
||||
public $recursive = -1;
|
||||
|
||||
public $actsAs = array(
|
||||
'Containable',
|
||||
);
|
||||
|
||||
public $belongsTo = array(
|
||||
'User' => array(
|
||||
'className' => 'User',
|
||||
'foreignKey' => 'user_id',
|
||||
)
|
||||
);
|
||||
|
||||
|
||||
public $validate = array(
|
||||
);
|
||||
|
||||
public function beforeValidate($options = array())
|
||||
/**
|
||||
* @param array $user
|
||||
* @param int $eventId
|
||||
* @return bool True if insert was successful.
|
||||
*/
|
||||
public function insertLock(array $user, $eventId)
|
||||
{
|
||||
parent::beforeValidate();
|
||||
return true;
|
||||
return $this->insertLockToRedis($eventId, "user:{$user['id']}", [
|
||||
'type' => 'user',
|
||||
'timestamp' => time(),
|
||||
'User' => [
|
||||
'id' => $user['id'],
|
||||
'org_id' => $user['org_id'],
|
||||
'email' => $user['email'],
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
public function insertLock($user, $eventId)
|
||||
/**
|
||||
* @param int $eventId
|
||||
* @param int $jobId
|
||||
* @return bool True if insert was successful.
|
||||
*/
|
||||
public function insertLockBackgroundJob($eventId, $jobId)
|
||||
{
|
||||
$date = new DateTime();
|
||||
$lock = array(
|
||||
'timestamp' => $date->getTimestamp(),
|
||||
return $this->insertLockToRedis($eventId, "job:$jobId", [
|
||||
'type' => 'job',
|
||||
'timestamp' => time(),
|
||||
'job_id' => $jobId,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $eventId
|
||||
* @return int|null Lock ID
|
||||
*/
|
||||
public function insertLockApi($eventId, array $user)
|
||||
{
|
||||
$apiLockId = mt_rand();
|
||||
if ($this->insertLockToRedis($eventId, "api:{$user['id']}:$apiLockId", [
|
||||
'type' => 'api',
|
||||
'user_id' => $user['id'],
|
||||
'event_id' => $eventId
|
||||
);
|
||||
$this->deleteAll(array('user_id' => $user['id']));
|
||||
$this->create();
|
||||
return $this->save($lock);
|
||||
'timestamp' => time(),
|
||||
])) {
|
||||
return $apiLockId;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function checkLock($user, $eventId)
|
||||
/**
|
||||
* @param int $eventId
|
||||
* @param int $apiLockId
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteApiLock($eventId, $apiLockId, array $user)
|
||||
{
|
||||
$this->cleanupLock($user, $eventId);
|
||||
$locks = $this->find('all', array(
|
||||
'recursive' => -1,
|
||||
'contain' => array('User.email', 'User.org_id', 'User.id'),
|
||||
'conditions' => array(
|
||||
'event_id' => $eventId
|
||||
)
|
||||
));
|
||||
return $locks;
|
||||
try {
|
||||
$redis = $this->setupRedisWithException();
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$deleted = $redis->hdel(self::PREFIX . $eventId, "api:{$user['id']}:$apiLockId");
|
||||
return $deleted > 0;
|
||||
}
|
||||
|
||||
// If a lock has been active for 15 minutes, delete it
|
||||
public function cleanupLock()
|
||||
/**
|
||||
* @param int $eventId
|
||||
* @param int $jobId
|
||||
* @return bool
|
||||
*/
|
||||
public function deleteBackgroundJobLock($eventId, $jobId)
|
||||
{
|
||||
$date = new DateTime();
|
||||
$timestamp = $date->getTimestamp();
|
||||
$timestamp -= 900;
|
||||
$this->deleteAll(array('timestamp <' => $timestamp));
|
||||
return true;
|
||||
try {
|
||||
$redis = $this->setupRedisWithException();
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$deleted = $redis->hDel(self::PREFIX . $eventId, "job:$jobId");
|
||||
return $deleted > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $user
|
||||
* @param int $eventId
|
||||
* @return array[]
|
||||
* @throws JsonException
|
||||
*/
|
||||
public function checkLock(array $user, $eventId)
|
||||
{
|
||||
try {
|
||||
$redis = $this->setupRedisWithException();
|
||||
} catch (Exception $e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$keys = $redis->hGetAll(self::PREFIX . $eventId);
|
||||
if (empty($keys)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$output = [];
|
||||
$now = time();
|
||||
foreach ($keys as $value) {
|
||||
$value = $this->jsonDecode($value);
|
||||
if ($value['timestamp'] + self::DEFAULT_TTL > $now) {
|
||||
$output[] = $value;
|
||||
}
|
||||
}
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $eventId
|
||||
* @param string $lockId
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
private function insertLockToRedis($eventId, $lockId, array $data)
|
||||
{
|
||||
try {
|
||||
$redis = $this->setupRedisWithException();
|
||||
} catch (Exception $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$pipeline = $redis->pipeline();
|
||||
$pipeline->hSet(self::PREFIX . $eventId, $lockId, json_encode($data));
|
||||
$pipeline->expire(self::PREFIX . $eventId, self::DEFAULT_TTL); // prolong TTL
|
||||
return $pipeline->exec()[0] !== false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,6 +103,9 @@ class EventReport extends AppModel
|
|||
$report = ['EventReport' => $report];
|
||||
}
|
||||
$report['EventReport']['event_id'] = $eventId;
|
||||
if (!empty($report['EventReport']['id'])) {
|
||||
unset($report['EventReport']['id']);
|
||||
}
|
||||
$report = $this->captureSG($user, $report);
|
||||
$this->create();
|
||||
$errors = $this->saveAndReturnErrors($report, ['fieldList' => $this->captureFields]);
|
||||
|
@ -114,7 +117,7 @@ class EventReport extends AppModel
|
|||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* addReport Add a report
|
||||
*
|
||||
|
@ -131,7 +134,7 @@ class EventReport extends AppModel
|
|||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* editReport Edit a report
|
||||
*
|
||||
|
@ -208,7 +211,7 @@ class EventReport extends AppModel
|
|||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* restoreReport ACL-aware method to restore a report.
|
||||
*
|
||||
|
@ -231,11 +234,11 @@ class EventReport extends AppModel
|
|||
{
|
||||
$this->Event = ClassRegistry::init('Event');
|
||||
if (isset($report['EventReport']['distribution']) && $report['EventReport']['distribution'] == 4) {
|
||||
$report['EventReport'] = $this->Event->__captureSGForElement($report['EventReport'], $user);
|
||||
$report['EventReport'] = $this->Event->captureSGForElement($report['EventReport'], $user);
|
||||
}
|
||||
return $report;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* buildACLConditions Generate ACL conditions for viewing the report
|
||||
*
|
||||
|
@ -299,7 +302,7 @@ class EventReport extends AppModel
|
|||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* fetchReports ACL-aware method. Basically find with ACL
|
||||
*
|
||||
|
@ -391,7 +394,7 @@ class EventReport extends AppModel
|
|||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public function reArrangeReport(array $report)
|
||||
{
|
||||
$rearrangeObjects = array('Event', 'SharingGroup');
|
||||
|
@ -460,15 +463,17 @@ class EventReport extends AppModel
|
|||
$objects = [];
|
||||
$templateConditions = [];
|
||||
foreach ($event['Object'] as $k => $object) {
|
||||
foreach ($object['Attribute'] as &$objectAttribute) {
|
||||
unset($objectAttribute['ShadowAttribute']);
|
||||
$objectAttribute['object_uuid'] = $object['uuid'];
|
||||
$attributes[$objectAttribute['uuid']] = $objectAttribute;
|
||||
if (isset($object['Attribute'])) {
|
||||
foreach ($object['Attribute'] as &$objectAttribute) {
|
||||
unset($objectAttribute['ShadowAttribute']);
|
||||
$objectAttribute['object_uuid'] = $object['uuid'];
|
||||
$attributes[$objectAttribute['uuid']] = $objectAttribute;
|
||||
|
||||
foreach ($objectAttribute['AttributeTag'] as $at) {
|
||||
$allTagNames[$at['Tag']['name']] = $at['Tag'];
|
||||
foreach ($objectAttribute['AttributeTag'] as $at) {
|
||||
$allTagNames[$at['Tag']['name']] = $at['Tag'];
|
||||
}
|
||||
$this->Event->Attribute->removeGalaxyClusterTags($objectAttribute);
|
||||
}
|
||||
$this->Event->Attribute->removeGalaxyClusterTags($objectAttribute);
|
||||
}
|
||||
$objects[$object['uuid']] = $object;
|
||||
|
||||
|
@ -548,7 +553,7 @@ class EventReport extends AppModel
|
|||
}
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
||||
public function applySuggestionsInText($contentWithSuggestions, array $attribute, $value)
|
||||
{
|
||||
$textToBeReplaced = "@[suggestion]($value)";
|
||||
|
@ -592,7 +597,7 @@ class EventReport extends AppModel
|
|||
'attribute' => $savedAttribute
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* transformFreeTextIntoReplacement
|
||||
*
|
||||
|
@ -704,7 +709,7 @@ class EventReport extends AppModel
|
|||
'replacementResult' => $replacementResult,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* extractWithReplacements Extract context information from report with special care for ATT&CK
|
||||
*
|
||||
|
@ -907,7 +912,7 @@ class EventReport extends AppModel
|
|||
if ($tagId === -1) {
|
||||
$tagId = $this->EventTag->Tag->captureTag(['name' => $tagName], $user);
|
||||
}
|
||||
$this->EventTag->attachTagToEvent($eventId, $tagId);
|
||||
$this->EventTag->attachTagToEvent($eventId, ['id' => $tagId]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -85,25 +85,25 @@ class EventTag extends AppModel
|
|||
public function handleEventTag($event_id, $tag, &$nothingToChange = false)
|
||||
{
|
||||
if (empty($tag['deleted'])) {
|
||||
$result = $this->attachTagToEvent($event_id, $tag['id'], $nothingToChange);
|
||||
$result = $this->attachTagToEvent($event_id, $tag, $nothingToChange);
|
||||
} else {
|
||||
$result = $this->detachTagFromEvent($event_id, $tag['id'], $nothingToChange);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function attachTagToEvent($event_id, $tag_id, &$nothingToChange = false)
|
||||
public function attachTagToEvent($event_id, $tag, &$nothingToChange = false)
|
||||
{
|
||||
$existingAssociation = $this->find('first', array(
|
||||
'recursive' => -1,
|
||||
'conditions' => array(
|
||||
'tag_id' => $tag_id,
|
||||
'tag_id' => $tag['id'],
|
||||
'event_id' => $event_id
|
||||
)
|
||||
));
|
||||
if (empty($existingAssociation)) {
|
||||
$this->create();
|
||||
if (!$this->save(array('event_id' => $event_id, 'tag_id' => $tag_id))) {
|
||||
if (!$this->save(array('event_id' => $event_id, 'tag_id' => $tag['id'], 'local' => !empty($tag['local'])))) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -32,7 +32,10 @@ class Feed extends AppModel
|
|||
'rule' => array('urlOrExistingFilepath')
|
||||
),
|
||||
'provider' => 'valueNotEmpty',
|
||||
'name' => 'valueNotEmpty',
|
||||
'name' => [
|
||||
'rule' => 'valueNotEmpty',
|
||||
'required' => true,
|
||||
],
|
||||
'event_id' => array(
|
||||
'rule' => array('numeric'),
|
||||
'message' => 'Please enter a numeric event ID or leave this field blank.',
|
||||
|
@ -207,7 +210,7 @@ class Feed extends AppModel
|
|||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getManifest($feed, HttpSocket $HttpSocket = null)
|
||||
public function getManifest(array $feed, HttpSocket $HttpSocket = null)
|
||||
{
|
||||
$events = $this->downloadManifest($feed, $HttpSocket);
|
||||
$events = $this->__filterEventsIndex($events, $feed);
|
||||
|
@ -658,13 +661,19 @@ class Feed extends AppModel
|
|||
return true;
|
||||
}
|
||||
|
||||
private function __filterEventsIndex($events, $feed)
|
||||
private function __filterEventsIndex(array $events, array $feed)
|
||||
{
|
||||
$filterRules = $this->__prepareFilterRules($feed);
|
||||
if (!$filterRules) {
|
||||
$filterRules = array();
|
||||
}
|
||||
foreach ($events as $k => $event) {
|
||||
if (isset($event['orgc']) && !isset($event['Orgc'])) { // fix key case
|
||||
$event['Orgc'] = $event['orgc'];
|
||||
unset($event['orgc']);
|
||||
$events[$k] = $event;
|
||||
}
|
||||
|
||||
if (isset($filterRules['orgs']['OR']) && !empty($filterRules['orgs']['OR']) && !in_array($event['Orgc']['name'], $filterRules['orgs']['OR'])) {
|
||||
unset($events[$k]);
|
||||
continue;
|
||||
|
@ -767,7 +776,13 @@ class Feed extends AppModel
|
|||
return $result;
|
||||
}
|
||||
|
||||
private function __prepareEvent($event, $feed, $filterRules)
|
||||
/**
|
||||
* @param array $event
|
||||
* @param array $feed
|
||||
* @param array $filterRules
|
||||
* @return array|string
|
||||
*/
|
||||
private function __prepareEvent($event, array $feed, $filterRules)
|
||||
{
|
||||
if (isset($event['response'])) {
|
||||
$event = $event['response'];
|
||||
|
@ -776,7 +791,11 @@ class Feed extends AppModel
|
|||
$event = $event[0];
|
||||
}
|
||||
if (!isset($event['Event']['uuid'])) {
|
||||
throw new Exception("Event uuid field missing.");
|
||||
throw new InvalidArgumentException("Event UUID field missing.");
|
||||
}
|
||||
if (isset($event['Event']['orgc']) && !isset($event['Event']['Orgc'])) { // fix key case
|
||||
$event['Event']['Orgc'] = $event['Event']['orgc'];
|
||||
unset($event['Event']['orgc']);
|
||||
}
|
||||
$event['Event']['distribution'] = $feed['Feed']['distribution'];
|
||||
$event['Event']['sharing_group_id'] = $feed['Feed']['sharing_group_id'];
|
||||
|
@ -994,7 +1013,7 @@ class Feed extends AppModel
|
|||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public function saveFreetextFeedData($feed, $data, $user, $jobId = false)
|
||||
public function saveFreetextFeedData(array $feed, array $data, array $user, $jobId = false)
|
||||
{
|
||||
$this->Event = ClassRegistry::init('Event');
|
||||
|
||||
|
@ -1037,25 +1056,14 @@ class Feed extends AppModel
|
|||
}
|
||||
}
|
||||
if ($feed['Feed']['fixed_event']) {
|
||||
$existsAttributes = $this->Event->Attribute->find('all', array(
|
||||
$existsAttributesValueToId = $this->Event->Attribute->find('list', array(
|
||||
'conditions' => array(
|
||||
'Attribute.deleted' => 0,
|
||||
'Attribute.event_id' => $event['Event']['id']
|
||||
),
|
||||
'recursive' => -1,
|
||||
'fields' => array('id', 'value1', 'value2')
|
||||
'fields' => array('value', 'id')
|
||||
));
|
||||
$existsAttributesValueToId = array();
|
||||
foreach ($existsAttributes as $t) {
|
||||
if (!empty($t['Attribute']['value2'])) {
|
||||
$value = $t['Attribute']['value1'] . '|' . $t['Attribute']['value2'];
|
||||
} else {
|
||||
$value = $t['Attribute']['value1'];
|
||||
}
|
||||
// Since event values are unique, it is OK to put value into key
|
||||
$existsAttributesValueToId[$value] = $t['Attribute']['id'];
|
||||
}
|
||||
unset($existsAttributes);
|
||||
|
||||
// Create event diff. After this cycle, `$data` will contains just attributes that do not exists in current
|
||||
// event and in `$existsAttributesValueToId` will contains just attributes that do not exists in current feed.
|
||||
|
@ -1095,7 +1103,6 @@ class Feed extends AppModel
|
|||
return true;
|
||||
}
|
||||
|
||||
$data = array_values($data);
|
||||
$uniqueValues = array();
|
||||
foreach ($data as $key => $value) {
|
||||
if (isset($uniqueValues[$value['value']])) {
|
||||
|
@ -1125,7 +1132,7 @@ class Feed extends AppModel
|
|||
$this->Event->publishRouter($event['Event']['id'], null, $user);
|
||||
}
|
||||
if ($feed['Feed']['tag_id']) {
|
||||
$this->Event->EventTag->attachTagToEvent($event['Event']['id'], $feed['Feed']['tag_id']);
|
||||
$this->Event->EventTag->attachTagToEvent($event['Event']['id'], ['id' => $feed['Feed']['tag_id']]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1235,7 +1242,7 @@ class Feed extends AppModel
|
|||
$md5Values = array_map('md5', array_column($values, 'value'));
|
||||
|
||||
$redis->del('misp:feed_cache:' . $feedId);
|
||||
foreach (array_chunk($md5Values, 1000) as $k => $chunk) {
|
||||
foreach (array_chunk($md5Values, 5000) as $k => $chunk) {
|
||||
$pipe = $redis->multi(Redis::PIPELINE);
|
||||
if (method_exists($redis, 'sAddArray')) {
|
||||
$redis->sAddArray('misp:feed_cache:' . $feedId, $chunk);
|
||||
|
@ -1247,7 +1254,7 @@ class Feed extends AppModel
|
|||
}
|
||||
}
|
||||
$pipe->exec();
|
||||
$this->jobProgress($jobId, __('Feed %s: %s/%s values cached.', $feedId, $k * 1000, count($md5Values)));
|
||||
$this->jobProgress($jobId, __('Feed %s: %s/%s values cached.', $feedId, $k * 5000, count($md5Values)));
|
||||
}
|
||||
$redis->set('misp:feed_cache_timestamp:' . $feedId, time());
|
||||
return true;
|
||||
|
@ -1469,7 +1476,7 @@ class Feed extends AppModel
|
|||
}
|
||||
}
|
||||
$this->create();
|
||||
if (!$this->save($feed, true, array('name', 'provider', 'url', 'rules', 'source_format', 'fixed_event', 'delta_merge', 'override_ids', 'publish', 'settings', 'tag_id', 'default', 'lookup_visible'))) {
|
||||
if (!$this->save($feed, true, array('name', 'provider', 'url', 'rules', 'source_format', 'fixed_event', 'delta_merge', 'override_ids', 'publish', 'settings', 'tag_id', 'default', 'lookup_visible', 'headers'))) {
|
||||
$results['fails']++;
|
||||
} else {
|
||||
$results['successes']++;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue