From 85a69954ead093c3d864640b28d142ae9a6d3000 Mon Sep 17 00:00:00 2001 From: Phillip Tarrant Date: Wed, 12 Aug 2020 21:33:36 -0400 Subject: [PATCH] building out databases and config files --- .gitignore | 3 +- app/classes/shared/helpers.py | 36 +++++--- app/classes/shared/models.py | 103 +++++++++++++++++++++++ app/classes/web/public_handler.py | 48 ++++++++++- app/classes/web/tornado.py | 6 +- app/config/logging.json | 4 +- app/frontend/templates/public/login.html | 1 + app/config/config.ini => config.ini | 0 main.py | 5 +- 9 files changed, 188 insertions(+), 18 deletions(-) create mode 100644 app/classes/shared/models.py rename app/config/config.ini => config.ini (100%) diff --git a/.gitignore b/.gitignore index ace9e3e1..1d56cc5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ **/logs *.log +*.sqlite __pycache__/ *.py[cod] @@ -15,4 +16,4 @@ venv.bak/ .idea/ -app/config/web \ No newline at end of file +app/config/web diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index 689ee8b2..443f70bb 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -17,6 +17,7 @@ logger = logging.getLogger(__name__) try: from OpenSSL import crypto + from argon2 import PasswordHasher except ModuleNotFoundError as e: logger.critical("Import Error: Unable to load {} module".format(e, e.name)) @@ -29,23 +30,24 @@ class Helpers: self.root_dir = os.path.abspath(os.path.curdir) self.config_dir = os.path.join(self.root_dir, 'app', 'config') self.session_file = os.path.join(self.root_dir, 'session.lock') + self.settings_file = os.path.join(self.root_dir, 'config.ini') self.webroot = os.path.join(self.root_dir, 'app', 'frontend') - self.db_path = os.path.join(self.root_dir, 'app', 'config', 'commander.sqlite') + self.db_path = os.path.join(self.root_dir, 'commander.sqlite') + self.passhasher = PasswordHasher() self.exiting = False def get_setting(self, section, key): - config_file = os.path.join(self.config_dir, 'config.ini') try: our_config = configparser.ConfigParser() - our_config.read(config_file) + our_config.read(self.settings_file) if our_config.has_option(section, key): return our_config[section][key] except Exception as e: - logger.critical("Config File Error: Unable to read {} due to {}".format(config_file, e)) - console.critical("Config File Error: Unable to read {} due to {}".format(config_file, e)) + logger.critical("Config File Error: Unable to read {} due to {}".format(self.settings_file, e)) + console.critical("Config File Error: Unable to read {} due to {}".format(self.settings_file, e)) return False @@ -82,6 +84,17 @@ class Helpers: console.critical("Unable to create exit file!") sys.exit(1) + def encode_pass(self, password): + return self.passhasher.hash(password) + + def verify_pass(self, password, currenthash): + try: + self.passhasher.verify(currenthash, password) + return True + except: + pass + return False + @staticmethod def check_writeable(path: str): filename = os.path.join(path, "tempfile.txt") @@ -97,20 +110,20 @@ class Helpers: return False def ensure_logging_setup(self): - log_file = os.path.join(os.path.curdir, 'app', 'logs', 'commander.log') + log_file = os.path.join(os.path.curdir, 'logs', 'commander.log') logger.info("Checking app directory writable") - writeable = self.check_writeable(os.path.join(os.path.curdir, 'app')) + writeable = self.check_writeable(self.root_dir) # if not writeable, let's bomb out if not writeable: - logger.critical("Unable to write to app directory!") + logger.critical("Unable to write to {} directory!".format(self.root_dir)) sys.exit(1) # ensure the log directory is there try: - os.makedirs(os.path.join(os.path.curdir, 'app', 'logs')) + os.makedirs(os.path.join(self.root_dir, 'logs')) except Exception as e: pass @@ -121,9 +134,9 @@ class Helpers: console.critical("Unable to open log file!") sys.exit(1) - # del any old session.log file as this is a new session + # del any old session.lock file as this is a new session try: - os.remove(os.path.join(os.path.curdir, "app", "logs", "session.log")) + os.remove(self.session_file) except: pass @@ -308,4 +321,5 @@ class Helpers: """ return ''.join(random.choice(chars) for x in range(size)) + helper = Helpers() diff --git a/app/classes/shared/models.py b/app/classes/shared/models.py new file mode 100644 index 00000000..036f01e2 --- /dev/null +++ b/app/classes/shared/models.py @@ -0,0 +1,103 @@ +import sys +import logging +import datetime + +from app.classes.shared.helpers import helper +from app.classes.shared.console import console + +logger = logging.getLogger(__name__) + +try: + from peewee import * + from playhouse.shortcuts import model_to_dict + +except ModuleNotFoundError as e: + logger.critical("Import Error: Unable to load {} module".format(e, e.name)) + console.critical("Import Error: Unable to load {} module".format(e, e.name)) + sys.exit(1) + +database = SqliteDatabase(helper.db_path, pragmas={ + 'journal_mode': 'wal', + 'cache_size': -1024 * 10}) + + +class BaseModel(Model): + class Meta: + database = database + + +class Users(BaseModel): + user_id = AutoField() + created = DateTimeField(default=datetime.datetime.now) + last_login = DateTimeField(default=datetime.datetime.now) + last_ip = CharField(default="") + username = CharField(default="") + password = CharField(default="") + enabled = BooleanField(default=True) + api_token = CharField(default="") + allowed_servers = CharField(default="[]") + + class Meta: + table_name = "users" + + +class Host_Stats(BaseModel): + time = DateTimeField(default=datetime.datetime.now) + boot_time = CharField(default="") + cpu_usage = FloatField(default=0) + cpu_cores = IntegerField(default=0) + cpu_cur_freq = FloatField(default=0) + cpu_max_freq = FloatField(default=0) + mem_percent = FloatField(default=0) + mem_usage = CharField(default="") + mem_total = CharField(default="") + disk_percent = FloatField(default=0) + disk_usage = CharField(default="") + disk_total = CharField(default="") + + class Meta: + table_name = "host_stats" + + +class Webhooks(BaseModel): + id = AutoField() + name = CharField(max_length=64, unique=True) + method = CharField(default="POST") + url = CharField(unique=True) + event = CharField(default="") + send_data = BooleanField(default=True) + + class Meta: + table_name = "webhooks" + + +class Backups(BaseModel): + directories = CharField() + storage_location = CharField() + max_backups = IntegerField() + server_id = IntegerField() + + class Meta: + table_name = 'backups' + +class db_builder: + + @staticmethod + def create_tables(): + with database: + database.create_tables([ + Backups, + Users, + Host_Stats, + Webhooks + ]) + + def default_settings(self): + Users.insert({ + Users.username: 'Admin', + Users.password: helper.encode_pass('asdfasdf'), + Users.api_token: helper.random_string_generator(32), + Users.enabled: True + }).execute() + +installer = db_builder() diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index fa01405b..92544447 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -38,9 +38,12 @@ class PublicHandler(BaseHandler): self.clear_cookie("user") self.clear_cookie("user_data") - error = bleach.clean(self.get_argument('error', "")) - template = "public/404.html" + # print(page) + if page is None: + self.redirect("public/login") + + error = bleach.clean(self.get_argument('error', "")) if error: error_msg = "Invalid Login!" @@ -57,3 +60,44 @@ class PublicHandler(BaseHandler): self.render(template, data=context) + def post(self, page=None): + + if page == 'login': + next_page = "/public/login" + + entered_email = bleach.clean(self.get_argument('email')) + entered_password = bleach.clean(self.get_argument('password')) + + user_data = Users.get_or_none(Users.email_address == entered_email) + + # if we already have a user with this email... + if not user_data: + next_page = "/public/login?error=Login_Failed" + self.redirect(next_page) + return False + + login_result = helper.verify_pass(entered_password, user_data.password) + + # Valid Login + if login_result: + self.set_current_user(entered_email) + logger.info("User: {} Logged in from IP: {}".format(entered_email, self.get_remote_ip())) + + Users.update({ + Users.last_ip: self.get_remote_ip() + }).execute() + + cookie_data = { + "user_email": user_data.email_address, + "user_id": user_data, + "account_type": str(user_data.account_type).upper(), + + } + + self.set_secure_cookie('user_data', json.dumps(cookie_data)) + + next_page = "/pro/dashboard" + self.redirect(next_page) + else: + self.redirect("/public/login") + diff --git a/app/classes/web/tornado.py b/app/classes/web/tornado.py index cc30f9ac..b4f2fd8d 100644 --- a/app/classes/web/tornado.py +++ b/app/classes/web/tornado.py @@ -84,7 +84,10 @@ class webserver: debug_errors = helper.get_setting("WEB", 'show_errors') cookie_secret = helper.get_setting("WEB", 'cookie_secret') - if cookie_secret.lower == "random" or cookie_secret is False: + if cookie_secret is False: + cookie_secret = helper.random_string_generator(32) + + if cookie_secret.lower == "random": cookie_secret = helper.random_string_generator(32) if not lang: @@ -114,6 +117,7 @@ class webserver: handlers = [ (r'/', PublicHandler), + (r'/public/(.*)', PublicHandler), ] app = tornado.web.Application( diff --git a/app/config/logging.json b/app/config/logging.json index 8bae3e87..927049a1 100644 --- a/app/config/logging.json +++ b/app/config/logging.json @@ -17,7 +17,7 @@ "main_file_handler": { "class": "logging.handlers.RotatingFileHandler", "formatter": "commander", - "filename": "app/logs/commander.log", + "filename": "logs/commander.log", "maxBytes": 5242880, "backupCount": 20, "encoding": "utf8" @@ -25,7 +25,7 @@ "session_file_handler": { "class": "logging.handlers.RotatingFileHandler", "formatter": "commander", - "filename": "app/logs/session.log", + "filename": "logs/session.log", "backupCount": 0, "encoding": "utf8" } diff --git a/app/frontend/templates/public/login.html b/app/frontend/templates/public/login.html index 367892ba..18b4d4ca 100755 --- a/app/frontend/templates/public/login.html +++ b/app/frontend/templates/public/login.html @@ -31,6 +31,7 @@
+ {% raw xsrf_form_html() %}
diff --git a/app/config/config.ini b/config.ini similarity index 100% rename from app/config/config.ini rename to config.ini diff --git a/main.py b/main.py index c1d40acb..8513bf8b 100644 --- a/main.py +++ b/main.py @@ -8,7 +8,7 @@ import logging.config """ Our custom classes / pip packages """ from app.classes.shared.console import console from app.classes.shared.helpers import helper - +from app.classes.shared.models import installer from app.classes.shared.tasks import tasks_manager def do_intro(): @@ -89,6 +89,9 @@ if __name__ == '__main__': # this should always be last tasks_manager.start_main_kill_switch_watcher() + installer.create_tables() + installer.default_settings() + # our main loop while True: if tasks_manager.get_main_thread_run_status():