From be0a9823fa37f198f140b75a613611c885db9921 Mon Sep 17 00:00:00 2001 From: VertexC Date: Sun, 23 Jun 2019 13:33:13 -0700 Subject: [PATCH] Set up travis. Add unittest TestAppBase, which sets up the app and adds the admin user for further tests. --- .travis.yml | 10 ++ install/install.sh | 1 + run.py | 4 +- tests/installTestDb.sh | 49 ++++++++ tests/testAppBase.py | 110 ++++++++++++++++++ ...{TestNestedMenus.py => testNestedMenus.py} | 15 ++- travis_install.sh | 21 ++++ 7 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 .travis.yml create mode 100755 tests/installTestDb.sh create mode 100644 tests/testAppBase.py rename tests/{TestNestedMenus.py => testNestedMenus.py} (95%) create mode 100755 travis_install.sh diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..2af2956 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,10 @@ +# Config file for automatic testing at travis-ci.org +sudo: true # http://docs.travis-ci.com/user/migrating-from-legacy/ +language: python +python: + - 2.7 +install: + - sudo ./travis_install.sh + - sudo ./tests/installTestDb.sh +script: + - nose2 -v \ No newline at end of file diff --git a/install/install.sh b/install/install.sh index 5ad7590..949df16 100755 --- a/install/install.sh +++ b/install/install.sh @@ -137,6 +137,7 @@ DATABASE_URI = '${config_db_uri}' COLLECTOR_UDP_PORT = ${config_collector_udp} COLLECTOR_SSL_PORT = ${config_collector_ssl} NETWORK_INTERFACE = ${network_interface} +TESTING = False " > "${dir}/../config.py" echo "* Making necessary scripts executable (if they aren't already)" chmod +x "${dir}/../bin/pipotd" "${dir}/../bin/create_image.sh" "${dir}/../../client/bin/chroot.sh" >> "$install_log" 2>&1 diff --git a/run.py b/run.py index 5e1abba..02bef6b 100644 --- a/run.py +++ b/run.py @@ -57,7 +57,9 @@ def install_secret_keys(application, secret_session='secret_key', if do_exit: sys.exit(1) -install_secret_keys(app) + +if not app.config['TESTING']: + install_secret_keys(app) # Expose submenu method for jinja templates diff --git a/tests/installTestDb.sh b/tests/installTestDb.sh new file mode 100755 index 0000000..5545582 --- /dev/null +++ b/tests/installTestDb.sh @@ -0,0 +1,49 @@ +dir=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +date=`date +%Y-%m-%d` +install_log="${dir}/TestDbSetUp_${date}_log.txt" +read -e -p "Password of the 'root' user of MySQL: " -i "" db_root_password +# Verify password +while ! mysql -u root --password="${db_root_password}" -e ";" ; do + read -e -p "Invalid password, please retry: " -i "" db_root_password +done +db_user="pipot" +db_name="pipotTest" +mysql -u root --password="${db_root_password}" -e "CREATE DATABASE IF NOT EXISTS ${db_name};" >> "$install_log" 2>&1 +# Check if DB exists +db_exists=`mysql -u root --password="${db_root_password}" -se"USE ${db_name};" 2>&1` +if [ ! "${db_exists}" == "" ]; then + echo "Failed to create the database! Please check the installation log!" + exit -1 +fi +# Check if user exists +db_user_exists=`mysql -u root --password="${db_root_password}" -sse "SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = '${db_user}')"` +db_user_password="" +if [ ${db_user_exists} = 0 ]; then + rand_pass=$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1) + read -e -p "Password for ${db_user} (will be created): " -i "${rand_pass}" db_user_password + # Attempt to create the user + mysql -u root --password="$db_root_password" -e "CREATE USER '${db_user}'@'localhost' IDENTIFIED BY '${db_user_password}';" >> "$install_log" 2>&1 + db_user_exists=`mysql -u root --password="$db_root_password" -sse "SELECT EXISTS(SELECT 1 FROM mysql.user WHERE user = '$db_user')"` + if [ ${db_user_exists} = 0 ]; then + echo "Failed to create the user! Please check the installation log!" + exit -1 + fi +else + read -e -p "Password for ${db_user}: " db_user_password + # Check if we have access + while ! mysql -u "${db_user}" --password="${db_user_password}" -e ";" ; do + read -e -p "Invalid password, please retry: " -i "" db_user_password + done +fi +# Grant user access to database +mysql -u root --password="${db_root_password}" -e "GRANT ALL ON ${db_name}.* TO '${db_user}'@'localhost';" >> "$install_log" 2>&1 +# Check if user has access +db_access=`mysql -u "${db_user}" --password="${db_user_password}" -se"USE ${db_name};" 2>&1` +if [ ! "${db_access}" == "" ]; then + echo "Failed to grant user access to database! Please check the installation log!" + exit -1 +fi + +echo "# Auto-generated configuration by installTestDb.sh +DATABASE_URI = 'mysql+pymysql://${db_user}:${db_user_password}@localhost:3306/${db_name}' +" > "${dir}/tests/config.py" \ No newline at end of file diff --git a/tests/testAppBase.py b/tests/testAppBase.py new file mode 100644 index 0000000..2b39380 --- /dev/null +++ b/tests/testAppBase.py @@ -0,0 +1,110 @@ +import os +import sys +import mock +import unittest +from mock import patch + + +from sqlalchemy import create_engine +from sqlalchemy.ext.declarative import declarative_base, DeclarativeMeta +from sqlalchemy.orm import scoped_session, sessionmaker + +# Need to append server root path to ensure we can import the necessary files. +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +import tests.config +from collections import namedtuple +from flask import g, current_app +from database import create_session, Base +from mod_auth.models import User, Role, Page, PageAccess +from mod_config.models import Service, Notification, Actions, Conditions, Rule +from mod_honeypot.models import Profile, PiModels, PiPotReport, ProfileService, \ + CollectorTypes, Deployment + + +def generate_keys(tempdir): + secret_csrf_path = os.path.join(tempdir, "secret_csrf") + secret_key_path = os.path.join(tempdir, "secret_key") + if not os.path.exists(secret_csrf_path): + secret_csrf_cmd = "head -c 24 /dev/urandom > {path}".format(path=secret_csrf_path) + os.system(secret_csrf_cmd) + if not os.path.exists(secret_key_path): + secret_key_cmd = "head -c 24 /dev/urandom > {path}".format(path=secret_key_path) + os.system(secret_key_cmd) + + return {'secret_csrf_path': secret_csrf_path, 'secret_key_path': secret_key_path} + + +def load_config(tempdir): + key_paths = generate_keys(tempdir) + with open(key_paths['secret_key_path'], 'rb') as secret_key_file: + secret_key = secret_key_file.read() + with open(key_paths['secret_csrf_path'], 'rb') as secret_csrf_file: + secret_csrf = secret_csrf_file.read() + + return { + 'TESTING': True, + 'WTF_CSRF_ENABLED': False, + 'SQLALCHEMY_POOL_SIZE': 1, + 'SECRET_KEY': secret_key, + 'CSRF_SESSION_KEY': secret_csrf, + 'SERVER_IP': '127.0.0.1', + 'SERVER_PORT': 443, + 'INSTANCE_NAME': 'testInstance', + 'APPLICATION_ROOT': '/', + 'CSRF_ENABLED': False, + 'DATABASE_URI': tests.config.DATABASE_URI, + 'COLLECTOR_UDP_PORT': 1234, + 'COLLECTOR_SSL_PORT': 1235 + } + + +class TestAppBaseTest(unittest.TestCase): + tempdir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "temp") + + def create_app(self): + with patch('config_parser.parse_config', return_value=load_config(self.tempdir)): + from run import app + return app + + def create_admin(self): + # test if there is admin existed + db = create_session(self.app.config['DATABASE_URI'], drop_tables=False) + role = Role(name="Admin") + db.add(role) + db.commit() + admin_user = User(role_id=role.id, name="Admin", password="admin", email="admin@sample.com") + db.add(admin_user) + db.commit() + db.remove() + return admin_user + + def setUp(self): + if not os.path.exists(self.tempdir): + os.mkdir(self.tempdir) + self.app = self.create_app() + + def tearDown(self): + db_engine = create_engine(self.app.config['DATABASE_URI'], convert_unicode=True) + Base.metadata.drop_all(bind=db_engine) + + def test_app_is_running(self): + self.assertFalse(current_app is None) + + def test_app_is_testing(self): + self.assertTrue(self.app.config['TESTING']) + + def admin_is_created(self): + db = create_session(self.app.config['DATABASE_URI'], drop_tables=False) + admin_row = Role.query.filter(Role.is_admin).first() + admin = User.query.filter(User.role_id == admin_row.id).first() + db.remove() + return admin is not None + + def test_create_admin(self): + self.create_admin() + self.assertTrue(self.admin_is_created()) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/TestNestedMenus.py b/tests/testNestedMenus.py similarity index 95% rename from tests/TestNestedMenus.py rename to tests/testNestedMenus.py index 15de8d0..5f609a6 100644 --- a/tests/TestNestedMenus.py +++ b/tests/testNestedMenus.py @@ -1,13 +1,18 @@ import unittest +import os +import sys from mock import patch, call -from server.decorators import get_menu_entries, get_permissible_entries +# Need to append server root path to ensure we can import the necessary files. +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +from decorators import get_menu_entries, get_permissible_entries class TestGetMenuEntries(unittest.TestCase): @patch('mod_auth.models.User') - @patch('server.decorators.get_permissible_entries') + @patch('decorators.get_permissible_entries') def test_get_menu_entries_with_simple_entries(self, mock_permissible_entries, mock_user): """ Passing a menu entry to get_menu_entries() when all the @@ -59,7 +64,7 @@ def side_effect(*args): mock_permissible_entries.assert_has_calls(calls) # Check that mocked function correctly called @patch('mod_auth.models.User') - @patch('server.decorators.get_permissible_entries') + @patch('decorators.get_permissible_entries') def test_get_menu_entries_with_no_permissions(self, mock_permissible_entries, mock_user): """ Passing a menu entry to get_menu_entries() when user is not @@ -158,3 +163,7 @@ def side_effect(*args): 'entries': [{'title': 'Honeypot services', 'route': 'config.services', 'icon': 'sliders'}], 'icon': 'bell-o'}], 'icon': 'bell-o'} self.assertDictEqual(entries, correct_entries) + + +if __name__ == "__main__": + unittest.main() diff --git a/travis_install.sh b/travis_install.sh new file mode 100755 index 0000000..83556db --- /dev/null +++ b/travis_install.sh @@ -0,0 +1,21 @@ +echo "-------------------------------" +echo "| Installing dependencies |" +echo "-------------------------------" +echo "" +echo "* Updating package list " +apt-get update +echo "* Installing nginx, python & pip " + +apt-get -q -y install dnsutils nginx python python-dev python-pip + +if [[ "$OSTYPE" == "linux-gnu" ]]; then + apt-get -q -y install build-essential libffi-dev libssl-dev +fi +if [ ! -f /etc/init.d/mysql* ]; then + echo "* Installing MySQL (root password will be empty!)" + DEBIAN_FRONTEND=noninteractive apt-get install -y mysql-server +fi +echo "* Update setuptools " +pip install --upgrade setuptools +echo "* Installing pip dependencies" +pip install nose2 mock ipaddress enum34 cryptography idna sqlalchemy twisted pyopenssl flask-sqlalchemy flask passlib pymysql service_identity pycrypto flask-wtf netifaces gunicorn \ No newline at end of file