From 7ddcdd4e0e41f9411bc3bf4fa045f4905211167e Mon Sep 17 00:00:00 2001 From: computergeek125 Date: Sun, 20 Dec 2020 11:40:25 -0500 Subject: [PATCH 1/5] Removed execute bit on a few files --- app/frontend/templates/public/404.html | 0 app/frontend/templates/public/error.html | 0 app/frontend/templates/public/login.html | 0 app/frontend/templates/setup/setup1.html | 0 4 files changed, 0 insertions(+), 0 deletions(-) mode change 100755 => 100644 app/frontend/templates/public/404.html mode change 100755 => 100644 app/frontend/templates/public/error.html mode change 100755 => 100644 app/frontend/templates/public/login.html mode change 100755 => 100644 app/frontend/templates/setup/setup1.html diff --git a/app/frontend/templates/public/404.html b/app/frontend/templates/public/404.html old mode 100755 new mode 100644 diff --git a/app/frontend/templates/public/error.html b/app/frontend/templates/public/error.html old mode 100755 new mode 100644 diff --git a/app/frontend/templates/public/login.html b/app/frontend/templates/public/login.html old mode 100755 new mode 100644 diff --git a/app/frontend/templates/setup/setup1.html b/app/frontend/templates/setup/setup1.html old mode 100755 new mode 100644 From cf61c3c9316b4efed887eadc9d827be926c47d62 Mon Sep 17 00:00:00 2001 From: computergeek125 Date: Thu, 24 Dec 2020 17:55:15 -0500 Subject: [PATCH 2/5] Skeleton user manager added --- app/classes/shared/models.py | 232 ++++++++++++++---- app/classes/web/panel_handler.py | 142 ++++++++++- app/classes/web/public_handler.py | 2 +- .../templates/panel/panel_config.html | 4 +- .../templates/panel/panel_edit_user.html | 219 +++++++++++++++++ 5 files changed, 547 insertions(+), 52 deletions(-) create mode 100644 app/frontend/templates/panel/panel_edit_user.html diff --git a/app/classes/shared/models.py b/app/classes/shared/models.py index 4b3bcb14..bfd8a0cd 100644 --- a/app/classes/shared/models.py +++ b/app/classes/shared/models.py @@ -34,28 +34,46 @@ class Users(BaseModel): created = DateTimeField(default=datetime.datetime.now) last_login = DateTimeField(default=datetime.datetime.now) last_ip = CharField(default="") - username = CharField(default="") + username = CharField(default="", unique=True, index=True) password = CharField(default="") enabled = BooleanField(default=True) - api_token = CharField(default="") - allowed_servers = CharField(default="[]") + superuser = BooleanField(default=False) + api_token = CharField(default="", unique=True, index=True) # we may need to revisit this class Meta: table_name = "users" +class Roles(BaseModel): + role_id = AutoField() + created = DateTimeField(default=datetime.datetime.now) + role_name = CharField(default="", unique=True, index=True) + + class Meta: + table_name = "roles" + + +class User_Roles(BaseModel): + user_id = ForeignKeyField(Users, backref='user_role') + role_id = ForeignKeyField(Roles, backref='user_role') + + class Meta: + table_name = 'user_roles' + primary_key = CompositeKey('user_id', 'role_id') + + class Audit_Log(BaseModel): audit_id = AutoField() created = DateTimeField(default=datetime.datetime.now) user_name = CharField(default="") - user_id = IntegerField(default=0) + user_id = IntegerField(default=0, index=True) source_ip = CharField(default='127.0.0.1') - server_id = IntegerField(default=None) + server_id = IntegerField(default=None, index=True) # When auditing global events, use server ID 0 log_msg = TextField(default='') class Host_Stats(BaseModel): - time = DateTimeField(default=datetime.datetime.now) + time = DateTimeField(default=datetime.datetime.now, index=True) boot_time = CharField(default="") cpu_usage = FloatField(default=0) cpu_cores = IntegerField(default=0) @@ -73,8 +91,8 @@ class Host_Stats(BaseModel): class Servers(BaseModel): server_id = AutoField() created = DateTimeField(default=datetime.datetime.now) - server_uuid = CharField(default="") - server_name = CharField(default="Server") + server_uuid = CharField(default="", index=True) + server_name = CharField(default="Server", index=True) path = CharField(default="") executable = CharField(default="") log_path = CharField(default="") @@ -90,10 +108,28 @@ class Servers(BaseModel): table_name = "servers" +class User_Servers(BaseModel): + user_id = ForeignKeyField(Users, backref='user_server') + server_id = ForeignKeyField(Servers, backref='user_server') + + class Meta: + table_name = 'user_servers' + primary_key = CompositeKey('user_id', 'server_id') + + +class Role_Servers(BaseModel): + user_id = ForeignKeyField(Roles, backref='role_server') + server_id = ForeignKeyField(Servers, backref='role_server') + + class Meta: + table_name = 'role_servers' + primary_key = CompositeKey('role_id', 'server_id') + + class Server_Stats(BaseModel): stats_id = AutoField() created = DateTimeField(default=datetime.datetime.now) - server_id = ForeignKeyField(Servers, backref='server') + server_id = ForeignKeyField(Servers, backref='server', index=True) started = CharField(default="") running = BooleanField(default=False) cpu = FloatField(default=0) @@ -117,8 +153,8 @@ class Server_Stats(BaseModel): class Commands(BaseModel): command_id = AutoField() created = DateTimeField(default=datetime.datetime.now) - server_id = ForeignKeyField(Servers, backref='server') - user = ForeignKeyField(Users, backref='user') + server_id = ForeignKeyField(Servers, backref='server', index=True) + user = ForeignKeyField(Users, backref='user', index=True) source_ip = CharField(default='127.0.0.1') command = CharField(default='') executed = BooleanField(default=False) @@ -129,7 +165,7 @@ class Commands(BaseModel): class Webhooks(BaseModel): id = AutoField() - name = CharField(max_length=64, unique=True) + name = CharField(max_length=64, unique=True, index=True) method = CharField(default="POST") url = CharField(unique=True) event = CharField(default="") @@ -143,7 +179,7 @@ class Backups(BaseModel): directories = CharField() storage_location = CharField() max_backups = IntegerField() - server_id = IntegerField() + server_id = IntegerField(index=True) class Meta: table_name = 'backups' @@ -157,6 +193,8 @@ class db_builder: database.create_tables([ Backups, Users, + Roles, + User_Roles, Host_Stats, Webhooks, Servers, @@ -173,16 +211,18 @@ class db_builder: username = default_data.get("username", 'admin') password = default_data.get("password", 'crafty') - api_token = helper.random_string_generator(32) - - Users.insert({ - Users.username: username.lower(), - Users.password: helper.encode_pass(password), - Users.api_token: api_token, - Users.enabled: True - }).execute() + #api_token = helper.random_string_generator(32) + # + #Users.insert({ + # Users.username: username.lower(), + # Users.password: helper.encode_pass(password), + # Users.api_token: api_token, + # Users.enabled: True, + # Users.superuser: True + #}).execute() + db_shortcuts.add_user(username, password=password, superuser=True) - console.info("API token is {}".format(api_token)) + #console.info("API token is {}".format(api_token)) @staticmethod def is_fresh_install(): @@ -196,7 +236,8 @@ class db_builder: class db_shortcuts: - def return_rows(self, query): + @staticmethod + def return_rows(query): rows = [] try: @@ -209,7 +250,8 @@ class db_shortcuts: return rows - def get_server_data_by_id(self, server_id): + @staticmethod + def get_server_data_by_id(server_id): try: query = Servers.get_by_id(server_id) except DoesNotExist: @@ -217,25 +259,29 @@ class db_shortcuts: return model_to_dict(query) - def get_all_defined_servers(self): + @staticmethod + def get_all_defined_servers(): query = Servers.select() - return self.return_rows(query) + return db_helper.return_rows(query) - def get_all_servers_stats(self): - servers = self.get_all_defined_servers() + @staticmethod + def get_all_servers_stats(): + servers = db_helper.get_all_defined_servers() server_data = [] for s in servers: latest = Server_Stats.select().where(Server_Stats.server_id == s.get('server_id')).order_by(Server_Stats.created.desc()).limit(1) - server_data.append({'server_data': s, "stats": self.return_rows(latest)}) + server_data.append({'server_data': s, "stats": db_helper.return_rows(latest)}) return server_data - def get_server_stats_by_id(self, server_id): + @staticmethod + def get_server_stats_by_id(server_id): stats = Server_Stats.select().where(Server_Stats.server_id == server_id).order_by(Server_Stats.created.desc()).limit(1) - return self.return_rows(stats) + return db_helper.return_rows(stats) - def server_id_exists(self, server_id): - if not self.get_server_data_by_id(server_id): + @staticmethod + def server_id_exists(server_id): + if not db_helper.get_server_data_by_id(server_id): return False return True @@ -244,24 +290,118 @@ class db_shortcuts: query = Host_Stats.select().order_by(Host_Stats.id.desc()).get() return model_to_dict(query) - def get_all_users(self): + @staticmethod + def new_api_token(): + while True: + token = helper.random_string_generator(32) + test = list(Users.select(Users.user_id).where(Users.api_token == token)) + if len(test) == 0: + return token + + @staticmethod + def get_all_users(): query = Users.select() return query - def get_unactioned_commands(self): - query = Commands.select().where(Commands.executed == 0) - return self.return_rows(query) + @staticmethod + def get_all_roles(): + query = Roles.select() + return query - def get_server_friendly_name(self, server_id): - server_data = self.get_server_data_by_id(server_id) + @staticmethod + def get_userid_by_name(username): + try: + return (Users.get(Users.username == username)).user_id + except DoesNotExist: + return None + + @staticmethod + def get_user(uid): + query = Users.select(Users, Roles).join(User_Roles, JOIN.LEFT_OUTER).join(Roles, JOIN.LEFT_OUTER).where(Users.user_id == uid) + query = [model_to_dict(r) for r in query] + if len(query) > 0: + user = query[0].copy() + return user + else: + return {} + + @staticmethod + def update_user(user_id, user_data={}): + base_data = db_helper.get_user(user_id) + up_data = {} + for key in user_data: + if key == "user_id": + continue + elif key == "roles": + continue + elif key == "regen_api": + up_data['api_token'] = db_shortcuts.new_api_token() + elif key == "password": + up_data['password'] = helper.encode_pass(user_data['password']) + elif base_data[key] != user_data[key]: + up_data[key] = user_data[key] + Users.update(up_data).where(Users.user_id == user_id).execute() + + + @staticmethod + def add_user(username, password=None, api_token=None, enabled=True, superuser=False): + if password is not None: + pw_enc = helper.encode_pass(password) + else: + pw_enc = None + if api_token is None: + api_token = db_shortcuts.new_api_token() + else: + if type(api_token) is not str and len(api_token) != 32: + raise ValueError("API token must be a 32 character string") + user_id = Users.insert({ + Users.username: username.lower(), + Users.password: pw_enc, + Users.api_token: api_token, + Users.enabled: enabled, + Users.superuser: superuser + }).execute() + return user_id + + @staticmethod + def remove_user(user_id): + user = Users.get(Users.user_id == user_id) + return user.delete_instance() + + @staticmethod + def user_id_exists(user_id): + if not db_shortcuts.get_user(user_id): + return False + return True + + @staticmethod + def get_roleid_by_name(role_name): + return (Roles.get(Roles.role_name == role_name)).role_id + + @staticmethod + def get_role(role_id): + query = model_to_dict(Roles.get(Roles.role_id == role_id)) + Roles['roles_flat'] = [] + Roles['allowed_servers_flat'] = [] + return query + + @staticmethod + def get_unactioned_commands(): + query = Commands.select().where(Commands.executed == 0) + return db_helper.return_rows(query) + + @staticmethod + def get_server_friendly_name(server_id): + server_data = db_helper.get_server_data_by_id(server_id) friendly_name = "{}-{}".format(server_data.get('server_id', 0), server_data.get('server_name', None)) return friendly_name - def send_command(self, user_id, server_id, remote_ip, command): + @staticmethod + def send_command(user_id, server_id, remote_ip, command): - server_name = self.get_server_friendly_name(server_id) + server_name = db_helper.get_server_friendly_name(server_id) - self.add_to_audit_log(user_id, "Issued Command {} for Server: {}".format(command, server_name), + db_helper.add_to_audit_log(user_id, "Issued Command {} for Server: {}".format(command, server_name), server_id, remote_ip) Commands.insert({ @@ -271,11 +411,13 @@ class db_shortcuts: Commands.command: command }).execute() - def get_actity_log(self): + @staticmethod + def get_actity_log(): q = Audit_Log.select() - return self.return_db_rows(q) + return db_helper.return_db_rows(q) - def return_db_rows(self, model): + @staticmethod + def return_db_rows(model): data = [model_to_dict(row) for row in model] return data diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 873ce834..8c231f7f 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -120,9 +120,57 @@ class PanelHandler(BaseHandler): elif page == 'panel_config': page_data['users'] = db_helper.get_all_users() - # print(page_data['users']) template = "panel/panel_config.html" + elif page == "add_user": + page_data['new_user'] = True + page_data['user'] = {} + page_data['user']['username'] = "" + page_data['user']['user_id'] = -1 + page_data['user']['enabled'] = True + page_data['user']['superuser'] = False + page_data['user']['roles'] = [] + + page_data['roles_all'] = db_helper.get_all_roles() + template = "panel/panel_edit_user.html" + + elif page == "edit_user": + page_data['new_user'] = False + uid = self.get_argument('id', None) + page_data['user'] = db_helper.get_user(uid) + page_data['roles_all'] = db_helper.get_all_roles() + template = "panel/panel_edit_user.html" + + elif page == "remove_user": + user_id = bleach.clean(self.get_argument('id', None)) + + user_data = json.loads(self.get_secure_cookie("user_data")) + exec_user = db_helper.get_user(user_data['user_id']) + + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif user_id is None: + self.redirect("/panel/error?error=Invalid User ID") + return False + else: + # does this user id exist? + target_user = db_helper.get_user(user_id) + if not target_user: + self.redirect("/panel/error?error=Invalid User ID") + return False + elif target_user['superuser']: + self.redirect("/panel/error?error=Cannot remove a superuser") + return False + + db_helper.remove_user(user_id) + + db_helper.add_to_audit_log(exec_user['user_id'], + "Removed user {} (UID:{})".format(target_user['username'], user_id), + server_id=0, + source_ip=self.get_remote_ip()) + self.redirect("/panel/panel_config") + elif page == "activity_logs": page_data['audit_logs'] = db_helper.get_actity_log() @@ -151,7 +199,13 @@ class PanelHandler(BaseHandler): crash_detection = int(float(self.get_argument('crash_detection', '0'))) subpage = self.get_argument('subpage', None) - if server_id is None: + user_data = json.loads(self.get_secure_cookie("user_data")) + exec_user = db_helper.get_user(user_data['user_id']) + + if not exec_user.superuser: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif server_id is None: self.redirect("/panel/error?error=Invalid Server ID") return False else: @@ -178,11 +232,91 @@ class PanelHandler(BaseHandler): controller.refresh_server_settings(server_id) - user_data = json.loads(self.get_secure_cookie("user_data")) - db_helper.add_to_audit_log(user_data['user_id'], "Edited server {} named {}".format(server_id, server_name), server_id, self.get_remote_ip()) self.redirect("/panel/server_detail?id={}&subpage=config".format(server_id)) + + elif page == "edit_user": + user_id = bleach.clean(self.get_argument('id', None)) + username = bleach.clean(self.get_argument('username', None)) + password0 = bleach.clean(self.get_argument('password0', None)) + password1 = bleach.clean(self.get_argument('password1', None)) + enabled = int(float(bleach.clean(self.get_argument('enabled'), '0'))) + regen_api = int(float(bleach.clean(self.get_argument('regen_api', '0')))) + + user_data = json.loads(self.get_secure_cookie("user_data")) + exec_user = db_helper.get_user(user_data['user_id']) + + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif username is None or username == "": + self.redirect("/panel/error?error=Invalid username") + return False + elif user_id is None: + self.redirect("/panel/error?error=Invalid User ID") + return False + else: + # does this user id exist? + if not db_helper.user_id_exists(user_id): + self.redirect("/panel/error?error=Invalid User ID") + return False + + if password0 != password1: + self.redirect("/panel/error?error=Passwords must match") + return False + + user_data = { + "username": username, + "password": password0, + "enabled": enabled, + "regen_api": regen_api, + "roles": [] + } + db_helper.update_user(user_id, user_data=user_data) + + db_helper.add_to_audit_log(exec_user['user_id'], + "Edited user {} (UID:{})".format(username, user_id), + server_id=0, + source_ip=self.get_remote_ip()) + self.redirect("/panel/panel_config") + + + elif page == "add_user": + username = bleach.clean(self.get_argument('username', None)) + password0 = bleach.clean(self.get_argument('password0', None)) + password1 = bleach.clean(self.get_argument('password1', None)) + enabled = int(float(bleach.clean(self.get_argument('enabled'), '0'))) + + user_data = json.loads(self.get_secure_cookie("user_data")) + exec_user = db_helper.get_user(user_data['user_id']) + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif username is None or username == "": + self.redirect("/panel/error?error=Invalid username") + return False + else: + # does this user id exist? + if db_helper.get_userid_by_name(username) is not None: + self.redirect("/panel/error?error=User exists") + return False + + if password0 != password1: + self.redirect("/panel/error?error=Passwords must match") + return False + + user_id = db_helper.add_user(username, password=password0, enabled=enabled) + + db_helper.add_to_audit_log(exec_user['user_id'], + "Added user {} (UID:{})".format(username, user_id), + server_id=0, + source_ip=self.get_remote_ip()) + db_helper.add_to_audit_log(exec_user['user_id'], + "Edited user {} (UID:{})".format(username, user_id), + server_id=0, + source_ip=self.get_remote_ip()) + self.redirect("/panel/panel_config") \ No newline at end of file diff --git a/app/classes/web/public_handler.py b/app/classes/web/public_handler.py index 34356510..7f968677 100644 --- a/app/classes/web/public_handler.py +++ b/app/classes/web/public_handler.py @@ -106,7 +106,7 @@ class PublicHandler(BaseHandler): cookie_data = { "username": user_data.username, "user_id": user_data.user_id, - "account_type": user_data.allowed_servers, + "account_type": user_data.superuser, } self.set_secure_cookie('user_data', json.dumps(cookie_data)) diff --git a/app/frontend/templates/panel/panel_config.html b/app/frontend/templates/panel/panel_config.html index b91859de..107ba8fb 100644 --- a/app/frontend/templates/panel/panel_config.html +++ b/app/frontend/templates/panel/panel_config.html @@ -66,8 +66,8 @@ {{ user.api_token }} - {{ user.allowed_servers}} - + {{ [] }} + {% end %} diff --git a/app/frontend/templates/panel/panel_edit_user.html b/app/frontend/templates/panel/panel_edit_user.html new file mode 100644 index 00000000..c5d5df18 --- /dev/null +++ b/app/frontend/templates/panel/panel_edit_user.html @@ -0,0 +1,219 @@ +{% extends ../base.html %} + +{% block meta %} + +{% end %} + +{% block title %}Crafty Controller - Edit User{% end %} + +{% block content %} + +
+ + +
+
+ +
+ +
+ + +
+ +
+
+
+ + +
+
+ {% if data['new_user'] %} +
+ {% else %} + + {% end %} + {% raw xsrf_form_html() %} + + + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + + + + + + + {% for role in data['roles_all'] %} + + + + + {% end %} + + +
Role NameMember?
{{ role.role_name }} + {% if role.role_name in data['user']['roles'] %} + + Yes + + {% else %} + + No + + + {% end %} + +
+
+
+ +
+ + + + + + +
+ + + +
+
+ +
+
+
+

User Config Area

+

Here is where you can change the configuration of your user

+
+

+ It is recommended to NOT change the paths of a server managed by Crafty. + Changing paths CAN break things, especially on Linux type operating systems where + file permissions are more locked down. +

+ If you feel you have to change a where a server is located + you may do so as long as you give the "Crafty" user permission to read / write to the server path. +
+
+ On Linux this is best done by executing the following:
+ + sudo chown crafty:crafty /path/to/your/server -R
+ sudo chmod 2775 /path/to/your/server -R
+
+

+
+
+
+
+ {% if data['new_user'] %} + Delete User
+ You cannot delete something that does not yet exist + {% elif data['user']['superuser'] %} + Delete User
+ You cannot delete a superuser + {% else %} + Delete User + {% end %} + +
+
+
+ +
+
+
+
+ + + +
+ + +{% end %} + +{% block js %} + + +{% end %} \ No newline at end of file From 8bba57d9506fc11a083d94bb0f195e13b203448a Mon Sep 17 00:00:00 2001 From: computergeek125 Date: Fri, 25 Dec 2020 15:40:55 -0500 Subject: [PATCH 3/5] Added exception handler if the initial request fails --- app/classes/minecraft/serverjars.py | 40 +++++++++++++++++------------ 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py index 282a55a9..9a1a2505 100644 --- a/app/classes/minecraft/serverjars.py +++ b/app/classes/minecraft/serverjars.py @@ -25,13 +25,19 @@ except ModuleNotFoundError as e: class ServerJars: + def __init__(self): + self.base_url = "https://serverjars.com" + def _get_api_result(self, call_url: str): - base_url = "https://serverjars.com" - full_url = "{base}{call_url}".format(base=base_url, call_url=call_url) + full_url = "{base}{call_url}".format(base=self.base_url, call_url=call_url) - r = requests.get(full_url, timeout=2) + try: + r = requests.get(full_url, timeout=2) - if r.status_code not in [200, 201]: + if r.status_code not in [200, 201]: + return {} + except Exception as e: + logger.error("Unable to connect to serverjar.com api due to error: {}".format(e)) return {} try: @@ -66,18 +72,21 @@ class ServerJars: data = self._read_cache() return data.get('servers') - @staticmethod - def _check_api_alive(): + def _check_api_alive(self): logger.info("Checking serverjars.com API status") - check_url = "https://serverjars.com/api/fetchTypes" - r = requests.get(check_url, timeout=2) + check_url = "{base}/api/fetchTypes".format(base=self.base_url) + try: + r = requests.get(check_url, timeout=2) - if r.status_code in [200, 201]: - logger.info("Serverjars.com API is alive") - return True + if r.status_code in [200, 201]: + logger.info("Serverjars.com API is alive") + return True + except Exception as e: + logger.error("Unable to connect to serverjar.com api due to error: {}".format(e)) + return {} - logger.error("unable to contact Serverjars.com api") + logger.error("unable to contact serverjars.com api") return False def refresh_cache(self): @@ -141,12 +150,11 @@ class ServerJars: response = self._get_api_result(url) return response - @staticmethod - def download_jar(server, version, path): - base_url = "https://serverjars.com/api/fetchJar/{server}/{version}".format(server=server, version=version) + def download_jar(self, server, version, path): + fetch_url = "{base}/api/fetchJar/{server}/{version}".format(base=self.base_url, server=server, version=version) # open a file stream - with requests.get(base_url, timeout=2, stream=True) as r: + with requests.get(fetch_url, timeout=2, stream=True) as r: try: with open(path, 'wb') as output: shutil.copyfileobj(r.raw, output) From d91361efa5400be647757528183f05e6220a87fa Mon Sep 17 00:00:00 2001 From: computergeek125 Date: Fri, 25 Dec 2020 22:00:28 -0500 Subject: [PATCH 4/5] Updated user manager to support server selection --- app/classes/shared/models.py | 58 +++++++++++++--- app/classes/web/panel_handler.py | 66 +++++++++++++++++-- .../templates/panel/panel_edit_user.html | 62 ++++++++++------- 3 files changed, 148 insertions(+), 38 deletions(-) diff --git a/app/classes/shared/models.py b/app/classes/shared/models.py index bfd8a0cd..063b45d2 100644 --- a/app/classes/shared/models.py +++ b/app/classes/shared/models.py @@ -118,7 +118,7 @@ class User_Servers(BaseModel): class Role_Servers(BaseModel): - user_id = ForeignKeyField(Roles, backref='role_server') + role_id = ForeignKeyField(Roles, backref='role_server') server_id = ForeignKeyField(Servers, backref='role_server') class Meta: @@ -198,6 +198,8 @@ class db_builder: Host_Stats, Webhooks, Servers, + User_Servers, + Role_Servers, Server_Stats, Commands, Audit_Log @@ -316,31 +318,67 @@ class db_shortcuts: return None @staticmethod - def get_user(uid): - query = Users.select(Users, Roles).join(User_Roles, JOIN.LEFT_OUTER).join(Roles, JOIN.LEFT_OUTER).where(Users.user_id == uid) - query = [model_to_dict(r) for r in query] - if len(query) > 0: - user = query[0].copy() + def get_user(user_id): + user = model_to_dict(Users.get(Users.user_id == user_id)) + + if user: + roles_query = User_Roles.select().join(Roles, JOIN.INNER).where(User_Roles.user_id == user_id) + # TODO: this query needs to be narrower + roles = set() + for r in roles_query: + roles.add(r.role_id.role_id) + servers_query = User_Servers.select().join(Servers, JOIN.INNER).where(User_Servers.user_id == user_id) + # TODO: this query needs to be narrower + servers = set() + for s in servers_query: + servers.add(s.server_id.server_id) + user['roles'] = roles + user['servers'] = servers + logger.debug("user: ({}) {}".format(user_id, user)) return user else: + logger.debug("user: ({}) {}".format(user_id, {})) return {} @staticmethod def update_user(user_id, user_data={}): base_data = db_helper.get_user(user_id) up_data = {} + added_roles = set() + removed_roles = set() + added_servers = set() + removed_servers = set() for key in user_data: if key == "user_id": continue elif key == "roles": - continue + added_roles = user_data['roles'].difference(base_data['roles']) + removed_roles = base_data['roles'].difference(user_data['roles']) + elif key == "servers": + added_servers = user_data['servers'].difference(base_data['servers']) + removed_servers = base_data['servers'].difference(user_data['servers']) elif key == "regen_api": - up_data['api_token'] = db_shortcuts.new_api_token() + if user_data['regen_api']: + up_data['api_token'] = db_shortcuts.new_api_token() elif key == "password": - up_data['password'] = helper.encode_pass(user_data['password']) + if user_data['password'] is not None and user_data['password'] != "": + up_data['password'] = helper.encode_pass(user_data['password']) elif base_data[key] != user_data[key]: up_data[key] = user_data[key] - Users.update(up_data).where(Users.user_id == user_id).execute() + logger.debug("user: {} +role:{} -role:{} +server:{} -server{}".format(user_data, added_roles, removed_roles, added_servers, removed_servers)) + with database.atomic(): + for role in added_roles: + User_Roles.get_or_create(user_id=user_id, role_id=role) + # TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point + User_Roles.delete().where(User_Roles.user_id == user_id).where(User_Roles.role_id.in_(removed_roles)).execute() + + for server in added_servers: + User_Servers.get_or_create(user_id=user_id, server_id=server) + # TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point + User_Servers.delete().where(User_Servers.user_id == user_id).where(User_Servers.server_id.in_(removed_servers)).execute() + if up_data: + Users.update(up_data).where(Users.user_id == user_id).execute() + @staticmethod diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 8c231f7f..716f8f75 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -120,6 +120,10 @@ class PanelHandler(BaseHandler): elif page == 'panel_config': page_data['users'] = db_helper.get_all_users() + exec_user = db_helper.get_user(user_data['user_id']) + for user in page_data['users']: + if user.user_id != exec_user['user_id']: + user.api_token = "********" template = "panel/panel_config.html" elif page == "add_user": @@ -129,9 +133,15 @@ class PanelHandler(BaseHandler): page_data['user']['user_id'] = -1 page_data['user']['enabled'] = True page_data['user']['superuser'] = False - page_data['user']['roles'] = [] + page_data['user']['api_token'] = "N/A" + page_data['user']['created'] = "N/A" + page_data['user']['last_login'] = "N/A" + page_data['user']['last_ip'] = "N/A" + page_data['user']['roles'] = set() + page_data['user']['servers'] = set() page_data['roles_all'] = db_helper.get_all_roles() + page_data['servers_all'] = controller.list_defined_servers() template = "panel/panel_edit_user.html" elif page == "edit_user": @@ -139,6 +149,12 @@ class PanelHandler(BaseHandler): uid = self.get_argument('id', None) page_data['user'] = db_helper.get_user(uid) page_data['roles_all'] = db_helper.get_all_roles() + page_data['servers_all'] = controller.list_defined_servers() + + exec_user = db_helper.get_user(user_data['user_id']) + + if exec_user['user_id'] != page_data['user']['user_id']: + page_data['user']['api_token'] = "********" template = "panel/panel_edit_user.html" elif page == "remove_user": @@ -269,17 +285,38 @@ class PanelHandler(BaseHandler): self.redirect("/panel/error?error=Passwords must match") return False + roles = set() + for server in db_helper.get_all_roles(): + argument = int(float( + bleach.clean( + self.get_argument('role_{}_membership'.format(role['role_id']), '0') + ) + )) + if argument: + servers.add(role['role_id']) + + servers = set() + for server in controller.list_defined_servers(): + argument = int(float( + bleach.clean( + self.get_argument('server_{}_access'.format(server['server_id']), '0') + ) + )) + if argument: + servers.add(server['server_id']) + user_data = { "username": username, "password": password0, "enabled": enabled, "regen_api": regen_api, - "roles": [] + "roles": roles, + "servers": servers } db_helper.update_user(user_id, user_data=user_data) db_helper.add_to_audit_log(exec_user['user_id'], - "Edited user {} (UID:{})".format(username, user_id), + "Edited user {} (UID:{}) with roles {} and servers {}".format(username, user_id, roles, servers), server_id=0, source_ip=self.get_remote_ip()) self.redirect("/panel/panel_config") @@ -309,14 +346,35 @@ class PanelHandler(BaseHandler): self.redirect("/panel/error?error=Passwords must match") return False + roles = set() + for server in db_helper.get_all_roles(): + argument = int(float( + bleach.clean( + self.get_argument('role_{}_membership'.format(role['role_id']), '0') + ) + )) + if argument: + roles.add(role['role_id']) + + servers = set() + for server in controller.list_defined_servers(): + argument = int(float( + bleach.clean( + self.get_argument('server_{}_access'.format(server['server_id']), '0') + ) + )) + if argument: + servers.add(server['server_id']) + user_id = db_helper.add_user(username, password=password0, enabled=enabled) + db_helper.update_user(user_id, {"roles":roles, "servers": servers}) db_helper.add_to_audit_log(exec_user['user_id'], "Added user {} (UID:{})".format(username, user_id), server_id=0, source_ip=self.get_remote_ip()) db_helper.add_to_audit_log(exec_user['user_id'], - "Edited user {} (UID:{})".format(username, user_id), + "Edited user {} (UID:{}) with roles {} and servers {}".format(username, user_id, roles, servers), server_id=0, source_ip=self.get_remote_ip()) self.redirect("/panel/panel_config") \ No newline at end of file diff --git a/app/frontend/templates/panel/panel_edit_user.html b/app/frontend/templates/panel/panel_edit_user.html index c5d5df18..9f01988a 100644 --- a/app/frontend/templates/panel/panel_edit_user.html +++ b/app/frontend/templates/panel/panel_edit_user.html @@ -75,11 +75,6 @@ -
- - -
-
@@ -95,15 +90,10 @@ {{ role.role_name }} - {% if role.role_name in data['user']['roles'] %} - - Yes - + {% if role['role_id'] in data['user']['roles'] %} + {% else %} - - No - - + {% end %} @@ -115,6 +105,35 @@
+
+ +
+ + + + + + + + + {% for server in data['servers_all'] %} + + + + + {% end %} + + +
Server NameAccess?
{{ server['server_name'] }} + {% if server['server_id'] in data['user']['servers'] %} + + {% else %} + + {% end %} +
+
+
+
From 8131cfd396b6371ffd11a9b1810708a9a5ddf699 Mon Sep 17 00:00:00 2001 From: computergeek125 Date: Sun, 27 Dec 2020 11:00:26 -0500 Subject: [PATCH 5/5] Added skeleton role manager --- app/classes/shared/models.py | 74 ++++++- app/classes/web/panel_handler.py | 182 +++++++++++++++++- .../templates/panel/panel_config.html | 36 ++++ .../templates/panel/panel_edit_role.html | 159 +++++++++++++++ .../templates/panel/panel_edit_user.html | 8 +- 5 files changed, 438 insertions(+), 21 deletions(-) create mode 100644 app/frontend/templates/panel/panel_edit_role.html diff --git a/app/classes/shared/models.py b/app/classes/shared/models.py index 063b45d2..e7719051 100644 --- a/app/classes/shared/models.py +++ b/app/classes/shared/models.py @@ -33,6 +33,7 @@ class Users(BaseModel): user_id = AutoField() created = DateTimeField(default=datetime.datetime.now) last_login = DateTimeField(default=datetime.datetime.now) + last_update = DateTimeField(default=datetime.datetime.now) last_ip = CharField(default="") username = CharField(default="", unique=True, index=True) password = CharField(default="") @@ -47,6 +48,7 @@ class Users(BaseModel): class Roles(BaseModel): role_id = AutoField() created = DateTimeField(default=datetime.datetime.now) + last_update = DateTimeField(default=datetime.datetime.now) role_name = CharField(default="", unique=True, index=True) class Meta: @@ -365,6 +367,7 @@ class db_shortcuts: up_data['password'] = helper.encode_pass(user_data['password']) elif base_data[key] != user_data[key]: up_data[key] = user_data[key] + up_data['last_update'] = helper.get_time_as_string() logger.debug("user: {} +role:{} -role:{} +server:{} -server{}".format(user_data, added_roles, removed_roles, added_servers, removed_servers)) with database.atomic(): for role in added_roles: @@ -379,8 +382,6 @@ class db_shortcuts: if up_data: Users.update(up_data).where(Users.user_id == user_id).execute() - - @staticmethod def add_user(username, password=None, api_token=None, enabled=True, superuser=False): if password is not None: @@ -397,7 +398,8 @@ class db_shortcuts: Users.password: pw_enc, Users.api_token: api_token, Users.enabled: enabled, - Users.superuser: superuser + Users.superuser: superuser, + Users.created: helper.get_time_as_string() }).execute() return user_id @@ -414,14 +416,70 @@ class db_shortcuts: @staticmethod def get_roleid_by_name(role_name): - return (Roles.get(Roles.role_name == role_name)).role_id + try: + return (Roles.get(Roles.role_name == role_name)).role_id + except DoesNotExist: + return None @staticmethod def get_role(role_id): - query = model_to_dict(Roles.get(Roles.role_id == role_id)) - Roles['roles_flat'] = [] - Roles['allowed_servers_flat'] = [] - return query + role = model_to_dict(Roles.get(Roles.role_id == role_id)) + + if role: + servers_query = Role_Servers.select().join(Servers, JOIN.INNER).where(Role_Servers.role_id == role_id) + # TODO: this query needs to be narrower + servers = set() + for s in servers_query: + servers.add(s.server_id.server_id) + role['servers'] = servers + logger.debug("role: ({}) {}".format(role_id, role)) + return role + else: + logger.debug("role: ({}) {}".format(role_id, {})) + return {} + + @staticmethod + def update_role(role_id, role_data={}): + base_data = db_helper.get_role(role_id) + up_data = {} + added_servers = set() + removed_servers = set() + for key in role_data: + if key == "role_id": + continue + elif key == "servers": + added_servers = role_data['servers'].difference(base_data['servers']) + removed_servers = base_data['servers'].difference(role_data['servers']) + elif base_data[key] != role_data[key]: + up_data[key] = role_data[key] + up_data['last_update'] = helper.get_time_as_string() + logger.debug("role: {} +server:{} -server{}".format(role_data, added_servers, removed_servers)) + with database.atomic(): + for server in added_servers: + Role_Servers.get_or_create(role_id=role_id, server_id=server) + # TODO: This is horribly inefficient and we should be using bulk queries but im going for functionality at this point + Role_Servers.delete().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id.in_(removed_servers)).execute() + if up_data: + Roles.update(up_data).where(Roles.role_id == role_id).execute() + + @staticmethod + def add_role(role_name): + role_id = Roles.insert({ + Roles.role_name: role_name.lower(), + Roles.created: helper.get_time_as_string() + }).execute() + return role_id + + @staticmethod + def remove_role(role_id): + role = Roles.get(Roles.role_id == role_id) + return role.delete_instance() + + @staticmethod + def role_id_exists(role_id): + if not db_shortcuts.get_role(role_id): + return False + return True @staticmethod def get_unactioned_commands(): diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 716f8f75..3d31a8b4 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -120,6 +120,7 @@ class PanelHandler(BaseHandler): elif page == 'panel_config': page_data['users'] = db_helper.get_all_users() + page_data['roles'] = db_helper.get_all_roles() exec_user = db_helper.get_user(user_data['user_id']) for user in page_data['users']: if user.user_id != exec_user['user_id']: @@ -137,22 +138,36 @@ class PanelHandler(BaseHandler): page_data['user']['created'] = "N/A" page_data['user']['last_login'] = "N/A" page_data['user']['last_ip'] = "N/A" + page_data['role']['last_update'] = "N/A" page_data['user']['roles'] = set() page_data['user']['servers'] = set() + exec_user = db_helper.get_user(user_data['user_id']) + + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + page_data['roles_all'] = db_helper.get_all_roles() page_data['servers_all'] = controller.list_defined_servers() template = "panel/panel_edit_user.html" elif page == "edit_user": page_data['new_user'] = False - uid = self.get_argument('id', None) - page_data['user'] = db_helper.get_user(uid) + user_id = self.get_argument('id', None) + page_data['user'] = db_helper.get_user(user_id) page_data['roles_all'] = db_helper.get_all_roles() page_data['servers_all'] = controller.list_defined_servers() exec_user = db_helper.get_user(user_data['user_id']) - + + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif user_id is None: + self.redirect("/panel/error?error=Invalid User ID") + return False + if exec_user['user_id'] != page_data['user']['user_id']: page_data['user']['api_token'] = "********" template = "panel/panel_edit_user.html" @@ -162,7 +177,7 @@ class PanelHandler(BaseHandler): user_data = json.loads(self.get_secure_cookie("user_data")) exec_user = db_helper.get_user(user_data['user_id']) - + if not exec_user['superuser']: self.redirect("/panel/error?error=Unauthorized access: not superuser") return False @@ -187,6 +202,68 @@ class PanelHandler(BaseHandler): source_ip=self.get_remote_ip()) self.redirect("/panel/panel_config") + elif page == "add_role": + page_data['new_role'] = True + page_data['role'] = {} + page_data['role']['role_name'] = "" + page_data['role']['role_id'] = -1 + page_data['role']['created'] = "N/A" + page_data['role']['last_update'] = "N/A" + page_data['role']['servers'] = set() + + exec_user = db_helper.get_user(user_data['user_id']) + + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + + page_data['servers_all'] = controller.list_defined_servers() + template = "panel/panel_edit_role.html" + + elif page == "edit_role": + page_data['new_role'] = False + role_id = self.get_argument('id', None) + page_data['role'] = db_helper.get_role(role_id) + page_data['servers_all'] = controller.list_defined_servers() + + exec_user = db_helper.get_user(user_data['user_id']) + + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif role_id is None: + self.redirect("/panel/error?error=Invalid Role ID") + return False + + template = "panel/panel_edit_role.html" + + elif page == "remove_role": + role_id = bleach.clean(self.get_argument('id', None)) + + user_data = json.loads(self.get_secure_cookie("user_data")) + exec_user = db_helper.get_user(user_data['user_id']) + + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif role_id is None: + self.redirect("/panel/error?error=Invalid Role ID") + return False + else: + # does this user id exist? + target_role = db_helper.get_user(role_id) + if not target_role: + self.redirect("/panel/error?error=Invalid Role ID") + return False + + db_helper.remove_role(role_id) + + db_helper.add_to_audit_log(exec_user['user_id'], + "Removed role {} (RID:{})".format(target_role['role_name'], role_id), + server_id=0, + source_ip=self.get_remote_ip()) + self.redirect("/panel/panel_config") + elif page == "activity_logs": page_data['audit_logs'] = db_helper.get_actity_log() @@ -265,7 +342,7 @@ class PanelHandler(BaseHandler): user_data = json.loads(self.get_secure_cookie("user_data")) exec_user = db_helper.get_user(user_data['user_id']) - + if not exec_user['superuser']: self.redirect("/panel/error?error=Unauthorized access: not superuser") return False @@ -286,14 +363,14 @@ class PanelHandler(BaseHandler): return False roles = set() - for server in db_helper.get_all_roles(): + for role in db_helper.get_all_roles(): argument = int(float( bleach.clean( - self.get_argument('role_{}_membership'.format(role['role_id']), '0') + self.get_argument('role_{}_membership'.format(role.role_id), '0') ) )) if argument: - servers.add(role['role_id']) + roles.add(role.role_id) servers = set() for server in controller.list_defined_servers(): @@ -347,10 +424,10 @@ class PanelHandler(BaseHandler): return False roles = set() - for server in db_helper.get_all_roles(): + for role in db_helper.get_all_roles(): argument = int(float( bleach.clean( - self.get_argument('role_{}_membership'.format(role['role_id']), '0') + self.get_argument('role_{}_membership'.format(role.role_id), '0') ) )) if argument: @@ -377,4 +454,89 @@ class PanelHandler(BaseHandler): "Edited user {} (UID:{}) with roles {} and servers {}".format(username, user_id, roles, servers), server_id=0, source_ip=self.get_remote_ip()) + self.redirect("/panel/panel_config") + + elif page == "edit_role": + role_id = bleach.clean(self.get_argument('id', None)) + role_name = bleach.clean(self.get_argument('role_name', None)) + + user_data = json.loads(self.get_secure_cookie("user_data")) + exec_user = db_helper.get_user(user_data['user_id']) + + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif role_name is None or role_name == "": + self.redirect("/panel/error?error=Invalid username") + return False + elif role_id is None: + self.redirect("/panel/error?error=Invalid Role ID") + return False + else: + # does this user id exist? + if not db_helper.role_id_exists(role_id): + self.redirect("/panel/error?error=Invalid Role ID") + return False + + servers = set() + for server in controller.list_defined_servers(): + argument = int(float( + bleach.clean( + self.get_argument('server_{}_access'.format(server['server_id']), '0') + ) + )) + if argument: + servers.add(server['server_id']) + + role_data = { + "role_name": role_name, + "servers": servers + } + db_helper.update_role(role_id, role_data=role_data) + + db_helper.add_to_audit_log(exec_user['user_id'], + "Edited role {} (RID:{}) with servers {}".format(role_name, role_id, servers), + server_id=0, + source_ip=self.get_remote_ip()) + self.redirect("/panel/panel_config") + + + elif page == "add_role": + role_name = bleach.clean(self.get_argument('role_name', None)) + + user_data = json.loads(self.get_secure_cookie("user_data")) + exec_user = db_helper.get_user(user_data['user_id']) + if not exec_user['superuser']: + self.redirect("/panel/error?error=Unauthorized access: not superuser") + return False + elif role_name is None or role_name == "": + self.redirect("/panel/error?error=Invalid role name") + return False + else: + # does this user id exist? + if db_helper.get_roleid_by_name(role_name) is not None: + self.redirect("/panel/error?error=Role exists") + return False + + servers = set() + for server in controller.list_defined_servers(): + argument = int(float( + bleach.clean( + self.get_argument('server_{}_access'.format(server['server_id']), '0') + ) + )) + if argument: + servers.add(server['server_id']) + + role_id = db_helper.add_role(role_name) + db_helper.update_role(role_id, {"servers": servers}) + + db_helper.add_to_audit_log(exec_user['user_id'], + "Added role {} (RID:{})".format(role_name, role_id), + server_id=0, + source_ip=self.get_remote_ip()) + db_helper.add_to_audit_log(exec_user['user_id'], + "Edited role {} (RID:{}) with servers {}".format(role_name, role_id, servers), + server_id=0, + source_ip=self.get_remote_ip()) self.redirect("/panel/panel_config") \ No newline at end of file diff --git a/app/frontend/templates/panel/panel_config.html b/app/frontend/templates/panel/panel_config.html index 107ba8fb..37153ed2 100644 --- a/app/frontend/templates/panel/panel_config.html +++ b/app/frontend/templates/panel/panel_config.html @@ -73,11 +73,47 @@ + +
+
+
+
+

Roles

+
+ Can't see everything on mobile?
Try scrolling the table sideways.
+
+ +
+
+
+ + + + + + + + + + {% for role in data['roles'] %} + + + + + + {% end %} + +
RoleAllowed ServersEdit
{{ role.role_name }}{{ [] }}
+
+
+
+
+
diff --git a/app/frontend/templates/panel/panel_edit_role.html b/app/frontend/templates/panel/panel_edit_role.html new file mode 100644 index 00000000..241f98cb --- /dev/null +++ b/app/frontend/templates/panel/panel_edit_role.html @@ -0,0 +1,159 @@ +{% extends ../base.html %} + +{% block meta %} + +{% end %} + +{% block title %}Crafty Controller - Edit Role{% end %} + +{% block content %} + +
+ + +
+
+ +
+ +
+ + +
+ +
+
+
+ + +
+
+ {% if data['new_role'] %} +
+ {% else %} + + {% end %} + {% raw xsrf_form_html() %} + + + +
+ + +
+ +
+ +
+ + + + + + + + + {% for server in data['servers_all'] %} + + + + + {% end %} + + +
Server NameAccess?
{{ server['server_name'] }} + {% if server['server_id'] in data['role']['servers'] %} + + {% else %} + + {% end %} +
+
+
+ + + +
+
+ +
+
+
+

Role Config Area

+

Here is where you can change the configuration of your role

+
+

+ Created: {{ str(data['role']['created']) }} +
+ Last updated: {{ str(data['role']['last_update']) }} +
+

+
+
+
+
+ {% if data['new_role'] %} + Delete Role
+ You cannot delete something that does not yet exist + {% else %} + Delete Role + {% end %} + +
+
+
+ +
+
+
+
+ + + +
+ + +{% end %} + +{% block js %} + + +{% end %} \ No newline at end of file diff --git a/app/frontend/templates/panel/panel_edit_user.html b/app/frontend/templates/panel/panel_edit_user.html index 9f01988a..a23cbed6 100644 --- a/app/frontend/templates/panel/panel_edit_user.html +++ b/app/frontend/templates/panel/panel_edit_user.html @@ -90,10 +90,10 @@ {{ role.role_name }} - {% if role['role_id'] in data['user']['roles'] %} - + {% if role.role_id in data['user']['roles'] %} + {% else %} - + {% end %} @@ -177,6 +177,8 @@
Last login: {{ str(data['user']['last_login']) }}
+ Last update: {{ str(data['user']['last_update']) }} +
Last IP: {{ data['user']['last_ip'] }}
API Key: {{ data['user']['api_token'] }}