From 8ed215e89be65770af50102cf2aeff54c2b9f72f Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 11:05:37 +0200 Subject: [PATCH 01/79] Create sqlite3 connector class to handle all the errors and quirks for us --- private/lib/sqlitedb.py | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 private/lib/sqlitedb.py diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py new file mode 100644 index 0000000..6544cc5 --- /dev/null +++ b/private/lib/sqlitedb.py @@ -0,0 +1,71 @@ +#!/usr/bin/env python3 +import sqlite3 +from sys import stderr as STDERR + + +# create dictionary out of sqlite results +def dict_factory(cursor, row): + d = {} + for idx, col in enumerate(cursor.description): + d[col[0]] = row[idx] + return d + + +class SQLitedb: + db = "" + cursor = None + connection = None + lastrow = None + + def __init__(self, dbpath: str): + db = dbpath + try: + self.connection = sqlite3.connect(db) + self.cursor = self.connection.cursor() + except sqlite3.Error as e: + print("Connection error: %s" % e, file=STDERR) + + self.cursor.row_factory = dict_factory # every result will be a dict now + + def __del__(self): + try: + self.connection.close() + except sqlite3.Error as e: + print("Couldn't gracefully close db: %s" % e, file=STDERR) + + def query(self, qq: str): + try: + self.cursor.execute(qq) + self.lastrow = self.cursor.fetchall() + except sqlite3.Error as e: + print("Couldn't execute query %s, exception: %s" % (qq, e), file=STDERR) + self.lastrow = [] + return self.lastrow + + # sometimes we need the cursor for safety reasons, for example does sqlite3 all the security related + # escaoing in supplied strings for us, when we deliver it to con.execute in the second argument as a tuple + def getCursor(self): + return self.cursor + + # we could try to utilise that ourselfs in a function. Be c a r e f u l, these values in the tuple MUST HAVE + # THE RIGHT TYPE + def safequery(self, qq: str, deliver: tuple): + try: + self.cursor.execute(qq, deliver) + self.lastrow = self.cursor.fetchall() + except sqlite3.Error as e: + print("Couldn't execute query %s, exception: %s" % (qq, e), file=STDERR) + self.lastrow = [] + except TypeError as e: + print("Types in given tuple doesnt match to execute query \"%s\": %s" % (qq, e), file=STDERR) + self.lastrow = [] + return self.lastrow + + +if __name__ == "__main__": + try: + SQLitedb("bla.db") + print("hi") + exit(0) + except KeyboardInterrupt: + pass From 96837cebe305f9ae4172d5cb7413ecc4ad533805 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 11:39:19 +0200 Subject: [PATCH 02/79] Externalize CFG-Handling into lib/CFG.py. Never write this code again! --- private/lib/CFG.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 private/lib/CFG.py diff --git a/private/lib/CFG.py b/private/lib/CFG.py new file mode 100644 index 0000000..b957d8a --- /dev/null +++ b/private/lib/CFG.py @@ -0,0 +1,22 @@ +import configparser, logging, argparse, os + +cwd = os.environ.get('TILDE_CONF') +if cwd is None: + cwd = os.getcwd() + "/applicationsconfig.ini" +else: + if os.path.isfile(cwd) is False: + cwd = os.getcwd() + "/applicationsconfig.ini" +# cwd is now either cwd/applicationsconfig or $TILDE_CONF +argparser = argparse.ArgumentParser(description='interactive registration formular for tilde platforms') +argparser.add_argument('-c', '--config', default=cwd, + type=str, help='Path to configuration file', required=False) +args = argparser.parse_args() + +CONF_FILE = args.config +config = configparser.ConfigParser() +config.read(CONF_FILE) +logging.basicConfig(format="%(asctime)s: %(message)s", + level=int(config['LOG_LEVEL']['log_level']) + ) +del cwd +REG_FILE = config['DEFAULT']['applications_db'] \ No newline at end of file From 2eeeebdacba7dbcf7daea303af7fbe1e2aabc2cc Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 11:54:29 +0200 Subject: [PATCH 03/79] Add sceleton for listusers.py --- private/ListUsers.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 private/ListUsers.py diff --git a/private/ListUsers.py b/private/ListUsers.py new file mode 100644 index 0000000..43bd036 --- /dev/null +++ b/private/ListUsers.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python3 + +from lib.sqlitedb import SQLitedb +import lib.CFG as CFG + +if __name__ == "__main__": + try: + SQLitedb(CFG.REG_FILE) + print("hi") + exit(0) + except KeyboardInterrupt: + pass From 39158439431d65eca2911f753e2d60ef2b08cb7d Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 12:26:13 +0200 Subject: [PATCH 04/79] introducing the -u flag, which turns on the 'unapproved' mode. Oh, and just testing it in ListUsers.py :) --- private/ListUsers.py | 4 ++++ private/lib/CFG.py | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 43bd036..81bf72d 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -6,6 +6,10 @@ import lib.CFG as CFG if __name__ == "__main__": try: SQLitedb(CFG.REG_FILE) + if CFG.args.unapproved: + print("yes! Only unapproved ones!") + else: + print("Just approved ones") print("hi") exit(0) except KeyboardInterrupt: diff --git a/private/lib/CFG.py b/private/lib/CFG.py index b957d8a..861b052 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -7,9 +7,12 @@ else: if os.path.isfile(cwd) is False: cwd = os.getcwd() + "/applicationsconfig.ini" # cwd is now either cwd/applicationsconfig or $TILDE_CONF -argparser = argparse.ArgumentParser(description='interactive registration formular for tilde platforms') +argparser = argparse.ArgumentParser(description='Tilde administration tools') argparser.add_argument('-c', '--config', default=cwd, type=str, help='Path to configuration file', required=False) +# store_true just stores true when the command is supplied, so it doesn't need choices nor types +argparser.add_argument('-u', '--unapproved', default=False, action="store_true", + help='List only unapproved users', required=False) args = argparser.parse_args() CONF_FILE = args.config From f06d4fa94563b7a14200054eb91fe4694e438c43 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 13:34:01 +0200 Subject: [PATCH 05/79] first steps towards real listings! --- private/ListUsers.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 81bf72d..423be6e 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -3,13 +3,24 @@ from lib.sqlitedb import SQLitedb import lib.CFG as CFG + +class ListUsers: + db = None + usersFetch = None + + def __init__(self, ap: bool): + self.db = SQLitedb(CFG.REG_FILE) + if not ap: + query = "SELECT * FROM `applications` WHERE status = '1'" + else: + query = "SELECT * FROM `applications` WHERE status = '0'" + self.usersFetch = self.db.query(query) + print(self.usersFetch) + + if __name__ == "__main__": try: - SQLitedb(CFG.REG_FILE) - if CFG.args.unapproved: - print("yes! Only unapproved ones!") - else: - print("Just approved ones") + L = ListUsers(CFG.args.unapproved) print("hi") exit(0) except KeyboardInterrupt: From b368ea058c5d231afedc4b57c51e7cb9955ce2d4 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 13:55:22 +0200 Subject: [PATCH 06/79] huh, that already looks close to good... o.o --- private/ListUsers.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 423be6e..1abee08 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -15,13 +15,37 @@ class ListUsers: else: query = "SELECT * FROM `applications` WHERE status = '0'" self.usersFetch = self.db.query(query) - print(self.usersFetch) + + def getFetch(self): + return self.usersFetch if __name__ == "__main__": try: L = ListUsers(CFG.args.unapproved) - print("hi") + fetch = L.getFetch() + # MAYBE best solution: https://pypi.org/project/texttable/ + # examle: + """ +from texttable import Texttable +t = Texttable() +t.add_rows([['Name', 'Age'], ['Alice', 24], ['Bob', 19]]) +print(t.draw()) +---------------> Results in: + ++-------+-----+ +| Name | Age | ++=======+=====+ +| Alice | 24 | ++-------+-----+ +| Bob | 19 | ++-------+-----+ + + """ + for user in fetch: + print("ID: {}; Username: \"{}\"; Mail: <{}>; Name: \"{}\"; Registered: {}; Status: {}".format( + user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] + )) exit(0) except KeyboardInterrupt: pass From cab4cd25a2d8506c09ab0b81d0aca682bb6402f4 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 14:21:26 +0200 Subject: [PATCH 07/79] Well, that alternative view rocks hard... --- private/ListUsers.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 1abee08..8314d0f 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -16,6 +16,9 @@ class ListUsers: query = "SELECT * FROM `applications` WHERE status = '0'" self.usersFetch = self.db.query(query) + def prettyPrint(self): + pass # see below why not implemented yet, texttable... + def getFetch(self): return self.usersFetch @@ -41,9 +44,16 @@ print(t.draw()) | Bob | 19 | +-------+-----+ - """ + for user in fetch: + print("ID: {}; Username: \"{}\"; Mail: {}; Name: \"{}\"; Registered: {}; Status: {}".format( + user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] + ))""" + print("ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s| State |" % ( + " ", " ", " ", " ", " " + )) + print(101*"-") for user in fetch: - print("ID: {}; Username: \"{}\"; Mail: <{}>; Name: \"{}\"; Registered: {}; Status: {}".format( + print("%-4i| %-14s| %-25s| %-22s| %-8s| %-6i|" % ( user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] )) exit(0) From c40ef4d40a6fa2ec179a96d4e72273afe5e1a05c Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 14:27:10 +0200 Subject: [PATCH 08/79] Max username length is 16 now --- public/userapplication.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/userapplication.py b/public/userapplication.py index b3e96ca..e751717 100755 --- a/public/userapplication.py +++ b/public/userapplication.py @@ -82,6 +82,9 @@ def check_username(value): if len(value) < 3: VALID_USER=False return False + if len(value) > 16: + VALID_USER=False + return False try: from pwd import getpwnam getpwnam(value) @@ -125,7 +128,8 @@ def main(): username = input("Welcome to the ~.fun user application form!\n\nWhat is your desired username? [a-z0-9] allowed:\n") while (not re.match("[a-z]+[a-z0-9]", username)) or (not check_username(username)): username = input("Invalid Username, maybe it exists already?\nValid characters are only a-z and 0-9." - "\nMake sure your username starts with a character and not a number." + "\nMake sure your username starts with a character and not a number" + " and is not larger than 16 characters." "\nWhat is your desired username? [a-z0-9] allowed:\n") fullname = input("\nPlease enter your full name:\n") From 6e86bf11bbb6dc29a2b5edac6e1c3b9b4905ccb0 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 12 Oct 2019 19:21:33 +0200 Subject: [PATCH 09/79] -a Flag: default false, do action on only approved users --- private/ListUsers.py | 12 +++++++----- private/lib/CFG.py | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 8314d0f..0e190db 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -8,12 +8,14 @@ class ListUsers: db = None usersFetch = None - def __init__(self, ap: bool): + def __init__(self, uap: bool = CFG.args.unapproved, a: bool = CFG.args.approved): self.db = SQLitedb(CFG.REG_FILE) - if not ap: - query = "SELECT * FROM `applications` WHERE status = '1'" - else: + if uap: # only unapproved users query = "SELECT * FROM `applications` WHERE status = '0'" + elif a: # Approved users + query = "SELECT * FROM `applications` WHERE status = '1'" + else: # All users + query = "SELECT * FROM `applications`" self.usersFetch = self.db.query(query) def prettyPrint(self): @@ -25,7 +27,7 @@ class ListUsers: if __name__ == "__main__": try: - L = ListUsers(CFG.args.unapproved) + L = ListUsers() fetch = L.getFetch() # MAYBE best solution: https://pypi.org/project/texttable/ # examle: diff --git a/private/lib/CFG.py b/private/lib/CFG.py index 861b052..d8efe95 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -12,7 +12,9 @@ argparser.add_argument('-c', '--config', default=cwd, type=str, help='Path to configuration file', required=False) # store_true just stores true when the command is supplied, so it doesn't need choices nor types argparser.add_argument('-u', '--unapproved', default=False, action="store_true", - help='List only unapproved users', required=False) + help='only unapproved users. Default is only approved.', required=False) +argparser.add_argument('-a', '--approved', default=False, action="store_true", + help="Only approved Users.", required=False) args = argparser.parse_args() CONF_FILE = args.config From 4134e3cc2e34acf0c2b9f59744b5958acd20ede6 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 10:42:15 +0200 Subject: [PATCH 10/79] introduces -f/--file, which outputs or takes from a file(conditional to the current program) --- private/Backup.py | 0 private/lib/CFG.py | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 private/Backup.py diff --git a/private/Backup.py b/private/Backup.py new file mode 100644 index 0000000..e69de29 diff --git a/private/lib/CFG.py b/private/lib/CFG.py index d8efe95..7c596e8 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -15,6 +15,8 @@ argparser.add_argument('-u', '--unapproved', default=False, action="store_true", help='only unapproved users. Default is only approved.', required=False) argparser.add_argument('-a', '--approved', default=False, action="store_true", help="Only approved Users.", required=False) +argparser.add_argument('-f', '--file', default="stdout", + type=str, help='write to file instead of stdout', required=False) args = argparser.parse_args() CONF_FILE = args.config From a984b14c370d980997e05e62229f292183d46e56 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 10:46:25 +0200 Subject: [PATCH 11/79] Implement -f/--file option. Defaults to stdout --- private/ListUsers.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 0e190db..5be3512 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -8,11 +8,11 @@ class ListUsers: db = None usersFetch = None - def __init__(self, uap: bool = CFG.args.unapproved, a: bool = CFG.args.approved): + def __init__(self, uap: bool = CFG.args.unapproved, app: bool = CFG.args.approved): self.db = SQLitedb(CFG.REG_FILE) if uap: # only unapproved users query = "SELECT * FROM `applications` WHERE status = '0'" - elif a: # Approved users + elif app: # Approved users query = "SELECT * FROM `applications` WHERE status = '1'" else: # All users query = "SELECT * FROM `applications`" @@ -27,9 +27,10 @@ class ListUsers: if __name__ == "__main__": try: + ret = "" L = ListUsers() fetch = L.getFetch() - # MAYBE best solution: https://pypi.org/project/texttable/ + # @TODO MAYBE best solution: https://pypi.org/project/texttable/ # examle: """ from texttable import Texttable @@ -50,14 +51,19 @@ print(t.draw()) print("ID: {}; Username: \"{}\"; Mail: {}; Name: \"{}\"; Registered: {}; Status: {}".format( user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] ))""" - print("ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s| State |" % ( + ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s| State |\n" % ( " ", " ", " ", " ", " " - )) - print(101*"-") + ) + ret += 101*"-" + "\n" for user in fetch: - print("%-4i| %-14s| %-25s| %-22s| %-8s| %-6i|" % ( + ret += "%-4i| %-14s| %-25s| %-22s| %-8s| %-6i|\n" % ( user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] - )) + ) + if CFG.args.file != "stdout": + with open(CFG.args.file, 'w') as f: + print(ret, file=f) + else: + print(ret) exit(0) except KeyboardInterrupt: pass From 90b2ee5236163a16585738b4e4cebca10735dd9c Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 10:52:48 +0200 Subject: [PATCH 12/79] add gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f5a8070 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__/ +.idea/ +test* + From 2afb4c79a2d8c89ccfbd42eb406d2a736e65004e Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 11:01:08 +0200 Subject: [PATCH 13/79] Implements a backup to csv. Uses StringIO because it has an own writer() method, which is pretty nice to have when csv.writer() want's that on its passed variable. Also respects every flag yet introduced(-c, -f, -a, -u) and reuses the code already written in ListUsers.py. It could be very nice to bring that code into lib/ because it is probably needed way more often --- private/Backup.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/private/Backup.py b/private/Backup.py index e69de29..f60d444 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + +from lib.sqlitedb import SQLitedb +import lib.CFG as CFG +import ListUsers, csv, os, io + +if __name__ == "__main__": + try: + L = ListUsers.ListUsers() + fetch = L.getFetch() + ret = io.StringIO() + writer = csv.writer(ret, quoting=csv.QUOTE_NONNUMERIC) # @TODO: Should be a specific dialect instead? + writer.writerow(['id', 'username', 'email', 'name', 'pubkey' 'timestamp', 'status']) + for user in fetch: + writer.writerow([user['id'], user['username'], user['email'], user['name'], user['pubkey'], + user['timestamp'], user['status']]) + + if CFG.args.file == "stdout": + print(ret.getvalue()) + else: + with open(CFG.args.file, "w") as f: + print(ret.getvalue(), file=f) + exit(0) + except KeyboardInterrupt as e: + pass \ No newline at end of file From 89faf57b58636893b57dbf7c64991a4887ce5b03 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 17:16:48 +0200 Subject: [PATCH 14/79] System: Still a lot @TODO here, but add it. Should(TM) work, but didn't test it yet and also have to do a whole lot more. e.g. write out in dry-mode which commands are getting to run in serious-mode but build up together correctly in the correct order --- private/lib/System.py | 108 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 private/lib/System.py diff --git a/private/lib/System.py b/private/lib/System.py new file mode 100644 index 0000000..4546b92 --- /dev/null +++ b/private/lib/System.py @@ -0,0 +1,108 @@ +import sys, os, subprocess, pwd +""" + @staticmethod + def __execScript(user): + # @TODO: omfg just write some wrapper-class/lib... sucks hard! + username = user["username"] + home_dir = "/home/" + username + "/" + ssh_dir = home_dir + ".ssh/" + executed = [] +""" + + +class System: + dry = False + create_command = [] + home = "" + + def __init__(self, dryrun: bool = False, home: str = "/home/"): + self.dry = dryrun + self.home = home + + def register(self, username: str, pubkey: str, cc: tuple = tuple(["useradd", "-m"])): + create_command = cc + cc = create_command + tuple([username]) + if self.dry: + self.printTuple(cc) + return 0 + elif not self.dry: + rt = subprocess.call(cc) + if rt != 0: + print(f"Could not create user {username}; '{cc}' returned '{rt}'") # @TODO Logging/Exception + return False + + def unregister(self, username: str): + pass + + def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/"): + if self.dry: + print("Nah, @TODO, but actually kinda too lazy for this lul. Just a lot happening here") + return True + if not sshdir.endswith("/"): + return False # @TODO Exception in Log + ssh_dir = self.home + username + "/" + sshdir + try: + os.mkdir(ssh_dir) + except FileExistsError: + pass # thats actually a good one for us :D + except OSError as e: + print(f"Could not create {ssh_dir}: Exception: {e}") + return False + with open(ssh_dir + "authorized_keys", "w") as f: + print(pubkey, file=f) + f.close() + try: + os.chmod(ssh_dir + "authorized_keys", 0o700) # directory is already 777? + os.chmod(ssh_dir, 0o700) # directory is already 777? + except OSError as e: + print(f"Could not chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}") + return False + try: + pwdnam = pwd.getpwnam(username) + os.chown(ssh_dir, pwdnam[2], pwdnam[3]) # 2=>uid, 3=>gid + os.chown(ssh_dir + "authorized_keys", pwd.getpwnam(username)[2], pwd.getpwnam(username)[3]) + except OSError as e: + print(f"Could not chown {ssh_dir} and/or authorized_keys to {username} and their group, Exception: {e}") + return False + return True + + def lock_user_pw(self, username: str, cc: tuple = tuple(["usermod", "--lock"])): + lock_command = cc + cc = lock_command + tuple([username]) + if self.dry: + self.printTuple(cc) + return 0 + elif not self.dry: + rt = subprocess.call(cc) + if rt != 0: + print(f"Could not lock user '{username}'; '{cc}' returned '{rt}'") + + def add_to_usergroup(self, username: str, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])): + add_command = cc + cc = add_command + tuple([group, username]) + if self.dry: + self.printTuple(cc) + return 0 + elif not self.dry: + rt = subprocess.call(cc) + if rt != 0: + print(f"Could not add user '{username}' to group '{group}' with command '{cc}', returned '{rt}'") + + def printTuple(self, tup: tuple): + pp = "" + for i in tup: + pp += i + " " + print(pp) + + +if __name__ == "__main__": + try: + S = System(dryrun=True) + S.register("dar", "test") + S.lock_user_pw("dar") + S.add_to_usergroup("dar") + #if not S.make_ssh_usable("dar", "SSHpub"): + # print("Huh, error :shrug:") + exit(0) + except KeyboardInterrupt: + pass From c43cad73fa30f32b2bebe181a3220b49e44f6fb7 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 17:35:04 +0200 Subject: [PATCH 15/79] Print Error messages to stderr instead of standard-out. Makes very much sense indeed! --- private/lib/System.py | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/private/lib/System.py b/private/lib/System.py index 4546b92..ef8f579 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -1,13 +1,4 @@ import sys, os, subprocess, pwd -""" - @staticmethod - def __execScript(user): - # @TODO: omfg just write some wrapper-class/lib... sucks hard! - username = user["username"] - home_dir = "/home/" + username + "/" - ssh_dir = home_dir + ".ssh/" - executed = [] -""" class System: @@ -28,12 +19,14 @@ class System: elif not self.dry: rt = subprocess.call(cc) if rt != 0: - print(f"Could not create user {username}; '{cc}' returned '{rt}'") # @TODO Logging/Exception + print(f"Could not create user {username}; '{cc}' returned '{rt}'", file=sys.stderr) + # @TODO Logging/Exception return False def unregister(self, username: str): pass + # @TODO errno def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/"): if self.dry: print("Nah, @TODO, but actually kinda too lazy for this lul. Just a lot happening here") @@ -46,7 +39,7 @@ class System: except FileExistsError: pass # thats actually a good one for us :D except OSError as e: - print(f"Could not create {ssh_dir}: Exception: {e}") + print(f"Could not create {ssh_dir}: Exception: {e}", file=sys.stderr) return False with open(ssh_dir + "authorized_keys", "w") as f: print(pubkey, file=f) @@ -55,14 +48,15 @@ class System: os.chmod(ssh_dir + "authorized_keys", 0o700) # directory is already 777? os.chmod(ssh_dir, 0o700) # directory is already 777? except OSError as e: - print(f"Could not chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}") + print(f"Could not chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}", file=sys.stderr) return False try: pwdnam = pwd.getpwnam(username) os.chown(ssh_dir, pwdnam[2], pwdnam[3]) # 2=>uid, 3=>gid os.chown(ssh_dir + "authorized_keys", pwd.getpwnam(username)[2], pwd.getpwnam(username)[3]) except OSError as e: - print(f"Could not chown {ssh_dir} and/or authorized_keys to {username} and their group, Exception: {e}") + print(f"Could not chown {ssh_dir} and/or authorized_keys to {username} and their group, Exception: {e}", + file=sys.stderr) return False return True @@ -75,7 +69,7 @@ class System: elif not self.dry: rt = subprocess.call(cc) if rt != 0: - print(f"Could not lock user '{username}'; '{cc}' returned '{rt}'") + print(f"Could not lock user '{username}'; '{cc}' returned '{rt}'", file=sys.stderr) def add_to_usergroup(self, username: str, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])): add_command = cc @@ -86,7 +80,8 @@ class System: elif not self.dry: rt = subprocess.call(cc) if rt != 0: - print(f"Could not add user '{username}' to group '{group}' with command '{cc}', returned '{rt}'") + print(f"Could not add user '{username}' to group '{group}' with command '{cc}', returned '{rt}'", + file=sys.stderr) def printTuple(self, tup: tuple): pp = "" From a6d63fee42de4d4210d3d373a8907b02f5bba56b Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 17:37:34 +0200 Subject: [PATCH 16/79] PEP8: One Import per line... -.- Add TODO statements and foremost return False on error. Missed that --- private/Backup.py | 5 +++-- private/lib/CFG.py | 5 ++++- private/lib/System.py | 23 +++++++++++++++-------- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index f60d444..2333c70 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 -from lib.sqlitedb import SQLitedb +import ListUsers +import csv +import io import lib.CFG as CFG -import ListUsers, csv, os, io if __name__ == "__main__": try: diff --git a/private/lib/CFG.py b/private/lib/CFG.py index 7c596e8..270e0c5 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -1,4 +1,7 @@ -import configparser, logging, argparse, os +import argparse +import configparser +import logging +import os cwd = os.environ.get('TILDE_CONF') if cwd is None: diff --git a/private/lib/System.py b/private/lib/System.py index ef8f579..ee11d78 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -1,4 +1,7 @@ -import sys, os, subprocess, pwd +import sys +import os +import subprocess +import pwd class System: @@ -41,15 +44,16 @@ class System: except OSError as e: print(f"Could not create {ssh_dir}: Exception: {e}", file=sys.stderr) return False - with open(ssh_dir + "authorized_keys", "w") as f: - print(pubkey, file=f) - f.close() try: + with open(ssh_dir + "authorized_keys", "w") as f: + print(pubkey, file=f) + f.close() os.chmod(ssh_dir + "authorized_keys", 0o700) # directory is already 777? os.chmod(ssh_dir, 0o700) # directory is already 777? except OSError as e: - print(f"Could not chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}", file=sys.stderr) - return False + print(f"Could not write and/or chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}", + file=sys.stderr) + return False # @TODO Exception in Log try: pwdnam = pwd.getpwnam(username) os.chown(ssh_dir, pwdnam[2], pwdnam[3]) # 2=>uid, 3=>gid @@ -57,7 +61,7 @@ class System: except OSError as e: print(f"Could not chown {ssh_dir} and/or authorized_keys to {username} and their group, Exception: {e}", file=sys.stderr) - return False + return False # @TODO Exception in Log return True def lock_user_pw(self, username: str, cc: tuple = tuple(["usermod", "--lock"])): @@ -70,6 +74,8 @@ class System: rt = subprocess.call(cc) if rt != 0: print(f"Could not lock user '{username}'; '{cc}' returned '{rt}'", file=sys.stderr) + return False + # @TODO Exception in Log def add_to_usergroup(self, username: str, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])): add_command = cc @@ -81,7 +87,8 @@ class System: rt = subprocess.call(cc) if rt != 0: print(f"Could not add user '{username}' to group '{group}' with command '{cc}', returned '{rt}'", - file=sys.stderr) + file=sys.stderr) # @TODO Exception in Log + return False def printTuple(self, tup: tuple): pp = "" From d413662b36951316aa853a29e491a07b60b47cbd Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 18:49:38 +0200 Subject: [PATCH 17/79] Adds ability to delete users in db as well as on the system + type hints.. .... in function names for further documentation. --- private/ListUsers.py | 5 +++-- private/lib/System.py | 32 ++++++++++++++++++++++++-------- private/lib/sqlitedb.py | 41 ++++++++++++++++++++++++++++++----------- 3 files changed, 57 insertions(+), 21 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 5be3512..6deb520 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import sqlite3 from lib.sqlitedb import SQLitedb import lib.CFG as CFG @@ -18,10 +19,10 @@ class ListUsers: query = "SELECT * FROM `applications`" self.usersFetch = self.db.query(query) - def prettyPrint(self): + def prettyPrint(self) -> None: pass # see below why not implemented yet, texttable... - def getFetch(self): + def getFetch(self) -> sqlite3: return self.usersFetch diff --git a/private/lib/System.py b/private/lib/System.py index ee11d78..354a4ff 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -13,24 +13,25 @@ class System: self.dry = dryrun self.home = home - def register(self, username: str, pubkey: str, cc: tuple = tuple(["useradd", "-m"])): + def register(self, username: str, pubkey: str, cc: tuple = tuple(["useradd", "-m"])) -> bool: create_command = cc cc = create_command + tuple([username]) if self.dry: self.printTuple(cc) - return 0 + return True elif not self.dry: rt = subprocess.call(cc) if rt != 0: print(f"Could not create user {username}; '{cc}' returned '{rt}'", file=sys.stderr) # @TODO Logging/Exception return False + return True def unregister(self, username: str): pass # @TODO errno - def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/"): + def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/") -> bool: if self.dry: print("Nah, @TODO, but actually kinda too lazy for this lul. Just a lot happening here") return True @@ -64,38 +65,53 @@ class System: return False # @TODO Exception in Log return True - def lock_user_pw(self, username: str, cc: tuple = tuple(["usermod", "--lock"])): + def lock_user_pw(self, username: str, cc: tuple = tuple(["usermod", "--lock"])) -> bool: lock_command = cc cc = lock_command + tuple([username]) if self.dry: self.printTuple(cc) - return 0 + return True elif not self.dry: rt = subprocess.call(cc) if rt != 0: print(f"Could not lock user '{username}'; '{cc}' returned '{rt}'", file=sys.stderr) return False # @TODO Exception in Log + return True - def add_to_usergroup(self, username: str, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])): + def add_to_usergroup(self, username: str, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])) -> bool: add_command = cc cc = add_command + tuple([group, username]) if self.dry: self.printTuple(cc) - return 0 + return True elif not self.dry: rt = subprocess.call(cc) if rt != 0: print(f"Could not add user '{username}' to group '{group}' with command '{cc}', returned '{rt}'", file=sys.stderr) # @TODO Exception in Log return False + return True - def printTuple(self, tup: tuple): + def printTuple(self, tup: tuple) -> None: pp = "" for i in tup: pp += i + " " print(pp) + def removeUser(self, username: str, cc: tuple = tuple(["userdel", "-r"])) -> bool: + remove_command = cc + cc = remove_command + tuple([username]) + if self.dry: + self.printTuple(cc) + return True + else: + ret = subprocess.call(cc) + if ret != 0: + print(f"Could not delete user with command {cc}. Return code: {ret}") + return False + return True + if __name__ == "__main__": try: diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py index 6544cc5..fed0953 100644 --- a/private/lib/sqlitedb.py +++ b/private/lib/sqlitedb.py @@ -15,7 +15,7 @@ class SQLitedb: db = "" cursor = None connection = None - lastrow = None + last_result = None def __init__(self, dbpath: str): db = dbpath @@ -29,37 +29,56 @@ class SQLitedb: def __del__(self): try: + self.connection.commit() self.connection.close() except sqlite3.Error as e: print("Couldn't gracefully close db: %s" % e, file=STDERR) - def query(self, qq: str): + def query(self, qq: str) -> list: try: self.cursor.execute(qq) - self.lastrow = self.cursor.fetchall() + self.last_result = self.cursor.fetchall() except sqlite3.Error as e: print("Couldn't execute query %s, exception: %s" % (qq, e), file=STDERR) - self.lastrow = [] - return self.lastrow + self.last_result = [] + return self.last_result # sometimes we need the cursor for safety reasons, for example does sqlite3 all the security related # escaoing in supplied strings for us, when we deliver it to con.execute in the second argument as a tuple - def getCursor(self): + def getCursor(self) -> sqlite3: return self.cursor # we could try to utilise that ourselfs in a function. Be c a r e f u l, these values in the tuple MUST HAVE # THE RIGHT TYPE - def safequery(self, qq: str, deliver: tuple): + def safequery(self, qq: str, deliver: tuple) -> list: try: self.cursor.execute(qq, deliver) - self.lastrow = self.cursor.fetchall() + self.last_result = self.cursor.fetchall() except sqlite3.Error as e: print("Couldn't execute query %s, exception: %s" % (qq, e), file=STDERR) - self.lastrow = [] + self.last_result = [] except TypeError as e: print("Types in given tuple doesnt match to execute query \"%s\": %s" % (qq, e), file=STDERR) - self.lastrow = [] - return self.lastrow + self.last_result = [] + return self.last_result + + def removeApplicantFromDB(self, userid: int) -> bool: + try: + self.last_result = self.cursor.execute("DELETE FROM `applications` WHERE id = ? ", [userid]) + self.connection.commit() + except sqlite3.Error as e: + print(f"Could not delete user with id: {userid}, exception in DB: {e}") # @TODO LOGGING FFS + return False + return True + + def removeApplicantFromDBperUsername(self, username: str) -> bool: + try: + self.last_result = self.cursor.execute("DELETE FROM `applications` WHERE username = ?", [username]) + self.connection.commit() + except sqlite3.Error as e: + print(f"Could not delete user {username}, exception in DB: {e}") # @TODO LOGGING + return False + return True if __name__ == "__main__": From 290e72f15938009aaed1d5d47f52a87ee7478cc4 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 19:20:25 +0200 Subject: [PATCH 18/79] Add various documentation to the System class and sqlitedb-Class... ... which are all written in rst-format, which sphynx understands and has an own formal specification in http://docutils.sourceforge.net/rst.html --- private/ListUsers.py | 8 +++- private/lib/System.py | 81 ++++++++++++++++++++++++++++++++++++++--- private/lib/sqlitedb.py | 40 ++++++++++++++++++++ 3 files changed, 122 insertions(+), 7 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 6deb520..428d54a 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import sqlite3 from lib.sqlitedb import SQLitedb import lib.CFG as CFG @@ -22,7 +21,12 @@ class ListUsers: def prettyPrint(self) -> None: pass # see below why not implemented yet, texttable... - def getFetch(self) -> sqlite3: + def getFetch(self) -> list: + """ Returns a complete fetch done by the sqlitedb-class + + :return: Complete fetchall() in a dict-factory + :rtype: list + """ return self.usersFetch diff --git a/private/lib/System.py b/private/lib/System.py index 354a4ff..a32f281 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -5,15 +5,34 @@ import pwd class System: + """Class to interact with the system specifically to support our needs 0w0""" dry = False create_command = [] home = "" def __init__(self, dryrun: bool = False, home: str = "/home/"): + """Creates an objects. Can set dry run. + + :param dryrun: Run all command in a dry-run? When enabled, doesn't make any changes to the system (defaults to + false) + :type dryrun: bool + :param home: Standard directory to search for the home directories of your users(default is /home/) + :type home: str + """ self.dry = dryrun self.home = home - def register(self, username: str, pubkey: str, cc: tuple = tuple(["useradd", "-m"])) -> bool: + def register(self, username: str, cc: tuple = tuple(["useradd", "-m"])) -> bool: + """Creates an local account for the given username + + :param username: Username to create + :type username: str + :param cc: Tuple with commands separated to execute on the machine. (defaults to useradd -m) + :type cc: tuple + :returns: True, when the user was successfully created, False when not + :rtype: bool + """ + create_command = cc cc = create_command + tuple([username]) if self.dry: @@ -32,16 +51,28 @@ class System: # @TODO errno def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/") -> bool: + """ Make SSH usable for our newly registered user + + :param username: Username you want to affect with it, casually used directly after register() + :type username: str + :param pubkey: Public SSH Key for the User you want accessible by SSH + :type pubkey: str + :param sshdir: Directory to write the authorized_keys File to. PWD is $HOME of said user. (defaults to ".ssh/") + :type sshdir: str + :return: True, when everything worked out good, false when something bad happened. Outputs the error of it. + :rtype: bool + """ + if self.dry: print("Nah, @TODO, but actually kinda too lazy for this lul. Just a lot happening here") return True if not sshdir.endswith("/"): - return False # @TODO Exception in Log + return False # @TODO Exception in Log ssh_dir = self.home + username + "/" + sshdir try: os.mkdir(ssh_dir) except FileExistsError: - pass # thats actually a good one for us :D + pass # thats actually a good one for us :D except OSError as e: print(f"Could not create {ssh_dir}: Exception: {e}", file=sys.stderr) return False @@ -57,7 +88,7 @@ class System: return False # @TODO Exception in Log try: pwdnam = pwd.getpwnam(username) - os.chown(ssh_dir, pwdnam[2], pwdnam[3]) # 2=>uid, 3=>gid + os.chown(ssh_dir, pwdnam[2], pwdnam[3]) # 2=>uid, 3=>gid os.chown(ssh_dir + "authorized_keys", pwd.getpwnam(username)[2], pwd.getpwnam(username)[3]) except OSError as e: print(f"Could not chown {ssh_dir} and/or authorized_keys to {username} and their group, Exception: {e}", @@ -66,6 +97,16 @@ class System: return True def lock_user_pw(self, username: str, cc: tuple = tuple(["usermod", "--lock"])) -> bool: + """Lock a users password so it stays empty + + :param username: Username of the user which accounts password you want to lock + :type username: str + :param cc: Commands to run in the subprocess to lock it down(defaults to usermod --lock) + :type cc: tuple + :rtype: bool + :return: True when the lock worked, false when not. + """ + lock_command = cc cc = lock_command + tuple([username]) if self.dry: @@ -80,6 +121,18 @@ class System: return True def add_to_usergroup(self, username: str, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])) -> bool: + """ Adds a given user to a given group + + :param username: Username to add to your wanted group + :type username: str + :param group: Groupname where you want to add your user to + :type group: str + :param cc: Commands to execute that adds your user to said specific group(defaults to usermod -a -G") + :type cc: tuple + :return: True, if worked, False when not. + :rtype bool + """ + add_command = cc cc = add_command + tuple([group, username]) if self.dry: @@ -94,12 +147,30 @@ class System: return True def printTuple(self, tup: tuple) -> None: + """Prints a tuple with spaces as separators + + :param tup: Tuple you want to print + :type tup: tuple + :rtype: None + :returns: Nothing + """ + pp = "" for i in tup: pp += i + " " print(pp) def removeUser(self, username: str, cc: tuple = tuple(["userdel", "-r"])) -> bool: + """Removes the specified user from the system + + :param username: The username you want to delete from the system. + :type username: str + :param cc: Commands to execute to delete the user from the System(defaults to userdel -r) + :type cc: tuple + :return: True, when worked, False if not. + :rtype: bool + """ + remove_command = cc cc = remove_command + tuple([username]) if self.dry: @@ -116,7 +187,7 @@ class System: if __name__ == "__main__": try: S = System(dryrun=True) - S.register("dar", "test") + S.register("dar") S.lock_user_pw("dar") S.add_to_usergroup("dar") #if not S.make_ssh_usable("dar", "SSHpub"): diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py index fed0953..d457cf9 100644 --- a/private/lib/sqlitedb.py +++ b/private/lib/sqlitedb.py @@ -12,12 +12,21 @@ def dict_factory(cursor, row): class SQLitedb: + """SQLitedb handles EVERYTHING directly related to our Database.""" + db = "" cursor = None connection = None last_result = None def __init__(self, dbpath: str): + """ + :param dbpath: Path to the database we want to open + :type dbpath: str + :returns: Object for the SQLitedb-Class. + :rtype: object + """ + db = dbpath try: self.connection = sqlite3.connect(db) @@ -35,6 +44,13 @@ class SQLitedb: print("Couldn't gracefully close db: %s" % e, file=STDERR) def query(self, qq: str) -> list: + """Do a query and automagically get the fetched results in a list + :param qq: Query to execute + :type qq: str + :returns: A tuple(/list) consisting with any fetched results + :rtype: list + """ + try: self.cursor.execute(qq) self.last_result = self.cursor.fetchall() @@ -46,11 +62,21 @@ class SQLitedb: # sometimes we need the cursor for safety reasons, for example does sqlite3 all the security related # escaoing in supplied strings for us, when we deliver it to con.execute in the second argument as a tuple def getCursor(self) -> sqlite3: + """Returns SQLite3 Cursor. Use with **c a u t i o n**... """ return self.cursor # we could try to utilise that ourselfs in a function. Be c a r e f u l, these values in the tuple MUST HAVE # THE RIGHT TYPE def safequery(self, qq: str, deliver: tuple) -> list: + """ Shall handle any query that has user input in it as an alternative to self.query + :param qq: Query to execute + :type qq: str + :param deliver: User inputs marked with the placeholder(`?`) in the str + :type deliver: tuple + :returns: A tuple(/list) consisting with any fetched results + :rtype: list + """ + try: self.cursor.execute(qq, deliver) self.last_result = self.cursor.fetchall() @@ -63,6 +89,13 @@ class SQLitedb: return self.last_result def removeApplicantFromDB(self, userid: int) -> bool: + """Removes Applicants from the DB by ID. Use along System.removeUser() + :param userid: User ID to remove from the Database + :type userid: int + :returns: True, if removal was successful(from the DB), False when not + :rtype: bool + """ + try: self.last_result = self.cursor.execute("DELETE FROM `applications` WHERE id = ? ", [userid]) self.connection.commit() @@ -72,6 +105,13 @@ class SQLitedb: return True def removeApplicantFromDBperUsername(self, username: str) -> bool: + """Removes Applicants from the DB by Username. Use along System.removeUser() + :param username: Username to remove from the database + :type username: str + :returns: True, if removal was successful(from the DB), False when not + :rtype: bool + """ + try: self.last_result = self.cursor.execute("DELETE FROM `applications` WHERE username = ?", [username]) self.connection.commit() From 43c763692018b565ef43268914c34f7932b30d50 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 21:12:17 +0200 Subject: [PATCH 19/79] Make PEP8 finally happy --- private/Backup.py | 2 +- private/lib/CFG.py | 2 +- private/lib/System.py | 5 +++-- private/lib/sqlitedb.py | 14 +++++++------- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index 2333c70..9a33128 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -23,4 +23,4 @@ if __name__ == "__main__": print(ret.getvalue(), file=f) exit(0) except KeyboardInterrupt as e: - pass \ No newline at end of file + pass diff --git a/private/lib/CFG.py b/private/lib/CFG.py index 270e0c5..13e2ec5 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -29,4 +29,4 @@ logging.basicConfig(format="%(asctime)s: %(message)s", level=int(config['LOG_LEVEL']['log_level']) ) del cwd -REG_FILE = config['DEFAULT']['applications_db'] \ No newline at end of file +REG_FILE = config['DEFAULT']['applications_db'] diff --git a/private/lib/System.py b/private/lib/System.py index a32f281..6e915de 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -146,7 +146,8 @@ class System: return False return True - def printTuple(self, tup: tuple) -> None: + @staticmethod + def printTuple(tup: tuple) -> None: """Prints a tuple with spaces as separators :param tup: Tuple you want to print @@ -190,7 +191,7 @@ if __name__ == "__main__": S.register("dar") S.lock_user_pw("dar") S.add_to_usergroup("dar") - #if not S.make_ssh_usable("dar", "SSHpub"): + # if not S.make_ssh_usable("dar", "SSHpub"): # print("Huh, error :shrug:") exit(0) except KeyboardInterrupt: diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py index d457cf9..66bb9f9 100644 --- a/private/lib/sqlitedb.py +++ b/private/lib/sqlitedb.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 import sqlite3 -from sys import stderr as STDERR +from sys import stderr as stderr # create dictionary out of sqlite results @@ -32,16 +32,16 @@ class SQLitedb: self.connection = sqlite3.connect(db) self.cursor = self.connection.cursor() except sqlite3.Error as e: - print("Connection error: %s" % e, file=STDERR) + print("Connection error: %s" % e, file=stderr) - self.cursor.row_factory = dict_factory # every result will be a dict now + self.cursor.row_factory = dict_factory # every result will be a dict now def __del__(self): try: self.connection.commit() self.connection.close() except sqlite3.Error as e: - print("Couldn't gracefully close db: %s" % e, file=STDERR) + print("Couldn't gracefully close db: %s" % e, file=stderr) def query(self, qq: str) -> list: """Do a query and automagically get the fetched results in a list @@ -55,7 +55,7 @@ class SQLitedb: self.cursor.execute(qq) self.last_result = self.cursor.fetchall() except sqlite3.Error as e: - print("Couldn't execute query %s, exception: %s" % (qq, e), file=STDERR) + print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) self.last_result = [] return self.last_result @@ -81,10 +81,10 @@ class SQLitedb: self.cursor.execute(qq, deliver) self.last_result = self.cursor.fetchall() except sqlite3.Error as e: - print("Couldn't execute query %s, exception: %s" % (qq, e), file=STDERR) + print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) self.last_result = [] except TypeError as e: - print("Types in given tuple doesnt match to execute query \"%s\": %s" % (qq, e), file=STDERR) + print("Types in given tuple doesnt match to execute query \"%s\": %s" % (qq, e), file=stderr) self.last_result = [] return self.last_result From 04d95c2d08d5b583ecd2a7d063da2d42860aa720 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 22:20:59 +0200 Subject: [PATCH 20/79] realign layout --- private/ListUsers.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 428d54a..05f21a6 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -10,16 +10,16 @@ class ListUsers: def __init__(self, uap: bool = CFG.args.unapproved, app: bool = CFG.args.approved): self.db = SQLitedb(CFG.REG_FILE) - if uap: # only unapproved users + if uap: # only unapproved users query = "SELECT * FROM `applications` WHERE status = '0'" - elif app: # Approved users + elif app: # Approved users query = "SELECT * FROM `applications` WHERE status = '1'" - else: # All users + else: # All users query = "SELECT * FROM `applications`" self.usersFetch = self.db.query(query) def prettyPrint(self) -> None: - pass # see below why not implemented yet, texttable... + pass # see below why not implemented yet, texttable... def getFetch(self) -> list: """ Returns a complete fetch done by the sqlitedb-class @@ -56,12 +56,12 @@ print(t.draw()) print("ID: {}; Username: \"{}\"; Mail: {}; Name: \"{}\"; Registered: {}; Status: {}".format( user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] ))""" - ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s| State |\n" % ( + ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % ( " ", " ", " ", " ", " " ) - ret += 101*"-" + "\n" + ret += 102 * "-" + "\n" for user in fetch: - ret += "%-4i| %-14s| %-25s| %-22s| %-8s| %-6i|\n" % ( + ret += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % ( user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] ) if CFG.args.file != "stdout": From e0efeee67df3f2e63cc8f9cea1b57b4ce1b93be9 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 13 Oct 2019 22:44:30 +0200 Subject: [PATCH 21/79] Better error handling, just hang a freakin' / to ssh_dir if it got screwed up --- private/lib/System.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/private/lib/System.py b/private/lib/System.py index 6e915de..39421fd 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -6,6 +6,7 @@ import pwd class System: """Class to interact with the system specifically to support our needs 0w0""" + dry = False create_command = [] home = "" @@ -19,7 +20,12 @@ class System: :param home: Standard directory to search for the home directories of your users(default is /home/) :type home: str """ + self.dry = dryrun + if not home.endswith("/"): + home += "/" + if not os.path.isdir(home): + raise ValueError("home should be an existent directory...") self.home = home def register(self, username: str, cc: tuple = tuple(["useradd", "-m"])) -> bool: @@ -67,7 +73,7 @@ class System: print("Nah, @TODO, but actually kinda too lazy for this lul. Just a lot happening here") return True if not sshdir.endswith("/"): - return False # @TODO Exception in Log + sshdir += "/" ssh_dir = self.home + username + "/" + sshdir try: os.mkdir(ssh_dir) From c28e616ea5823570ae77ebd8787ecd160a85c7a5 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 12:32:21 +0200 Subject: [PATCH 22/79] Faster rebuildung by moving system relevant stuff up --- Dockerfile | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9265143..168d461 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,21 +8,6 @@ RUN apt-get update &&\ # Clean up APT when done. RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - - -# private/{scripts, administrate.py}, public/{scripts, userapplications.py}, config/userapplicatonsconfig.ini -#configs, logs, db -COPY config/applicationsconfig.ini /app/data/applicationsconfig.ini - -# admin scripts -COPY private/ /app/admin/ - -# user accessible scripts -# Make TILDE_ENV -COPY public/ /app/user/ -#SSH config into /etc :) -COPY config/etc /etc - # create user for applications RUN useradd -Md /app/user/ -s /app/user/userapplication.py tilde @@ -34,11 +19,23 @@ RUN usermod -U tilde RUN useradd -Md /app/admin -s /app/admin/administrate.py admin # privilege separation directory RUN mkdir -p /var/run/sshd - # expose SSH port EXPOSE 22 ENV TILDE_CONF="/app/data/applicationsconfig.ini" +# private/{scripts, administrate.py}, public/{scripts, userapplications.py}, config/userapplicatonsconfig.ini +#configs, logs, db +COPY config/applicationsconfig.ini /app/data/applicationsconfig.ini + +# admin scripts +COPY private/ /app/admin/ + +# user accessible scripts +# Make TILDE_ENV +COPY public/ /app/user/ +#SSH config into /etc :) +COPY config/etc /etc + RUN touch /app/data/applications.sqlite RUN touch /app/data/applications.log # Doesnt work, @TODO why From 2202af78264d8f47027242db8937533f8ee89d08 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 12:33:07 +0200 Subject: [PATCH 23/79] Introduce imports, which comes with a new flag(--Import) --Import depends on --file being present pointing to a valid CSV-file that contains unique users with id's. When a user already exists with his id, the script will fail. It is also Backup.py-only which is reflected in the help message --- private/Backup.py | 95 ++++++++++++++++++++++++++++++++++++++++------ private/lib/CFG.py | 5 +++ 2 files changed, 89 insertions(+), 11 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index 9a33128..6710fbb 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -4,23 +4,96 @@ import ListUsers import csv import io import lib.CFG as CFG +import os + + +class Backup: + filename: str + quoting: int + dialect: str + field_names: tuple + + def __init__(self, fname: str = CFG.args.file, quoting: int = csv.QUOTE_NONNUMERIC, dialect: str = "excel"): + self.setFilename(fname) + self.setQuoting(quoting) + self.setDialect(dialect) + self.setFieldnames(tuple(['id', 'username', 'email', 'name', 'pubkey', 'timestamp', 'status'])) + + def setDialect(self, dialect: str): + self.dialect = dialect + + def setQuoting(self, quoting: int): + self.quoting = quoting + + def setFilename(self, filename: str): + self.filename = filename + + def setFieldnames(self, f_names: tuple): + self.field_names = f_names + + def BackupToFile(self, fetched: list): + returner = io.StringIO() + write_csv = csv.DictWriter(returner, fieldnames=self.field_names, quoting=self.quoting, dialect=self.dialect) + write_csv.writeheader() + write_csv.writerows(fetched) + + if self.filename == "stdout": + print(returner.getvalue()) + else: + with open(self.filename, "w") as f: + print(returner.getvalue(), file=f) + return True + + @staticmethod + def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: tuple = tuple([])): + if not os.path.isfile(fname): + return None # @TODO maybe some better output here + if not os.path.isfile(db): + return None # @TODO maybe some better output here + if userids: + pass # empty tuple means everything + try: + with open(fname, 'r', newline='') as f: + import lib.sqlitedb as sql + import lib.System + sysctl = lib.System.System() + sql = lib.sqlitedb.SQLitedb(CFG.REG_FILE) + reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option + for row in reader: + sql.safequery("INSERT INTO `applications` (id, username, name, timestamp, email, pubkey, status) " + "VALUES (?,?,?,?,?,?,?)", + tuple([row["id"], row["username"], row["name"], row["timestamp"], + row["email"], row["pubkey"], row["status"]])) # @TODO: without IDs + if row["status"] == "1": + sysctl.register(row["username"]) + sysctl.lock_user_pw(row["username"]) + sysctl.add_to_usergroup(row["username"]) + sysctl.make_ssh_usable(row["username"], row["pubkey"]) + print(row['id'], row['username'], row['email'], row['name'], row['pubkey'], row['timestamp'], + row['status'] + "====> Registered.") + elif row["status"] == "0": + print(row['id'], row['username'], row['email'], row['name'], row['pubkey'], row['timestamp'], + row['status'] + "not approved, therefore not registered.") + else: + print(f"Uhm, ok. Type is {type(row['status'])}, and value is {row['status']}") + pass # @TODO: Import with sqlitedb and system. Will be fun Kappa + except OSError as E: + print(f"UUFFF, something went WRONG with the file {fname}: {E}") + if __name__ == "__main__": try: L = ListUsers.ListUsers() fetch = L.getFetch() - ret = io.StringIO() - writer = csv.writer(ret, quoting=csv.QUOTE_NONNUMERIC) # @TODO: Should be a specific dialect instead? - writer.writerow(['id', 'username', 'email', 'name', 'pubkey' 'timestamp', 'status']) - for user in fetch: - writer.writerow([user['id'], user['username'], user['email'], user['name'], user['pubkey'], - user['timestamp'], user['status']]) - - if CFG.args.file == "stdout": - print(ret.getvalue()) + B = Backup() + if CFG.args.Import: + if not CFG.args.file: + print("You MUST set a CSV-file with the -f/--file flag that already exist") + exit(1) + if not B.ImportFromFile(CFG.args.file): + print("Backup didn't work because the file doesnt exist") else: - with open(CFG.args.file, "w") as f: - print(ret.getvalue(), file=f) + B.BackupToFile(fetch) exit(0) except KeyboardInterrupt as e: pass diff --git a/private/lib/CFG.py b/private/lib/CFG.py index 13e2ec5..5e2123b 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -20,6 +20,11 @@ argparser.add_argument('-a', '--approved', default=False, action="store_true", help="Only approved Users.", required=False) argparser.add_argument('-f', '--file', default="stdout", type=str, help='write to file instead of stdout', required=False) +argparser.add_argument('--Import', default=False, action="store_true", + help="Import Users from file. Affects currently only Backup.py.\n" + "Setting this to true will result in -f being interpreted as the input file to import " + "users from. The file MUST be a comma separated CSV file being readable having it's " + "defined columns written in the first line.") args = argparser.parse_args() CONF_FILE = args.config From 0718de20fb8f051fcf6a99a7a2fa31be567de4d4 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 14:00:57 +0200 Subject: [PATCH 24/79] Let SQLite check for incorrect dates on timestamp row --- public/userapplication.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/public/userapplication.py b/public/userapplication.py index e751717..7639040 100755 --- a/public/userapplication.py +++ b/public/userapplication.py @@ -46,7 +46,9 @@ def __createTable(cursor, connection): "id INTEGER PRIMARY KEY AUTOINCREMENT," "username TEXT NOT NULL, email TEXT NOT NULL," "name TEXT NOT NULL, pubkey TEXT NOT NULL," - "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP, status INTEGER NOT NULL DEFAULT 0);") + "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP CONSTRAINT " + "timestamp_valid CHECK( timestamp IS strftime('%Y-%m-%d %H:%M:%S', timestamp))" + ",status INTEGER NOT NULL DEFAULT 0);") connection.commit() except sqlite3.Error as e: logging.exception("Couldn't create needed SQLite Table! Exception: %s" % e) From cd8fe5fed06c6663357f719a087adeb4c80668df Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 14:33:17 +0200 Subject: [PATCH 25/79] commit on querys --- private/lib/sqlitedb.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py index 66bb9f9..d473f57 100644 --- a/private/lib/sqlitedb.py +++ b/private/lib/sqlitedb.py @@ -54,6 +54,7 @@ class SQLitedb: try: self.cursor.execute(qq) self.last_result = self.cursor.fetchall() + self.connection.commit() except sqlite3.Error as e: print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) self.last_result = [] @@ -80,6 +81,7 @@ class SQLitedb: try: self.cursor.execute(qq, deliver) self.last_result = self.cursor.fetchall() + self.connection.commit() except sqlite3.Error as e: print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) self.last_result = [] From c1488f6164345488581c7d4fe14b53a410bc4780 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 14:33:38 +0200 Subject: [PATCH 26/79] Return True on success --- private/Backup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/private/Backup.py b/private/Backup.py index 6710fbb..39801b0 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -79,6 +79,7 @@ class Backup: pass # @TODO: Import with sqlitedb and system. Will be fun Kappa except OSError as E: print(f"UUFFF, something went WRONG with the file {fname}: {E}") + return True if __name__ == "__main__": From 96c403a3d936f53065b12ceeb54033787a890986 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 16:16:30 +0200 Subject: [PATCH 27/79] Introducing own Exception classes to distinguish between possible System errors --- private/lib/System.py | 57 ++++++++++++++++++----------------- private/lib/UserExceptions.py | 22 ++++++++++++++ 2 files changed, 52 insertions(+), 27 deletions(-) create mode 100644 private/lib/UserExceptions.py diff --git a/private/lib/System.py b/private/lib/System.py index 39421fd..b4ec13a 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -1,7 +1,7 @@ -import sys import os import subprocess import pwd +import lib.UserExceptions class System: @@ -35,7 +35,7 @@ class System: :type username: str :param cc: Tuple with commands separated to execute on the machine. (defaults to useradd -m) :type cc: tuple - :returns: True, when the user was successfully created, False when not + :return: True, if worked, raises lib.UserExceptions.UserExistsAlready when not :rtype: bool """ @@ -47,15 +47,12 @@ class System: elif not self.dry: rt = subprocess.call(cc) if rt != 0: - print(f"Could not create user {username}; '{cc}' returned '{rt}'", file=sys.stderr) - # @TODO Logging/Exception - return False + raise lib.UserExceptions.UserExistsAlready(f"User {username} exists already") return True def unregister(self, username: str): pass - # @TODO errno def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/") -> bool: """ Make SSH usable for our newly registered user @@ -65,7 +62,8 @@ class System: :type pubkey: str :param sshdir: Directory to write the authorized_keys File to. PWD is $HOME of said user. (defaults to ".ssh/") :type sshdir: str - :return: True, when everything worked out good, false when something bad happened. Outputs the error of it. + :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode, lib.UserExceptions.HomeDirExistsAlready + or lib.UserExceptions.ModifyFilesystem when not :rtype: bool """ @@ -80,8 +78,8 @@ class System: except FileExistsError: pass # thats actually a good one for us :D except OSError as e: - print(f"Could not create {ssh_dir}: Exception: {e}", file=sys.stderr) - return False + raise lib.UserExceptions.HomeDirExistsAlready(f"Could not create {ssh_dir}: Exception: {e}") + try: with open(ssh_dir + "authorized_keys", "w") as f: print(pubkey, file=f) @@ -89,17 +87,17 @@ class System: os.chmod(ssh_dir + "authorized_keys", 0o700) # directory is already 777? os.chmod(ssh_dir, 0o700) # directory is already 777? except OSError as e: - print(f"Could not write and/or chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}", - file=sys.stderr) - return False # @TODO Exception in Log + raise lib.UserExceptions.ModifyFilesystem( + f"Could not write and/or chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}") try: pwdnam = pwd.getpwnam(username) os.chown(ssh_dir, pwdnam[2], pwdnam[3]) # 2=>uid, 3=>gid os.chown(ssh_dir + "authorized_keys", pwd.getpwnam(username)[2], pwd.getpwnam(username)[3]) - except OSError as e: - print(f"Could not chown {ssh_dir} and/or authorized_keys to {username} and their group, Exception: {e}", - file=sys.stderr) - return False # @TODO Exception in Log + except OSError as e: # by os.chown + raise lib.UserExceptions.ModifyFilesystem( + f"Could not chown {ssh_dir} and/or authorized_keys to {username} and their group, Exception: {e}",) + except KeyError as e: # by PWD + raise lib.UserExceptions.General(f"PWD can't find {username}: {e}") return True def lock_user_pw(self, username: str, cc: tuple = tuple(["usermod", "--lock"])) -> bool: @@ -110,7 +108,7 @@ class System: :param cc: Commands to run in the subprocess to lock it down(defaults to usermod --lock) :type cc: tuple :rtype: bool - :return: True when the lock worked, false when not. + :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode when not """ lock_command = cc @@ -121,9 +119,7 @@ class System: elif not self.dry: rt = subprocess.call(cc) if rt != 0: - print(f"Could not lock user '{username}'; '{cc}' returned '{rt}'", file=sys.stderr) - return False - # @TODO Exception in Log + raise lib.UserExceptions.UnknownReturnCode(f"Could not lock user '{username}'; '{cc}' returned '{rt}'") return True def add_to_usergroup(self, username: str, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])) -> bool: @@ -135,7 +131,7 @@ class System: :type group: str :param cc: Commands to execute that adds your user to said specific group(defaults to usermod -a -G") :type cc: tuple - :return: True, if worked, False when not. + :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode when not :rtype bool """ @@ -147,9 +143,8 @@ class System: elif not self.dry: rt = subprocess.call(cc) if rt != 0: - print(f"Could not add user '{username}' to group '{group}' with command '{cc}', returned '{rt}'", - file=sys.stderr) # @TODO Exception in Log - return False + raise lib.UserExceptions.UnknownReturnCode( + f"Could not add user '{username}' to group '{group}' with command '{cc}', returned '{rt}'",) return True @staticmethod @@ -174,7 +169,7 @@ class System: :type username: str :param cc: Commands to execute to delete the user from the System(defaults to userdel -r) :type cc: tuple - :return: True, when worked, False if not. + :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode when not :rtype: bool """ @@ -186,11 +181,19 @@ class System: else: ret = subprocess.call(cc) if ret != 0: - print(f"Could not delete user with command {cc}. Return code: {ret}") - return False + raise lib.UserExceptions.UnknownReturnCode( + f"Could not delete user with command {cc}. Return code: {ret}") return True +def AIO(username, pubkey, group="tilde"): + syst = System(dryrun=False) + syst.register(username) + syst.lock_user_pw(username) + syst.add_to_usergroup(username, group) + syst.make_ssh_usable(username, pubkey) + + if __name__ == "__main__": try: S = System(dryrun=True) diff --git a/private/lib/UserExceptions.py b/private/lib/UserExceptions.py new file mode 100644 index 0000000..02e8f47 --- /dev/null +++ b/private/lib/UserExceptions.py @@ -0,0 +1,22 @@ +class General(Exception): + pass + + +class UnknownUser(General): + pass + + +class UnknownReturnCode(General): + pass + + +class UserExistsAlready(UnknownReturnCode): + pass + + +class ModifyFilesystem(General): + pass + + +class HomeDirExistsAlready(ModifyFilesystem): + pass From 4760e35fda02eeaaba551c6b67cd9fde703a94b1 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 16:29:34 +0200 Subject: [PATCH 28/79] it's freakin stupid to import IDs. Nothing depends on them. Ignore them. --- private/Backup.py | 10 ++++----- public/userapplication.py | 47 ++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index 39801b0..e71599e 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -54,16 +54,12 @@ class Backup: pass # empty tuple means everything try: with open(fname, 'r', newline='') as f: - import lib.sqlitedb as sql + import lib.sqlitedb import lib.System sysctl = lib.System.System() sql = lib.sqlitedb.SQLitedb(CFG.REG_FILE) reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: - sql.safequery("INSERT INTO `applications` (id, username, name, timestamp, email, pubkey, status) " - "VALUES (?,?,?,?,?,?,?)", - tuple([row["id"], row["username"], row["name"], row["timestamp"], - row["email"], row["pubkey"], row["status"]])) # @TODO: without IDs if row["status"] == "1": sysctl.register(row["username"]) sysctl.lock_user_pw(row["username"]) @@ -76,6 +72,10 @@ class Backup: row['status'] + "not approved, therefore not registered.") else: print(f"Uhm, ok. Type is {type(row['status'])}, and value is {row['status']}") + sql.safequery("INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " + "VALUES (?,?,?,?,?,?)", + tuple([row["username"], row["name"], row["timestamp"], + row["email"], row["pubkey"], row["status"]])) # @TODO: without IDs pass # @TODO: Import with sqlitedb and system. Will be fun Kappa except OSError as E: print(f"UUFFF, something went WRONG with the file {fname}: {E}") diff --git a/public/userapplication.py b/public/userapplication.py index 7639040..3ca0206 100755 --- a/public/userapplication.py +++ b/public/userapplication.py @@ -1,30 +1,26 @@ #!/usr/bin/env python3 -import re, configparser, logging, sqlite3, argparse -from os import getcwd +import argparse +import configparser +import logging +import re +import sqlite3 from os import environ +from os import getcwd from os import path as ospath -import re, configparser, logging, sqlite3 - try: cwd = environ.get('TILDE_CONF') if cwd is None: - cwd=getcwd()+"/applicationsconfig.ini" + cwd = getcwd()+"/applicationsconfig.ini" else: if ospath.isfile(cwd) is False: - cwd=getcwd()+"/applicationsconfig.ini" + cwd = getcwd() + "/applicationsconfig.ini" # cwd is now either cwd/applicationsconfig or $TILDE_CONF argparser = argparse.ArgumentParser(description='interactive registration formular for tilde platforms') argparser.add_argument('-c', '--config', default=cwd, type=str, help='Config file', required=False) args = argparser.parse_args() CONF_FILE = args.config -except: - # intended broad, @TODO check them all for errors instead of everything in one - logging.exception("Argumentparser-Exception: ") - exit(0) - -try: config = configparser.ConfigParser() config.read(CONF_FILE) logging.basicConfig(format="%(asctime)s: %(message)s", filename=config['DEFAULT']['log_file'], @@ -82,17 +78,17 @@ def __checkSQLite(cursor, connection): def check_username(value): global VALID_USER if len(value) < 3: - VALID_USER=False + VALID_USER = False return False if len(value) > 16: - VALID_USER=False + VALID_USER = False return False try: from pwd import getpwnam getpwnam(value) VALID_USER = False - # intended broad - except Exception: + # everything from pwd throws an KeyError when the given user cannot be found + except KeyError: VALID_USER = True return True return False @@ -103,31 +99,32 @@ def validate_pubkey(value): global VALID_SSH import base64 if len(value) > 8192 or len(value) < 80: - VALID_SSH=False + VALID_SSH = False return False value = value.replace("\"", "").replace("'", "").replace("\\\"", "") value = value.split(' ') - types = [ 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', - 'ecdsa-sha2-nistp521', 'ssh-rsa', 'ssh-dss', 'ssh-ed25519' ] + types = ['ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', + 'ecdsa-sha2-nistp521', 'ssh-rsa', 'ssh-dss', 'ssh-ed25519'] if value[0] not in types: - VALID_SSH=False + VALID_SSH = False return False try: base64.decodebytes(bytes(value[1], "utf-8")) except TypeError: - VALID_SSH=False + VALID_SSH = False return False - VALID_SSH=True + VALID_SSH = True return True def main(): print(" ▗▀▖ \n▗▖▖ ▐ ▌ ▌▛▀▖\n▘▝▗▖▜▀ ▌ ▌▌ ▌\n ▝▘▐ ▝▀▘▘ ▘") - username = input("Welcome to the ~.fun user application form!\n\nWhat is your desired username? [a-z0-9] allowed:\n") + username = input("Welcome to the ~.fun user application form!\n\n" + "What is your desired username? [a-z0-9] allowed:\n") while (not re.match("[a-z]+[a-z0-9]", username)) or (not check_username(username)): username = input("Invalid Username, maybe it exists already?\nValid characters are only a-z and 0-9." "\nMake sure your username starts with a character and not a number" @@ -160,8 +157,8 @@ def main(): if re.match("[yY]", validation): print("Thank you for your application! We'll get in touch shortly. 🐧") try: - connection=sqlite3.connect(REG_FILE) - cursor=connection.cursor() + connection = sqlite3.connect(REG_FILE) + cursor = connection.cursor() __checkSQLite(cursor, connection) addtotable(cursor, connection, username, fullname, email, pubkey) connection.commit() From 8274cbb27e88e9ceefa3a8569197a64bfc69e91c Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 16:58:20 +0200 Subject: [PATCH 29/79] unregister() is just a different word for removing --- private/lib/System.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/private/lib/System.py b/private/lib/System.py index b4ec13a..8f9344b 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -51,7 +51,7 @@ class System: return True def unregister(self, username: str): - pass + self.removeUser(username) def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/") -> bool: """ Make SSH usable for our newly registered user From 0c1f7ea252322ed63a871974f8f3327081441b28 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 20:52:15 +0200 Subject: [PATCH 30/79] Validate even imported files and users Created a new file for the validation functions, should be soon(TM) used too in the userapplication-script but dont hurry --- private/Backup.py | 57 ++++++++++++++++++++++++----------- private/lib/UserExceptions.py | 27 ++++++++++++++++- private/lib/validator.py | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+), 18 deletions(-) create mode 100644 private/lib/validator.py diff --git a/private/Backup.py b/private/Backup.py index e71599e..35f9bd6 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -4,6 +4,8 @@ import ListUsers import csv import io import lib.CFG as CFG +import lib.validator +import lib.UserExceptions import os @@ -52,6 +54,7 @@ class Backup: return None # @TODO maybe some better output here if userids: pass # empty tuple means everything + # noinspection PyBroadException try: with open(fname, 'r', newline='') as f: import lib.sqlitedb @@ -60,25 +63,45 @@ class Backup: sql = lib.sqlitedb.SQLitedb(CFG.REG_FILE) reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: + # if any of this fails move on to the next user, just print a relatively helpful message lel + if not lib.validator.checkUsernameLength(row["username"]): + print(f"The username {row['username']} is either too long(>16) or short(<3).") + continue + if not lib.validator.checkUsernameCharacters(row["username"]): + print(f"The username contains unsupported characters or starts with a number: " + f"{row['username']}") + continue + if not lib.validator.checkSSHKey(row["pubkey"]): + print(f"Following SSH-Key isn't valid: {row['pubkey']}") + continue + if lib.validator.checkUserExists(row["username"]): + print(f"The user '{row['username']}' already exists.") + continue + if not lib.validator.checkEmail(row["email"]): + print(f"The E-Mail address {row['email']} is not valid.") + continue if row["status"] == "1": - sysctl.register(row["username"]) - sysctl.lock_user_pw(row["username"]) - sysctl.add_to_usergroup(row["username"]) - sysctl.make_ssh_usable(row["username"], row["pubkey"]) - print(row['id'], row['username'], row['email'], row['name'], row['pubkey'], row['timestamp'], - row['status'] + "====> Registered.") + try: + sysctl.register(row["username"]) # @TODO exception lib.UserExceptions.UserExistsAlready + sysctl.lock_user_pw(row["username"]) # @TODO exception lib.UserExceptions.UnknownReturnCode + sysctl.add_to_usergroup(row["username"]) # @TODO exception lib.UnknownReturnCode + sysctl.make_ssh_usable(row["username"], row["pubkey"]) # @TODO exception + print(row['username'], "====> Registered.") + except Exception as e: + print(e) + continue elif row["status"] == "0": - print(row['id'], row['username'], row['email'], row['name'], row['pubkey'], row['timestamp'], - row['status'] + "not approved, therefore not registered.") - else: - print(f"Uhm, ok. Type is {type(row['status'])}, and value is {row['status']}") - sql.safequery("INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " - "VALUES (?,?,?,?,?,?)", - tuple([row["username"], row["name"], row["timestamp"], - row["email"], row["pubkey"], row["status"]])) # @TODO: without IDs - pass # @TODO: Import with sqlitedb and system. Will be fun Kappa - except OSError as E: - print(f"UUFFF, something went WRONG with the file {fname}: {E}") + print(row['username'] + "not approved, therefore not registered.") + try: + sql.safequery( + "INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " + "VALUES (?,?,?,?,?,?)", tuple([row["username"], row["name"], row["timestamp"], + row["email"], row["pubkey"], row["status"]])) + except OSError as E: + pass + print(f"UUFFF, something went WRONG with the file {fname}: {E}") + except Exception as e: + print(f"Exception! UNCATCHED! {type(e)}") return True diff --git a/private/lib/UserExceptions.py b/private/lib/UserExceptions.py index 02e8f47..926c8e5 100644 --- a/private/lib/UserExceptions.py +++ b/private/lib/UserExceptions.py @@ -18,5 +18,30 @@ class ModifyFilesystem(General): pass -class HomeDirExistsAlready(ModifyFilesystem): +class SSHDirUncreatable(ModifyFilesystem): pass + + +class SQLiteDatabaseDoesntExistYet(General): + pass + + +class User(Exception): + pass + + +class UsernameLength(User): + pass + + +class UsernameTooShort(User): + pass + + +class UsernameTooLong(User): + pass + + +class UsernameInvalidCharacters(User): + pass + diff --git a/private/lib/validator.py b/private/lib/validator.py new file mode 100644 index 0000000..82283d0 --- /dev/null +++ b/private/lib/validator.py @@ -0,0 +1,52 @@ +import re +import pwd + + +def checkUsernameCharacters(username: str): + if re.match("[a-z]+[a-z0-9]", username): + return True + else: + return False + + +def checkUsernameLength(username: str): + if len(username) > 16: + return False + if len(username) < 3: + return False + return True + + +def checkUserExists(username: str): + try: + pwd.getpwnam(username) + except KeyError: + return True # User already exists + else: + return False # User doesnt exist + + +def checkSSHKey(key: str): + # taken from https://github.com/hashbang/provisor/blob/master/provisor/utils.py, all belongs to them! ;) + import base64 + if len(key) > 8192 or len(key) < 80: + return False + + key = key.replace("\"", "").replace("'", "").replace("\\\"", "") + key = key.split(' ') + types = ['ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', + 'ecdsa-sha2-nistp521', 'ssh-rsa', 'ssh-dss', 'ssh-ed25519'] + if key[0] not in types: + return False + try: + base64.decodebytes(bytes(key[1], "utf-8")) + except TypeError: + return False + return True + + +def checkEmail(mail: str): + if not re.match("(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", mail): + return False + else: + return True From a1563116f63bc5f39d1a50753c5a6b021988dd00 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 20:54:34 +0200 Subject: [PATCH 31/79] Renamed SSH-Exception --- private/lib/System.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/private/lib/System.py b/private/lib/System.py index 8f9344b..92e081c 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -38,7 +38,6 @@ class System: :return: True, if worked, raises lib.UserExceptions.UserExistsAlready when not :rtype: bool """ - create_command = cc cc = create_command + tuple([username]) if self.dry: @@ -78,7 +77,7 @@ class System: except FileExistsError: pass # thats actually a good one for us :D except OSError as e: - raise lib.UserExceptions.HomeDirExistsAlready(f"Could not create {ssh_dir}: Exception: {e}") + raise lib.UserExceptions.SSHDirUncreatable(f"Could not create {ssh_dir}: Exception: {e}") try: with open(ssh_dir + "authorized_keys", "w") as f: From d517db3b2ae189faf18dddf5976adb309742f68c Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 20:54:49 +0200 Subject: [PATCH 32/79] On Operational Exceptions we create the table and try again this could potentially result in a endless recursion when the table isnt writeable and a lot other funny things happen but normally and in 99,999999999% of our cases this is totally fine --- private/lib/sqlitedb.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py index d473f57..be4c98f 100644 --- a/private/lib/sqlitedb.py +++ b/private/lib/sqlitedb.py @@ -55,6 +55,9 @@ class SQLitedb: self.cursor.execute(qq) self.last_result = self.cursor.fetchall() self.connection.commit() + except sqlite3.OperationalError: + self._createTable() + return self.query(qq) except sqlite3.Error as e: print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) self.last_result = [] @@ -88,6 +91,9 @@ class SQLitedb: except TypeError as e: print("Types in given tuple doesnt match to execute query \"%s\": %s" % (qq, e), file=stderr) self.last_result = [] + except sqlite3.OperationalError as e: + self._createTable() + return self.safequery(qq, deliver) return self.last_result def removeApplicantFromDB(self, userid: int) -> bool: @@ -104,6 +110,10 @@ class SQLitedb: except sqlite3.Error as e: print(f"Could not delete user with id: {userid}, exception in DB: {e}") # @TODO LOGGING FFS return False + except sqlite3.OperationalError: + print("The database has probably not yet seen any users, so it didnt create your table yet. Come back" + "when a user tried to register") + return False return True def removeApplicantFromDBperUsername(self, username: str) -> bool: @@ -120,8 +130,27 @@ class SQLitedb: except sqlite3.Error as e: print(f"Could not delete user {username}, exception in DB: {e}") # @TODO LOGGING return False + except sqlite3.OperationalError: + print("The database has probably not yet seen any users, so it didnt create your table yet. Come back" + "when a user tried to register") + return False return True + def _createTable(self): + try: + self.cursor.execute( + "CREATE TABLE IF NOT EXISTS applications(" + "id INTEGER PRIMARY KEY AUTOINCREMENT," + "username TEXT NOT NULL, email TEXT NOT NULL," + "name TEXT NOT NULL, pubkey TEXT NOT NULL," + "timestamp DATETIME DEFAULT CURRENT_TIMESTAMP CONSTRAINT " + "timestamp_valid CHECK( timestamp IS strftime('%Y-%m-%d %H:%M:%S', timestamp))" + ",status INTEGER NOT NULL DEFAULT 0);") + self.connection.commit() + except sqlite3.Error as e: + print(f"The database probably doesn't exist yet, but read the message: {e}") + print("The database table didn't exist yet; created it successfully!") + if __name__ == "__main__": try: From 283143104d343cc87694536108451d7f5fc9f94d Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 16 Oct 2019 21:52:25 +0200 Subject: [PATCH 33/79] Actually we allow currently strange things, this fixes it on the usernames --- private/Backup.py | 45 ++++++++++++++++++++++++--------------- private/lib/validator.py | 14 ++++++------ public/userapplication.py | 6 ++++++ 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index 35f9bd6..89c6643 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -49,9 +49,11 @@ class Backup: @staticmethod def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: tuple = tuple([])): if not os.path.isfile(fname): - return None # @TODO maybe some better output here + print(f"File {fname} don't exist") + return None if not os.path.isfile(db): - return None # @TODO maybe some better output here + print(f"The database file {db} don't exist") + return None if userids: pass # empty tuple means everything # noinspection PyBroadException @@ -64,34 +66,43 @@ class Backup: reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: # if any of this fails move on to the next user, just print a relatively helpful message lel - if not lib.validator.checkUsernameLength(row["username"]): - print(f"The username {row['username']} is either too long(>16) or short(<3).") - continue if not lib.validator.checkUsernameCharacters(row["username"]): print(f"The username contains unsupported characters or starts with a number: " f"{row['username']}") continue + if not lib.validator.checkUsernameLength(row["username"]): + print(f"The username {row['username']} is either too long(>16) or short(<3).") + continue if not lib.validator.checkSSHKey(row["pubkey"]): print(f"Following SSH-Key isn't valid: {row['pubkey']}") continue - if lib.validator.checkUserExists(row["username"]): - print(f"The user '{row['username']}' already exists.") - continue if not lib.validator.checkEmail(row["email"]): print(f"The E-Mail address {row['email']} is not valid.") continue + if lib.validator.checkUserExists(row["username"]): + print(f"The user '{row['username']}' already exists.") + continue if row["status"] == "1": try: - sysctl.register(row["username"]) # @TODO exception lib.UserExceptions.UserExistsAlready - sysctl.lock_user_pw(row["username"]) # @TODO exception lib.UserExceptions.UnknownReturnCode - sysctl.add_to_usergroup(row["username"]) # @TODO exception lib.UnknownReturnCode - sysctl.make_ssh_usable(row["username"], row["pubkey"]) # @TODO exception + sysctl.register(row["username"]) + sysctl.lock_user_pw(row["username"]) + sysctl.add_to_usergroup(row["username"]) + sysctl.make_ssh_usable(row["username"], row["pubkey"]) print(row['username'], "====> Registered.") - except Exception as e: - print(e) + except lib.UserExceptions.UserExistsAlready as UEA: + pass # @TODO User was determined to exists already, shouldn't happen but is possible + except lib.UserExceptions.UnknownReturnCode as URC: + pass # @TODO Unknown Return Codes. Can happen in various function + except lib.UserExceptions.SSHDirUncreatable as SDU: + pass # @TODO SSH Directory doesn't exist AND couldn't be created. Inherently wrong design! + except lib.UserExceptions.ModifyFilesystem as MFS: + pass # @TODO Same as SSH Dir but more general, same problem: Wrong Permissions, + # Missing Dirs etc + except Exception as E: # @TODO well less broad is hard to achieve Kappa + print(E) continue elif row["status"] == "0": - print(row['username'] + "not approved, therefore not registered.") + print(row['username'] + " not approved, therefore not registered.") try: sql.safequery( "INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " @@ -100,8 +111,8 @@ class Backup: except OSError as E: pass print(f"UUFFF, something went WRONG with the file {fname}: {E}") - except Exception as e: - print(f"Exception! UNCATCHED! {type(e)}") + except Exception as didntCatch: + print(f"Exception! UNCATCHED! {type(didntCatch)}") return True diff --git a/private/lib/validator.py b/private/lib/validator.py index 82283d0..75e8881 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -3,10 +3,12 @@ import pwd def checkUsernameCharacters(username: str): - if re.match("[a-z]+[a-z0-9]", username): - return True - else: - return False + if " " not in username and "_" not in username and username.isascii() and username.islower() and \ + not username[0].isnumeric(): + if not re.search(r"\W+", username): + if not re.search("[^a-z0-9]", username): + return True + return False def checkUsernameLength(username: str): @@ -21,9 +23,9 @@ def checkUserExists(username: str): try: pwd.getpwnam(username) except KeyError: - return True # User already exists + return False # User already exists else: - return False # User doesnt exist + return True # User doesnt exist def checkSSHKey(key: str): diff --git a/public/userapplication.py b/public/userapplication.py index 3ca0206..6e2e66f 100755 --- a/public/userapplication.py +++ b/public/userapplication.py @@ -77,6 +77,12 @@ def __checkSQLite(cursor, connection): def check_username(value): global VALID_USER + if " " in value or "_ " in value or not value.isascii() or not value.islower() or value[0].isnumeric(): + VALID_USER = False + return False + if re.search(r"\W+", value): + VALID_USER = False + return False if len(value) < 3: VALID_USER = False return False From b0856b558e97ba9d228d0a6f127790112cf2c44d Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Thu, 17 Oct 2019 11:51:57 +0200 Subject: [PATCH 34/79] Move import related stuff to the Import.py script. and delete occurences in Import It is much tidier to look at and doesn't clutter the Script. Is even a whole seperate task so it does make no sense to call it in Backup when i think about it. --- private/Backup.py | 75 +-------------------------------------- private/Import.py | 88 ++++++++++++++++++++++++++++++++++++++++++++++ private/lib/CFG.py | 2 +- 3 files changed, 90 insertions(+), 75 deletions(-) create mode 100644 private/Import.py diff --git a/private/Backup.py b/private/Backup.py index 89c6643..ddb23ea 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -46,75 +46,6 @@ class Backup: print(returner.getvalue(), file=f) return True - @staticmethod - def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: tuple = tuple([])): - if not os.path.isfile(fname): - print(f"File {fname} don't exist") - return None - if not os.path.isfile(db): - print(f"The database file {db} don't exist") - return None - if userids: - pass # empty tuple means everything - # noinspection PyBroadException - try: - with open(fname, 'r', newline='') as f: - import lib.sqlitedb - import lib.System - sysctl = lib.System.System() - sql = lib.sqlitedb.SQLitedb(CFG.REG_FILE) - reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option - for row in reader: - # if any of this fails move on to the next user, just print a relatively helpful message lel - if not lib.validator.checkUsernameCharacters(row["username"]): - print(f"The username contains unsupported characters or starts with a number: " - f"{row['username']}") - continue - if not lib.validator.checkUsernameLength(row["username"]): - print(f"The username {row['username']} is either too long(>16) or short(<3).") - continue - if not lib.validator.checkSSHKey(row["pubkey"]): - print(f"Following SSH-Key isn't valid: {row['pubkey']}") - continue - if not lib.validator.checkEmail(row["email"]): - print(f"The E-Mail address {row['email']} is not valid.") - continue - if lib.validator.checkUserExists(row["username"]): - print(f"The user '{row['username']}' already exists.") - continue - if row["status"] == "1": - try: - sysctl.register(row["username"]) - sysctl.lock_user_pw(row["username"]) - sysctl.add_to_usergroup(row["username"]) - sysctl.make_ssh_usable(row["username"], row["pubkey"]) - print(row['username'], "====> Registered.") - except lib.UserExceptions.UserExistsAlready as UEA: - pass # @TODO User was determined to exists already, shouldn't happen but is possible - except lib.UserExceptions.UnknownReturnCode as URC: - pass # @TODO Unknown Return Codes. Can happen in various function - except lib.UserExceptions.SSHDirUncreatable as SDU: - pass # @TODO SSH Directory doesn't exist AND couldn't be created. Inherently wrong design! - except lib.UserExceptions.ModifyFilesystem as MFS: - pass # @TODO Same as SSH Dir but more general, same problem: Wrong Permissions, - # Missing Dirs etc - except Exception as E: # @TODO well less broad is hard to achieve Kappa - print(E) - continue - elif row["status"] == "0": - print(row['username'] + " not approved, therefore not registered.") - try: - sql.safequery( - "INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " - "VALUES (?,?,?,?,?,?)", tuple([row["username"], row["name"], row["timestamp"], - row["email"], row["pubkey"], row["status"]])) - except OSError as E: - pass - print(f"UUFFF, something went WRONG with the file {fname}: {E}") - except Exception as didntCatch: - print(f"Exception! UNCATCHED! {type(didntCatch)}") - return True - if __name__ == "__main__": try: @@ -122,11 +53,7 @@ if __name__ == "__main__": fetch = L.getFetch() B = Backup() if CFG.args.Import: - if not CFG.args.file: - print("You MUST set a CSV-file with the -f/--file flag that already exist") - exit(1) - if not B.ImportFromFile(CFG.args.file): - print("Backup didn't work because the file doesnt exist") + print("For importing please call the ./Import.py file with the --Import flag") else: B.BackupToFile(fetch) exit(0) diff --git a/private/Import.py b/private/Import.py new file mode 100644 index 0000000..7b6cbda --- /dev/null +++ b/private/Import.py @@ -0,0 +1,88 @@ +import lib.CFG as CFG +import csv +import os +import lib.UserExceptions +import lib.validator + + +def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: tuple = tuple([])): + if not os.path.isfile(fname): + print(f"File {fname} don't exist") + return None + if not os.path.isfile(db): + print(f"The database file {db} don't exist") + return None + if userids: + pass # empty tuple means everything + # noinspection PyBroadException + try: + with open(fname, 'r', newline='') as f: + import lib.sqlitedb + import lib.System + sysctl = lib.System.System() + sql = lib.sqlitedb.SQLitedb(CFG.REG_FILE) + reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option + for row in reader: + # if any of this fails move on to the next user, just print a relatively helpful message lel + if not lib.validator.checkUsernameCharacters(row["username"]): + print(f"The username contains unsupported characters or starts with a number: " + f"{row['username']}") + continue + if not lib.validator.checkUsernameLength(row["username"]): + print(f"The username {row['username']} is either too long(>16) or short(<3).") + continue + if not lib.validator.checkSSHKey(row["pubkey"]): + print(f"Following SSH-Key isn't valid: {row['pubkey']}") + continue + if not lib.validator.checkEmail(row["email"]): + print(f"The E-Mail address {row['email']} is not valid.") + continue + if lib.validator.checkUserExists(row["username"]): + print(f"The user '{row['username']}' already exists.") + continue + if row["status"] == "1": + try: + sysctl.register(row["username"]) + sysctl.lock_user_pw(row["username"]) + sysctl.add_to_usergroup(row["username"]) + sysctl.make_ssh_usable(row["username"], row["pubkey"]) + print(row['username'], "====> Registered.") + except lib.UserExceptions.UserExistsAlready as UEA: + pass # @TODO User was determined to exists already, shouldn't happen but is possible + except lib.UserExceptions.UnknownReturnCode as URC: + pass # @TODO Unknown Return Codes. Can happen in various function + except lib.UserExceptions.SSHDirUncreatable as SDU: + pass # @TODO SSH Directory doesn't exist AND couldn't be created. Inherently wrong design! + except lib.UserExceptions.ModifyFilesystem as MFS: + pass # @TODO Same as SSH Dir but more general, same problem: Wrong Permissions, + # Missing Dirs etc + except Exception as E: # @TODO well less broad is hard to achieve Kappa + print(E) + continue + elif row["status"] == "0": + print(row['username'] + " not approved, therefore not registered.") + try: + sql.safequery( + "INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " + "VALUES (?,?,?,?,?,?)", tuple([row["username"], row["name"], row["timestamp"], + row["email"], row["pubkey"], row["status"]])) + except OSError as E: + pass + print(f"UUFFF, something went WRONG with the file {fname}: {E}") + except Exception as didntCatch: + print(f"Exception! UNCATCHED! {type(didntCatch)}") + return True + + +if __name__ == "__main__": + try: + if not CFG.args.Import: + print("Error, need the import flag") + if not CFG.args.file: + print("Error, need the import file") + if not CFG.args.file: + print("You MUST set a CSV-file with the -f/--file flag that already exist") + exit(1) + exit(0) + except KeyboardInterrupt as e: + pass diff --git a/private/lib/CFG.py b/private/lib/CFG.py index 5e2123b..a8f79c7 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -21,7 +21,7 @@ argparser.add_argument('-a', '--approved', default=False, action="store_true", argparser.add_argument('-f', '--file', default="stdout", type=str, help='write to file instead of stdout', required=False) argparser.add_argument('--Import', default=False, action="store_true", - help="Import Users from file. Affects currently only Backup.py.\n" + help="Import Users from file. Affects currently only Import.py.\n" "Setting this to true will result in -f being interpreted as the input file to import " "users from. The file MUST be a comma separated CSV file being readable having it's " "defined columns written in the first line.") From 3ae497e2eb5ac0360b4f90917d2fb92d027c0527 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Thu, 17 Oct 2019 13:34:43 +0200 Subject: [PATCH 35/79] Let the container stop gracefully with exec! Right now the container can't stop gracefully because the sshd-server runs on the server as PID 0. This results in the docker daemon not killing it but waiting for it to die, which never happens, and results in the default timeout-wait before it KILLS the process. With exec, the sshd becomes PID 1 and can receive and process signals(probably SIGTERM here) and handles them as well. The container stops now nearly instantly.. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 168d461..0e3e6ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -42,4 +42,4 @@ RUN touch /app/data/applications.log #RUN setfacl -R -m u:tilde:rwx /app/data/ RUN chown -R tilde /app/data RUN mkdir /app/user/.ssh -CMD ["sh", "-c", " echo TILDE_CONF=$TILDE_CONF > /app/user/.ssh/environment && /usr/sbin/sshd -D"] +CMD ["sh", "-c", " echo TILDE_CONF=$TILDE_CONF > /app/user/.ssh/environment && exec /usr/sbin/sshd -D"] From 2ab27aa019bb4a73e3576b2b95d951f81f810b12 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Thu, 17 Oct 2019 14:05:17 +0200 Subject: [PATCH 36/79] Validate Timestamps and don't not insert into DB when error Validates now the timestamp in the import.py, doesn't insert users into the sqlite-database before creating the systems account and checks now for existence in the database too(unapproved users, who comes first, gets his name first..) --- private/Import.py | 23 ++++++++++++++++------- private/lib/validator.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 9 deletions(-) diff --git a/private/Import.py b/private/Import.py index 7b6cbda..dcf6390 100644 --- a/private/Import.py +++ b/private/Import.py @@ -23,22 +23,26 @@ def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: sql = lib.sqlitedb.SQLitedb(CFG.REG_FILE) reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: + db_insert = False # if any of this fails move on to the next user, just print a relatively helpful message lel if not lib.validator.checkUsernameCharacters(row["username"]): print(f"The username contains unsupported characters or starts with a number: " - f"{row['username']}") + f"{row['username']}. Skipping.") continue if not lib.validator.checkUsernameLength(row["username"]): - print(f"The username {row['username']} is either too long(>16) or short(<3).") + print(f"The username {row['username']} is either too long(>16) or short(<3). Skipping.") continue if not lib.validator.checkSSHKey(row["pubkey"]): - print(f"Following SSH-Key isn't valid: {row['pubkey']}") + print(f"Following SSH-Key isn't valid: {row['pubkey']}. Skipping.") continue if not lib.validator.checkEmail(row["email"]): - print(f"The E-Mail address {row['email']} is not valid.") + print(f"The E-Mail address {row['email']} is not valid. Skipping") continue - if lib.validator.checkUserExists(row["username"]): - print(f"The user '{row['username']}' already exists.") + if not lib.validator.checkUserExists(row["username"]): + print(f"The user '{row['username']}' already exists. Skipping.") + continue + if not lib.validator.checkDatetimeFormat(row["timestamp"]): + print(f"The timestamp '{row['timestamp']}' from user '{row['username']}' is invalid. Skipping.") continue if row["status"] == "1": try: @@ -59,9 +63,13 @@ def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: except Exception as E: # @TODO well less broad is hard to achieve Kappa print(E) continue + db_insert = True elif row["status"] == "0": print(row['username'] + " not approved, therefore not registered.") + db_insert = True try: + if not db_insert: + continue sql.safequery( "INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " "VALUES (?,?,?,?,?,?)", tuple([row["username"], row["name"], row["timestamp"], @@ -70,7 +78,7 @@ def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: pass print(f"UUFFF, something went WRONG with the file {fname}: {E}") except Exception as didntCatch: - print(f"Exception! UNCATCHED! {type(didntCatch)}") + print(f"Exception! UNCATCHED! {type(didntCatch)}: {didntCatch}") return True @@ -83,6 +91,7 @@ if __name__ == "__main__": if not CFG.args.file: print("You MUST set a CSV-file with the -f/--file flag that already exist") exit(1) + ImportFromFile() exit(0) except KeyboardInterrupt as e: pass diff --git a/private/lib/validator.py b/private/lib/validator.py index 75e8881..92492c4 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -1,5 +1,7 @@ import re import pwd +import lib.sqlitedb +import lib.CFG as CFG def checkUsernameCharacters(username: str): @@ -23,9 +25,22 @@ def checkUserExists(username: str): try: pwd.getpwnam(username) except KeyError: - return False # User already exists + return True # User already exists else: - return True # User doesnt exist + if checkUserInDB(username): + return True + return False + + +def checkUserInDB(username: str): + try: + L = lib.sqlitedb.SQLitedb(CFG.REG_FILE) + fetched = L.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) + if fetched: + return True + except lib.sqlitedb.sqlite3.Error as e: + print(f"SQLite Exception: {e}") + return False def checkSSHKey(key: str): @@ -52,3 +67,12 @@ def checkEmail(mail: str): return False else: return True + + +def checkDatetimeFormat(form: str): + import datetime + try: + datetime.datetime.strptime(form, "%Y-%m-%d %H:%M:%S") + except ValueError: + return False + return True From 77a31a44d1a24857a6718ad4cc9b2481ff19f102 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Thu, 17 Oct 2019 14:42:13 +0200 Subject: [PATCH 37/79] Abort on errors in import file before even trying to activate Moves every check regarding the imported file outside of import.py into the validator. Also removes every follow-up checks regarding it out of import.py. Looks a whole lot cleaner now! --- private/Import.py | 31 +++++-------------------------- private/lib/validator.py | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/private/Import.py b/private/Import.py index dcf6390..c44a31a 100644 --- a/private/Import.py +++ b/private/Import.py @@ -2,7 +2,6 @@ import lib.CFG as CFG import csv import os import lib.UserExceptions -import lib.validator def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: tuple = tuple([])): @@ -17,33 +16,17 @@ def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: # noinspection PyBroadException try: with open(fname, 'r', newline='') as f: + import lib.validator + err = lib.validator.checkImportFile(f) + if err is not True: + print(err) + exit(0) import lib.sqlitedb import lib.System sysctl = lib.System.System() sql = lib.sqlitedb.SQLitedb(CFG.REG_FILE) reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: - db_insert = False - # if any of this fails move on to the next user, just print a relatively helpful message lel - if not lib.validator.checkUsernameCharacters(row["username"]): - print(f"The username contains unsupported characters or starts with a number: " - f"{row['username']}. Skipping.") - continue - if not lib.validator.checkUsernameLength(row["username"]): - print(f"The username {row['username']} is either too long(>16) or short(<3). Skipping.") - continue - if not lib.validator.checkSSHKey(row["pubkey"]): - print(f"Following SSH-Key isn't valid: {row['pubkey']}. Skipping.") - continue - if not lib.validator.checkEmail(row["email"]): - print(f"The E-Mail address {row['email']} is not valid. Skipping") - continue - if not lib.validator.checkUserExists(row["username"]): - print(f"The user '{row['username']}' already exists. Skipping.") - continue - if not lib.validator.checkDatetimeFormat(row["timestamp"]): - print(f"The timestamp '{row['timestamp']}' from user '{row['username']}' is invalid. Skipping.") - continue if row["status"] == "1": try: sysctl.register(row["username"]) @@ -63,13 +46,9 @@ def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: except Exception as E: # @TODO well less broad is hard to achieve Kappa print(E) continue - db_insert = True elif row["status"] == "0": print(row['username'] + " not approved, therefore not registered.") - db_insert = True try: - if not db_insert: - continue sql.safequery( "INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " "VALUES (?,?,?,?,?,?)", tuple([row["username"], row["name"], row["timestamp"], diff --git a/private/lib/validator.py b/private/lib/validator.py index 92492c4..e476bbb 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -76,3 +76,39 @@ def checkDatetimeFormat(form: str): except ValueError: return False return True + + +def checkImportFile(f): + import csv + reador = csv.DictReader(f) + error_list = str() + valid = True + ln = 1 # line number + + for row in reador: + # if any of this fails move on to the next user, just print a relatively helpful message lel + if not lib.validator.checkUsernameCharacters(row["username"]): + error_list += (f"Line {ln}: The username contains unsupported characters or starts with a number: '" + f"{row['username']}'.\n") + valid = False + if not lib.validator.checkUsernameLength(row["username"]): + error_list += f"Line {ln}: The username '{row['username']}' is either too long(>16) or short(<3)\n" + valid = False + if not lib.validator.checkSSHKey(row["pubkey"]): + error_list += f"Line {ln}: Following SSH-Key from user '{row['username']}' isn't valid: '{row['pubkey']}'."\ + f"\n" + valid = False + if not lib.validator.checkEmail(row["email"]): + error_list += f"Line {ln}: The E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" + valid = False + if not lib.validator.checkUserExists(row["username"]): + error_list += f"Line {ln}: The user '{row['username']}' already exists.\n" + valid = False + if not lib.validator.checkDatetimeFormat(row["timestamp"]): + error_list += f"Line {ln}: The timestamp '{row['timestamp']}' from user '{row['username']}' is invalid.\n" + valid = False + ln += 1 + if valid: + return True + else: + return error_list From 91b5e6ae7b58a2e0c1b051d0433fcf49910894e9 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Thu, 17 Oct 2019 15:27:38 +0200 Subject: [PATCH 38/79] Positional fix: OperationalError before general --- private/lib/sqlitedb.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py index be4c98f..c51fb2d 100644 --- a/private/lib/sqlitedb.py +++ b/private/lib/sqlitedb.py @@ -85,15 +85,17 @@ class SQLitedb: self.cursor.execute(qq, deliver) self.last_result = self.cursor.fetchall() self.connection.commit() - except sqlite3.Error as e: - print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) - self.last_result = [] except TypeError as e: print("Types in given tuple doesnt match to execute query \"%s\": %s" % (qq, e), file=stderr) self.last_result = [] except sqlite3.OperationalError as e: self._createTable() return self.safequery(qq, deliver) + except sqlite3.Error as e: + print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) + print(deliver) + print(type(e)) + self.last_result = [] return self.last_result def removeApplicantFromDB(self, userid: int) -> bool: @@ -107,13 +109,13 @@ class SQLitedb: try: self.last_result = self.cursor.execute("DELETE FROM `applications` WHERE id = ? ", [userid]) self.connection.commit() - except sqlite3.Error as e: - print(f"Could not delete user with id: {userid}, exception in DB: {e}") # @TODO LOGGING FFS - return False except sqlite3.OperationalError: print("The database has probably not yet seen any users, so it didnt create your table yet. Come back" "when a user tried to register") return False + except sqlite3.Error as e: + print(f"Could not delete user with id: {userid}, exception in DB: {e}") # @TODO LOGGING FFS + return False return True def removeApplicantFromDBperUsername(self, username: str) -> bool: @@ -127,13 +129,13 @@ class SQLitedb: try: self.last_result = self.cursor.execute("DELETE FROM `applications` WHERE username = ?", [username]) self.connection.commit() - except sqlite3.Error as e: - print(f"Could not delete user {username}, exception in DB: {e}") # @TODO LOGGING - return False except sqlite3.OperationalError: print("The database has probably not yet seen any users, so it didnt create your table yet. Come back" "when a user tried to register") return False + except sqlite3.Error as e: + print(f"Could not delete user {username}, exception in DB: {e}") # @TODO LOGGING + return False return True def _createTable(self): From 5a57a62780f6fba2e0899933257504484dce21d7 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Thu, 17 Oct 2019 15:27:57 +0200 Subject: [PATCH 39/79] Allow uppercase Chars afters first character in usernames --- private/lib/validator.py | 19 +++++++++++-------- public/userapplication.py | 2 +- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/private/lib/validator.py b/private/lib/validator.py index e476bbb..f00b60b 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -5,10 +5,10 @@ import lib.CFG as CFG def checkUsernameCharacters(username: str): - if " " not in username and "_" not in username and username.isascii() and username.islower() and \ + if " " not in username and "_" not in username and username.isascii() and username[:1].islower() and \ not username[0].isnumeric(): if not re.search(r"\W+", username): - if not re.search("[^a-z0-9]", username): + if not re.search("[^a-zA-Z0-9]", username): return True return False @@ -88,24 +88,27 @@ def checkImportFile(f): for row in reador: # if any of this fails move on to the next user, just print a relatively helpful message lel if not lib.validator.checkUsernameCharacters(row["username"]): - error_list += (f"Line {ln}: The username contains unsupported characters or starts with a number: '" + error_list += (f"Line {ln}: Username contains unsupported characters or starts with a number: '" f"{row['username']}'.\n") valid = False if not lib.validator.checkUsernameLength(row["username"]): - error_list += f"Line {ln}: The username '{row['username']}' is either too long(>16) or short(<3)\n" + error_list += f"Line {ln}: Username '{row['username']}' is either too long(>16) or short(<3)\n" valid = False if not lib.validator.checkSSHKey(row["pubkey"]): - error_list += f"Line {ln}: Following SSH-Key from user '{row['username']}' isn't valid: '{row['pubkey']}'."\ + error_list += f"Line {ln}: Following SSH-Key of user '{row['username']}' isn't valid: '{row['pubkey']}'."\ f"\n" valid = False if not lib.validator.checkEmail(row["email"]): - error_list += f"Line {ln}: The E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" + error_list += f"Line {ln}: E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" valid = False if not lib.validator.checkUserExists(row["username"]): - error_list += f"Line {ln}: The user '{row['username']}' already exists.\n" + error_list += f"Line {ln}: User '{row['username']}' already exists.\n" valid = False if not lib.validator.checkDatetimeFormat(row["timestamp"]): - error_list += f"Line {ln}: The timestamp '{row['timestamp']}' from user '{row['username']}' is invalid.\n" + error_list += f"Line {ln}: Timestamp '{row['timestamp']}' from user '{row['username']}' is invalid.\n" + valid = False + if int(row["status"]) > 1 or int(row["status"]) < 0: + error_list += f"Line {ln}: Status '{row['status']}' MUST be either 0 or 1.\n" valid = False ln += 1 if valid: diff --git a/public/userapplication.py b/public/userapplication.py index 6e2e66f..151b18c 100755 --- a/public/userapplication.py +++ b/public/userapplication.py @@ -77,7 +77,7 @@ def __checkSQLite(cursor, connection): def check_username(value): global VALID_USER - if " " in value or "_ " in value or not value.isascii() or not value.islower() or value[0].isnumeric(): + if " " in value or "_ " in value or not value.isascii() or not value[:1].islower() or value[0].isnumeric(): VALID_USER = False return False if re.search(r"\W+", value): From 5c6ecaf627a256e5b7015341ec3e885d502fa879 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 13:50:30 +0200 Subject: [PATCH 40/79] Modularize CFG.py into CWD, default_cmd and cmd ui --- private/lib/CFG.py | 28 ++-------------------------- private/lib/cwd.py | 9 +++++++++ private/lib/default_cmd.py | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 26 deletions(-) create mode 100644 private/lib/cwd.py create mode 100644 private/lib/default_cmd.py diff --git a/private/lib/CFG.py b/private/lib/CFG.py index a8f79c7..dd5c1fa 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -1,37 +1,13 @@ import argparse import configparser import logging -import os - -cwd = os.environ.get('TILDE_CONF') -if cwd is None: - cwd = os.getcwd() + "/applicationsconfig.ini" -else: - if os.path.isfile(cwd) is False: - cwd = os.getcwd() + "/applicationsconfig.ini" -# cwd is now either cwd/applicationsconfig or $TILDE_CONF -argparser = argparse.ArgumentParser(description='Tilde administration tools') -argparser.add_argument('-c', '--config', default=cwd, - type=str, help='Path to configuration file', required=False) -# store_true just stores true when the command is supplied, so it doesn't need choices nor types -argparser.add_argument('-u', '--unapproved', default=False, action="store_true", - help='only unapproved users. Default is only approved.', required=False) -argparser.add_argument('-a', '--approved', default=False, action="store_true", - help="Only approved Users.", required=False) -argparser.add_argument('-f', '--file', default="stdout", - type=str, help='write to file instead of stdout', required=False) -argparser.add_argument('--Import', default=False, action="store_true", - help="Import Users from file. Affects currently only Import.py.\n" - "Setting this to true will result in -f being interpreted as the input file to import " - "users from. The file MUST be a comma separated CSV file being readable having it's " - "defined columns written in the first line.") -args = argparser.parse_args() +import lib.default_cmd as default_cmd +args = default_cmd.argparser.parse_args() CONF_FILE = args.config config = configparser.ConfigParser() config.read(CONF_FILE) logging.basicConfig(format="%(asctime)s: %(message)s", level=int(config['LOG_LEVEL']['log_level']) ) -del cwd REG_FILE = config['DEFAULT']['applications_db'] diff --git a/private/lib/cwd.py b/private/lib/cwd.py new file mode 100644 index 0000000..09f0056 --- /dev/null +++ b/private/lib/cwd.py @@ -0,0 +1,9 @@ +import os + +cwd = os.environ.get('TILDE_CONF') +if cwd is None: + cwd = os.getcwd() + "/applicationsconfig.ini" +else: + if os.path.isfile(cwd) is False: + cwd = os.getcwd() + "/applicationsconfig.ini" +# cwd is now either cwd/applicationsconfig or $TILDE_CONF diff --git a/private/lib/default_cmd.py b/private/lib/default_cmd.py new file mode 100644 index 0000000..7572b68 --- /dev/null +++ b/private/lib/default_cmd.py @@ -0,0 +1,19 @@ +import argparse +import configparser +import logging +import lib.cwd +argparser = argparse.ArgumentParser(description='Tilde administration tools') +argparser.add_argument('-c', '--config', default=lib.cwd.cwd, + type=str, help='Path to configuration file', required=False) +# store_true just stores true when the command is supplied, so it doesn't need choices nor types +argparser.add_argument('-u', '--unapproved', default=False, action="store_true", + help='only unapproved users. Default is only approved.', required=False) +argparser.add_argument('-a', '--approved', default=False, action="store_true", + help="Only approved Users.", required=False) +argparser.add_argument('-f', '--file', default="stdout", + type=str, help='write to file instead of stdout', required=False) +argparser.add_argument('--Import', default=False, action="store_true", + help="Import Users from file. Affects currently only Import.py.\n" + "Setting this to true will result in -f being interpreted as the input file to import " + "users from. The file MUST be a comma separated CSV file being readable having it's " + "defined columns written in the first line.") \ No newline at end of file From 192e70e4a2e6975525d14ad2f522c099ddcaa85f Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 13:53:21 +0200 Subject: [PATCH 41/79] Seperate out even more, own dir for UIs default UI is now in private/lib/uis/default.py --- private/lib/CFG.py | 3 +-- private/lib/uis/config_ui.py | 6 ++++++ private/lib/{default_cmd.py => uis/default.py} | 13 +++++-------- 3 files changed, 12 insertions(+), 10 deletions(-) create mode 100644 private/lib/uis/config_ui.py rename private/lib/{default_cmd.py => uis/default.py} (77%) diff --git a/private/lib/CFG.py b/private/lib/CFG.py index dd5c1fa..bb10f35 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -1,7 +1,6 @@ -import argparse import configparser import logging -import lib.default_cmd as default_cmd +import lib.uis.default as default_cmd args = default_cmd.argparser.parse_args() CONF_FILE = args.config diff --git a/private/lib/uis/config_ui.py b/private/lib/uis/config_ui.py new file mode 100644 index 0000000..28fb370 --- /dev/null +++ b/private/lib/uis/config_ui.py @@ -0,0 +1,6 @@ +import argparse +import lib.cwd + +argparser = argparse.ArgumentParser(description='Tilde administration tools') +argparser.add_argument('-c', '--config', default=lib.cwd.cwd, + type=str, help='Path to configuration file', required=False) diff --git a/private/lib/default_cmd.py b/private/lib/uis/default.py similarity index 77% rename from private/lib/default_cmd.py rename to private/lib/uis/default.py index 7572b68..38f9b8f 100644 --- a/private/lib/default_cmd.py +++ b/private/lib/uis/default.py @@ -1,10 +1,7 @@ -import argparse -import configparser -import logging -import lib.cwd -argparser = argparse.ArgumentParser(description='Tilde administration tools') -argparser.add_argument('-c', '--config', default=lib.cwd.cwd, - type=str, help='Path to configuration file', required=False) +import lib.uis.config_ui + +argparser = lib.uis.config_ui.argparser + # store_true just stores true when the command is supplied, so it doesn't need choices nor types argparser.add_argument('-u', '--unapproved', default=False, action="store_true", help='only unapproved users. Default is only approved.', required=False) @@ -16,4 +13,4 @@ argparser.add_argument('--Import', default=False, action="store_true", help="Import Users from file. Affects currently only Import.py.\n" "Setting this to true will result in -f being interpreted as the input file to import " "users from. The file MUST be a comma separated CSV file being readable having it's " - "defined columns written in the first line.") \ No newline at end of file + "defined columns written in the first line.") From 8c3ac4060f90b3c8a2a0b2fe6f9c4bff56a41279 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 14:02:52 +0200 Subject: [PATCH 42/79] Split CFG.py to private/lib/cfgparse.py --- private/lib/CFG.py | 16 +++++----------- private/lib/cfgparse.py | 12 ++++++++++++ 2 files changed, 17 insertions(+), 11 deletions(-) create mode 100644 private/lib/cfgparse.py diff --git a/private/lib/CFG.py b/private/lib/CFG.py index bb10f35..55d2a71 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -1,12 +1,6 @@ -import configparser -import logging -import lib.uis.default as default_cmd +import lib.cfgparse -args = default_cmd.argparser.parse_args() -CONF_FILE = args.config -config = configparser.ConfigParser() -config.read(CONF_FILE) -logging.basicConfig(format="%(asctime)s: %(message)s", - level=int(config['LOG_LEVEL']['log_level']) - ) -REG_FILE = config['DEFAULT']['applications_db'] +args = lib.cfgparse.args +CONF_FILE = lib.cfgparse.CONF_FILE +config = lib.cfgparse.config +REG_FILE = lib.cfgparse.REG_FILE \ No newline at end of file diff --git a/private/lib/cfgparse.py b/private/lib/cfgparse.py new file mode 100644 index 0000000..bb10f35 --- /dev/null +++ b/private/lib/cfgparse.py @@ -0,0 +1,12 @@ +import configparser +import logging +import lib.uis.default as default_cmd + +args = default_cmd.argparser.parse_args() +CONF_FILE = args.config +config = configparser.ConfigParser() +config.read(CONF_FILE) +logging.basicConfig(format="%(asctime)s: %(message)s", + level=int(config['LOG_LEVEL']['log_level']) + ) +REG_FILE = config['DEFAULT']['applications_db'] From 77efb4b339dabcd3f2c0164b5e5d576e6819ee92 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 14:21:46 +0200 Subject: [PATCH 43/79] delete cfgparse again and get it going as it where before --- private/lib/CFG.py | 16 +++++++++++----- private/lib/cfgparse.py | 12 ------------ 2 files changed, 11 insertions(+), 17 deletions(-) delete mode 100644 private/lib/cfgparse.py diff --git a/private/lib/CFG.py b/private/lib/CFG.py index 55d2a71..bb10f35 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -1,6 +1,12 @@ -import lib.cfgparse +import configparser +import logging +import lib.uis.default as default_cmd -args = lib.cfgparse.args -CONF_FILE = lib.cfgparse.CONF_FILE -config = lib.cfgparse.config -REG_FILE = lib.cfgparse.REG_FILE \ No newline at end of file +args = default_cmd.argparser.parse_args() +CONF_FILE = args.config +config = configparser.ConfigParser() +config.read(CONF_FILE) +logging.basicConfig(format="%(asctime)s: %(message)s", + level=int(config['LOG_LEVEL']['log_level']) + ) +REG_FILE = config['DEFAULT']['applications_db'] diff --git a/private/lib/cfgparse.py b/private/lib/cfgparse.py deleted file mode 100644 index bb10f35..0000000 --- a/private/lib/cfgparse.py +++ /dev/null @@ -1,12 +0,0 @@ -import configparser -import logging -import lib.uis.default as default_cmd - -args = default_cmd.argparser.parse_args() -CONF_FILE = args.config -config = configparser.ConfigParser() -config.read(CONF_FILE) -logging.basicConfig(format="%(asctime)s: %(message)s", - level=int(config['LOG_LEVEL']['log_level']) - ) -REG_FILE = config['DEFAULT']['applications_db'] From cdc72a30f4226c2864e3eddc9845ace819e7ba8f Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 14:25:43 +0200 Subject: [PATCH 44/79] Factor out REG_FILE --- private/Import.py | 5 +++-- private/ListUsers.py | 2 +- private/lib/CFG.py | 3 +-- private/lib/validator.py | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/private/Import.py b/private/Import.py index c44a31a..dc671ed 100644 --- a/private/Import.py +++ b/private/Import.py @@ -4,7 +4,8 @@ import os import lib.UserExceptions -def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: tuple = tuple([])): +def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.config['DEFAULT']['applications_db'], + userids: tuple = tuple([])): if not os.path.isfile(fname): print(f"File {fname} don't exist") return None @@ -24,7 +25,7 @@ def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.REG_FILE, userids: import lib.sqlitedb import lib.System sysctl = lib.System.System() - sql = lib.sqlitedb.SQLitedb(CFG.REG_FILE) + sql = lib.sqlitedb.SQLitedb(CFG.config['DEFAULT']['applications_db']) reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: if row["status"] == "1": diff --git a/private/ListUsers.py b/private/ListUsers.py index 05f21a6..1c311d8 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -9,7 +9,7 @@ class ListUsers: usersFetch = None def __init__(self, uap: bool = CFG.args.unapproved, app: bool = CFG.args.approved): - self.db = SQLitedb(CFG.REG_FILE) + self.db = SQLitedb(CFG.config['DEFAULT']['applications_db']) if uap: # only unapproved users query = "SELECT * FROM `applications` WHERE status = '0'" elif app: # Approved users diff --git a/private/lib/CFG.py b/private/lib/CFG.py index bb10f35..bfab52c 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -8,5 +8,4 @@ config = configparser.ConfigParser() config.read(CONF_FILE) logging.basicConfig(format="%(asctime)s: %(message)s", level=int(config['LOG_LEVEL']['log_level']) - ) -REG_FILE = config['DEFAULT']['applications_db'] + ) \ No newline at end of file diff --git a/private/lib/validator.py b/private/lib/validator.py index f00b60b..1431239 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -34,7 +34,7 @@ def checkUserExists(username: str): def checkUserInDB(username: str): try: - L = lib.sqlitedb.SQLitedb(CFG.REG_FILE) + L = lib.sqlitedb.SQLitedb(CFG.config['DEFAULT']['applications_db']) fetched = L.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) if fetched: return True From 670aa3d9c392e8e3da32fef5053744e040d07c7a Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 14:27:16 +0200 Subject: [PATCH 45/79] Factor out CONF_FILE --- private/lib/CFG.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/private/lib/CFG.py b/private/lib/CFG.py index bfab52c..bd60f9f 100644 --- a/private/lib/CFG.py +++ b/private/lib/CFG.py @@ -1,11 +1,6 @@ import configparser -import logging import lib.uis.default as default_cmd args = default_cmd.argparser.parse_args() -CONF_FILE = args.config config = configparser.ConfigParser() -config.read(CONF_FILE) -logging.basicConfig(format="%(asctime)s: %(message)s", - level=int(config['LOG_LEVEL']['log_level']) - ) \ No newline at end of file +config.read(args.config) From 934b6bf75a337bf8d7ecf9ad7d4cf0a658d7c2dc Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 14:32:01 +0200 Subject: [PATCH 46/79] Factor out 'as CFG' --- private/Backup.py | 9 +++------ private/Import.py | 12 ++++++------ private/ListUsers.py | 10 +++++----- private/lib/validator.py | 6 +++--- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index ddb23ea..faa1063 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -3,10 +3,7 @@ import ListUsers import csv import io -import lib.CFG as CFG -import lib.validator -import lib.UserExceptions -import os +import lib.CFG class Backup: @@ -15,7 +12,7 @@ class Backup: dialect: str field_names: tuple - def __init__(self, fname: str = CFG.args.file, quoting: int = csv.QUOTE_NONNUMERIC, dialect: str = "excel"): + def __init__(self, fname: str = lib.CFG.args.file, quoting: int = csv.QUOTE_NONNUMERIC, dialect: str = "excel"): self.setFilename(fname) self.setQuoting(quoting) self.setDialect(dialect) @@ -52,7 +49,7 @@ if __name__ == "__main__": L = ListUsers.ListUsers() fetch = L.getFetch() B = Backup() - if CFG.args.Import: + if lib.CFG.args.Import: print("For importing please call the ./Import.py file with the --Import flag") else: B.BackupToFile(fetch) diff --git a/private/Import.py b/private/Import.py index dc671ed..0686db2 100644 --- a/private/Import.py +++ b/private/Import.py @@ -1,10 +1,10 @@ -import lib.CFG as CFG +import lib.CFG import csv import os import lib.UserExceptions -def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.config['DEFAULT']['applications_db'], +def ImportFromFile(fname: str = lib.CFG.args.file, db: str = lib.CFG.config['DEFAULT']['applications_db'], userids: tuple = tuple([])): if not os.path.isfile(fname): print(f"File {fname} don't exist") @@ -25,7 +25,7 @@ def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.config['DEFAULT'][' import lib.sqlitedb import lib.System sysctl = lib.System.System() - sql = lib.sqlitedb.SQLitedb(CFG.config['DEFAULT']['applications_db']) + sql = lib.sqlitedb.SQLitedb(lib.CFG.config['DEFAULT']['applications_db']) reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: if row["status"] == "1": @@ -64,11 +64,11 @@ def ImportFromFile(fname: str = CFG.args.file, db: str = CFG.config['DEFAULT'][' if __name__ == "__main__": try: - if not CFG.args.Import: + if not lib.CFG.args.Import: print("Error, need the import flag") - if not CFG.args.file: + if not lib.CFG.args.file: print("Error, need the import file") - if not CFG.args.file: + if not lib.CFG.args.file: print("You MUST set a CSV-file with the -f/--file flag that already exist") exit(1) ImportFromFile() diff --git a/private/ListUsers.py b/private/ListUsers.py index 1c311d8..2851073 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -1,15 +1,15 @@ #!/usr/bin/env python3 from lib.sqlitedb import SQLitedb -import lib.CFG as CFG +import lib.CFG class ListUsers: db = None usersFetch = None - def __init__(self, uap: bool = CFG.args.unapproved, app: bool = CFG.args.approved): - self.db = SQLitedb(CFG.config['DEFAULT']['applications_db']) + def __init__(self, uap: bool = lib.CFG.args.unapproved, app: bool = lib.CFG.args.approved): + self.db = SQLitedb(lib.CFG.config['DEFAULT']['applications_db']) if uap: # only unapproved users query = "SELECT * FROM `applications` WHERE status = '0'" elif app: # Approved users @@ -64,8 +64,8 @@ print(t.draw()) ret += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % ( user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] ) - if CFG.args.file != "stdout": - with open(CFG.args.file, 'w') as f: + if lib.CFG.args.file != "stdout": + with open(lib.CFG.args.file, 'w') as f: print(ret, file=f) else: print(ret) diff --git a/private/lib/validator.py b/private/lib/validator.py index 1431239..6019048 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -1,7 +1,7 @@ import re import pwd import lib.sqlitedb -import lib.CFG as CFG +import lib.CFG def checkUsernameCharacters(username: str): @@ -34,8 +34,8 @@ def checkUserExists(username: str): def checkUserInDB(username: str): try: - L = lib.sqlitedb.SQLitedb(CFG.config['DEFAULT']['applications_db']) - fetched = L.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) + ldb = lib.sqlitedb.SQLitedb(lib.CFG.config['DEFAULT']['applications_db']) + fetched = ldb.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) if fetched: return True except lib.sqlitedb.sqlite3.Error as e: From 710ceacd7c0b6a4ba608d8185fba7d389fff0e06 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 21:56:46 +0200 Subject: [PATCH 47/79] Breaking up the code smell regarding the CFG.py! It began smelling already but having some duplicate code across the interfaces is still better than having all of it all over the place. It enables to write specific flags which are nice to have. For example, Import.py requires the --Import flag because it WANTS the user to read the whole Help before it acts actually as an importer. When the user supplies something they should know what's currently happening. Also removes the hardcoded dependency on lib.CFG-Calls from most calls which was already embarassingly present. Introduced some db and cfg-variables which doesnt clutter anything but suck much less. In future we provide a set of default arguments and a bare minimum - config_ui as the bare minimum, default as the full blown storm. This is rather big because it also patches several other smells including a bug where a user from the db wouldnt be reported as existent --- Dockerfile | 13 +++---- private/Backup.py | 20 ++++++---- private/Import.py | 28 +++++++++----- private/ListUsers.py | 18 ++++++--- private/editUsers.py | 7 ++++ private/lib/UserExceptions.py | 1 - private/lib/uis/config_ui.py | 2 +- private/lib/uis/default.py | 5 --- private/lib/validator.py | 69 +++++++++++++++++------------------ 9 files changed, 90 insertions(+), 73 deletions(-) create mode 100644 private/editUsers.py diff --git a/Dockerfile b/Dockerfile index 0e3e6ad..8b73220 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,13 +26,6 @@ ENV TILDE_CONF="/app/data/applicationsconfig.ini" # private/{scripts, administrate.py}, public/{scripts, userapplications.py}, config/userapplicatonsconfig.ini #configs, logs, db COPY config/applicationsconfig.ini /app/data/applicationsconfig.ini - -# admin scripts -COPY private/ /app/admin/ - -# user accessible scripts -# Make TILDE_ENV -COPY public/ /app/user/ #SSH config into /etc :) COPY config/etc /etc @@ -41,5 +34,11 @@ RUN touch /app/data/applications.log # Doesnt work, @TODO why #RUN setfacl -R -m u:tilde:rwx /app/data/ RUN chown -R tilde /app/data +# admin scripts +COPY private/ /app/admin/ + +# user accessible scripts +# Make TILDE_ENV +COPY public/ /app/user/ RUN mkdir /app/user/.ssh CMD ["sh", "-c", " echo TILDE_CONF=$TILDE_CONF > /app/user/.ssh/environment && exec /usr/sbin/sshd -D"] diff --git a/private/Backup.py b/private/Backup.py index faa1063..bd324e2 100644 --- a/private/Backup.py +++ b/private/Backup.py @@ -3,7 +3,14 @@ import ListUsers import csv import io -import lib.CFG +import configparser +import lib.uis.default as default_cmd # Follows -u, -a, -f flags + + +default_cmd.argparser.description += " - Backups Tilde Users to stdout or a file." +args = default_cmd.argparser.parse_args() +config = configparser.ConfigParser() +config.read(args.config) class Backup: @@ -12,7 +19,7 @@ class Backup: dialect: str field_names: tuple - def __init__(self, fname: str = lib.CFG.args.file, quoting: int = csv.QUOTE_NONNUMERIC, dialect: str = "excel"): + def __init__(self, fname: str, quoting: int = csv.QUOTE_NONNUMERIC, dialect: str = "excel"): self.setFilename(fname) self.setQuoting(quoting) self.setDialect(dialect) @@ -46,13 +53,10 @@ class Backup: if __name__ == "__main__": try: - L = ListUsers.ListUsers() + L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) fetch = L.getFetch() - B = Backup() - if lib.CFG.args.Import: - print("For importing please call the ./Import.py file with the --Import flag") - else: - B.BackupToFile(fetch) + B = Backup(args.file) + B.BackupToFile(fetch) exit(0) except KeyboardInterrupt as e: pass diff --git a/private/Import.py b/private/Import.py index 0686db2..20a7a7c 100644 --- a/private/Import.py +++ b/private/Import.py @@ -1,11 +1,21 @@ -import lib.CFG import csv import os +import configparser import lib.UserExceptions +import lib.uis.config_ui # dont go to default, just following -c flag +ArgParser = lib.uis.config_ui.argparser +ArgParser.description += "- Imports a CSV file consisting of user specific details to the database" +ArgParser.add_argument('-f', '--file', default="stdout", + type=str, help='Import from CSV file', required=True) +ArgParser.add_argument('--Import', default=False, action="store_true", + help="Import Users.", required=True) +args = ArgParser.parse_args() +config = configparser.ConfigParser() +config.read(args.config) -def ImportFromFile(fname: str = lib.CFG.args.file, db: str = lib.CFG.config['DEFAULT']['applications_db'], - userids: tuple = tuple([])): + +def ImportFromFile(fname: str, db: str, userids: tuple = tuple([])): if not os.path.isfile(fname): print(f"File {fname} don't exist") return None @@ -18,14 +28,14 @@ def ImportFromFile(fname: str = lib.CFG.args.file, db: str = lib.CFG.config['DEF try: with open(fname, 'r', newline='') as f: import lib.validator - err = lib.validator.checkImportFile(f) + sql = lib.sqlitedb.SQLitedb(db) + err = lib.validator.checkImportFile(fname, db) if err is not True: print(err) exit(0) import lib.sqlitedb import lib.System sysctl = lib.System.System() - sql = lib.sqlitedb.SQLitedb(lib.CFG.config['DEFAULT']['applications_db']) reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: if row["status"] == "1": @@ -64,14 +74,14 @@ def ImportFromFile(fname: str = lib.CFG.args.file, db: str = lib.CFG.config['DEF if __name__ == "__main__": try: - if not lib.CFG.args.Import: + if not args.Import: print("Error, need the import flag") - if not lib.CFG.args.file: + if not args.file: print("Error, need the import file") - if not lib.CFG.args.file: + if not args.file: print("You MUST set a CSV-file with the -f/--file flag that already exist") exit(1) - ImportFromFile() + ImportFromFile(args.file, config['DEFAULT']['applications_db']) exit(0) except KeyboardInterrupt as e: pass diff --git a/private/ListUsers.py b/private/ListUsers.py index 2851073..48fcdc8 100644 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -1,15 +1,21 @@ #!/usr/bin/env python3 from lib.sqlitedb import SQLitedb -import lib.CFG +import configparser +import lib.uis.default as default_cmd # Follows -u, -a, -f flags + +default_cmd.argparser.description += " - Lists Users from the Tilde database." +args = default_cmd.argparser.parse_args() +config = configparser.ConfigParser() +config.read(args.config) class ListUsers: db = None usersFetch = None - def __init__(self, uap: bool = lib.CFG.args.unapproved, app: bool = lib.CFG.args.approved): - self.db = SQLitedb(lib.CFG.config['DEFAULT']['applications_db']) + def __init__(self, db: str, uap: bool = False, app: bool = True): + self.db = SQLitedb(db) if uap: # only unapproved users query = "SELECT * FROM `applications` WHERE status = '0'" elif app: # Approved users @@ -33,7 +39,7 @@ class ListUsers: if __name__ == "__main__": try: ret = "" - L = ListUsers() + L = ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) fetch = L.getFetch() # @TODO MAYBE best solution: https://pypi.org/project/texttable/ # examle: @@ -64,8 +70,8 @@ print(t.draw()) ret += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % ( user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] ) - if lib.CFG.args.file != "stdout": - with open(lib.CFG.args.file, 'w') as f: + if args.file != "stdout": + with open(args.file, 'w') as f: print(ret, file=f) else: print(ret) diff --git a/private/editUsers.py b/private/editUsers.py new file mode 100644 index 0000000..b38341f --- /dev/null +++ b/private/editUsers.py @@ -0,0 +1,7 @@ +import lib.uis.default + +argparser = lib.uis.default.argparser + +args = argparser.parse_args() +config = argparser.ConfigParser() +config.read(args.config) diff --git a/private/lib/UserExceptions.py b/private/lib/UserExceptions.py index 926c8e5..a3e4637 100644 --- a/private/lib/UserExceptions.py +++ b/private/lib/UserExceptions.py @@ -44,4 +44,3 @@ class UsernameTooLong(User): class UsernameInvalidCharacters(User): pass - diff --git a/private/lib/uis/config_ui.py b/private/lib/uis/config_ui.py index 28fb370..6f70e82 100644 --- a/private/lib/uis/config_ui.py +++ b/private/lib/uis/config_ui.py @@ -1,6 +1,6 @@ import argparse import lib.cwd -argparser = argparse.ArgumentParser(description='Tilde administration tools') +argparser = argparse.ArgumentParser(description='Tilde administration tools ', conflict_handler="resolve") argparser.add_argument('-c', '--config', default=lib.cwd.cwd, type=str, help='Path to configuration file', required=False) diff --git a/private/lib/uis/default.py b/private/lib/uis/default.py index 38f9b8f..6435e73 100644 --- a/private/lib/uis/default.py +++ b/private/lib/uis/default.py @@ -9,8 +9,3 @@ argparser.add_argument('-a', '--approved', default=False, action="store_true", help="Only approved Users.", required=False) argparser.add_argument('-f', '--file', default="stdout", type=str, help='write to file instead of stdout', required=False) -argparser.add_argument('--Import', default=False, action="store_true", - help="Import Users from file. Affects currently only Import.py.\n" - "Setting this to true will result in -f being interpreted as the input file to import " - "users from. The file MUST be a comma separated CSV file being readable having it's " - "defined columns written in the first line.") diff --git a/private/lib/validator.py b/private/lib/validator.py index 6019048..a5c3be2 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -1,7 +1,6 @@ import re import pwd import lib.sqlitedb -import lib.CFG def checkUsernameCharacters(username: str): @@ -21,20 +20,18 @@ def checkUsernameLength(username: str): return True -def checkUserExists(username: str): +def checkUserExists(username: str, db: str): try: pwd.getpwnam(username) except KeyError: return True # User already exists else: - if checkUserInDB(username): - return True return False -def checkUserInDB(username: str): +def checkUserInDB(username: str, db: str): try: - ldb = lib.sqlitedb.SQLitedb(lib.CFG.config['DEFAULT']['applications_db']) + ldb = lib.sqlitedb.SQLitedb(db) fetched = ldb.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) if fetched: return True @@ -78,39 +75,39 @@ def checkDatetimeFormat(form: str): return True -def checkImportFile(f): - import csv - reador = csv.DictReader(f) +def checkImportFile(fname: str, db: str): error_list = str() valid = True ln = 1 # line number - - for row in reador: - # if any of this fails move on to the next user, just print a relatively helpful message lel - if not lib.validator.checkUsernameCharacters(row["username"]): - error_list += (f"Line {ln}: Username contains unsupported characters or starts with a number: '" - f"{row['username']}'.\n") - valid = False - if not lib.validator.checkUsernameLength(row["username"]): - error_list += f"Line {ln}: Username '{row['username']}' is either too long(>16) or short(<3)\n" - valid = False - if not lib.validator.checkSSHKey(row["pubkey"]): - error_list += f"Line {ln}: Following SSH-Key of user '{row['username']}' isn't valid: '{row['pubkey']}'."\ - f"\n" - valid = False - if not lib.validator.checkEmail(row["email"]): - error_list += f"Line {ln}: E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" - valid = False - if not lib.validator.checkUserExists(row["username"]): - error_list += f"Line {ln}: User '{row['username']}' already exists.\n" - valid = False - if not lib.validator.checkDatetimeFormat(row["timestamp"]): - error_list += f"Line {ln}: Timestamp '{row['timestamp']}' from user '{row['username']}' is invalid.\n" - valid = False - if int(row["status"]) > 1 or int(row["status"]) < 0: - error_list += f"Line {ln}: Status '{row['status']}' MUST be either 0 or 1.\n" - valid = False - ln += 1 + with open(fname, 'r', newline='') as f: + import csv + reador = csv.DictReader(f) + for row in reador: + # if any of this fails move on to the next user, just print a relatively helpful message lel + if not lib.validator.checkUsernameCharacters(row["username"]): + error_list += (f"Line {ln}: Username contains unsupported characters or starts with a number: '" + f"{row['username']}'.\n") + valid = False + if not lib.validator.checkUsernameLength(row["username"]): + error_list += f"Line {ln}: Username '{row['username']}' is either too long(>16) or short(<3)\n" + valid = False + if not lib.validator.checkSSHKey(row["pubkey"]): + error_list += f"Line {ln}: Following SSH-Key of user '{row['username']}' isn't valid: '{row['pubkey']}'."\ + f"\n" + valid = False + if not lib.validator.checkEmail(row["email"]): + error_list += f"Line {ln}: E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" + valid = False + if not lib.validator.checkUserExists(row["username"], db) or checkUserInDB(row["username"], db): + error_list += f"Line {ln}: User '{row['username']}' already exists.\n" + valid = False + if not lib.validator.checkDatetimeFormat(row["timestamp"]): + error_list += f"Line {ln}: Timestamp '{row['timestamp']}' from user '{row['username']}' is invalid.\n" + valid = False + if int(row["status"]) > 1 or int(row["status"]) < 0: + error_list += f"Line {ln}: Status '{row['status']}' MUST be either 0 or 1.\n" + valid = False + ln += 1 if valid: return True else: From 27e5f0445a8e75b347ed54b4185f4419ecc84ffb Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sat, 19 Oct 2019 22:49:58 +0200 Subject: [PATCH 48/79] Shortcuts to source when working on ssh-reg Just source the file and you get the aliases for dev_run, dev_stop(affects containers) and dev_build which rebuilds the ssh-reg image --- private/scripts/shortcuts.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 private/scripts/shortcuts.sh diff --git a/private/scripts/shortcuts.sh b/private/scripts/shortcuts.sh new file mode 100644 index 0000000..2076bef --- /dev/null +++ b/private/scripts/shortcuts.sh @@ -0,0 +1,4 @@ +alias dev_run="docker container run -l dev-ssh-reg --name dev-ssh-reg --rm -itd -v $PWD/private/:/app/admin ssh-reg" +alias dev_stop="docker container stop $(docker ps -a -q --filter ancestor=ssh-reg --format='{{.ID}}')" +alias dev_build="docker build -t ssh-reg:latest --force-rm ." +alias dev_bash="docker container exec -it dev-ssh-reg bash -c 'cd /app/admin; exec bash --login -i'" From 96097e26b12be6c55e4aecf6070b13b030cd1cea Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 20 Oct 2019 15:23:46 +0200 Subject: [PATCH 49/79] Catched that little error and killed it! --- private/lib/validator.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/private/lib/validator.py b/private/lib/validator.py index a5c3be2..e1e58f9 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -20,7 +20,7 @@ def checkUsernameLength(username: str): return True -def checkUserExists(username: str, db: str): +def checkUserExists(username: str): try: pwd.getpwnam(username) except KeyError: @@ -98,7 +98,7 @@ def checkImportFile(fname: str, db: str): if not lib.validator.checkEmail(row["email"]): error_list += f"Line {ln}: E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" valid = False - if not lib.validator.checkUserExists(row["username"], db) or checkUserInDB(row["username"], db): + if not lib.validator.checkUserExists(row["username"]) or checkUserInDB(row["username"], db): error_list += f"Line {ln}: User '{row['username']}' already exists.\n" valid = False if not lib.validator.checkDatetimeFormat(row["timestamp"]): From 7786df3761ac9b59de3dd02349bb334aa57bbee1 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 20 Oct 2019 15:24:11 +0200 Subject: [PATCH 50/79] System: Introduce function to write just write the auth file --- private/lib/System.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/private/lib/System.py b/private/lib/System.py index 92e081c..39ec9f7 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -80,11 +80,7 @@ class System: raise lib.UserExceptions.SSHDirUncreatable(f"Could not create {ssh_dir}: Exception: {e}") try: - with open(ssh_dir + "authorized_keys", "w") as f: - print(pubkey, file=f) - f.close() - os.chmod(ssh_dir + "authorized_keys", 0o700) # directory is already 777? - os.chmod(ssh_dir, 0o700) # directory is already 777? + self.write_ssh(pubkey, ssh_dir) except OSError as e: raise lib.UserExceptions.ModifyFilesystem( f"Could not write and/or chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}") @@ -99,6 +95,12 @@ class System: raise lib.UserExceptions.General(f"PWD can't find {username}: {e}") return True + def write_ssh(self, key: str, ssh_dir: str): + with open(ssh_dir + "authorized_keys", "w") as f: + print(key, file=f) + f.close() + os.chmod(ssh_dir + "authorized_keys", 0o700) # we dont care about the directory here + def lock_user_pw(self, username: str, cc: tuple = tuple(["usermod", "--lock"])) -> bool: """Lock a users password so it stays empty From 84431bda1d30dd0e2412268cbacbe750a7bec7e1 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 20 Oct 2019 15:25:14 +0200 Subject: [PATCH 51/79] Edit: Sceleton, Interface and first argument The sceleton for the interface and it's first possible argument are finished, so a users pbukey can be changed now without touching the DB itself directly. Checking for users existence and the presence of options/arguments is done beforehand and will throw out when nothing useful got delivered to the script. --- private/editUsers.py | 83 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 4 deletions(-) diff --git a/private/editUsers.py b/private/editUsers.py index b38341f..3137a51 100644 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -1,7 +1,82 @@ -import lib.uis.default +import configparser +import lib.uis.config_ui # only follow -c flag +import lib.validator +import sqlite3 -argparser = lib.uis.default.argparser +lib.uis.config_ui.argparser.description += " - Edit Tilde Users" +ArgParser = lib.uis.config_ui.argparser +ArgParser.add_argument('--user', type=str, + help='Tilde users name to edit', required=True) -args = argparser.parse_args() -config = argparser.ConfigParser() +Mutually = ArgParser.add_mutually_exclusive_group() +Mutually.add_argument('-r', '--remove', default=False, action="store_true", + help='Remove an approved/unapproved User from the system. Effectively purges him.', + required=False) +Mutually.add_argument('-a', '--approve', default=False, action="store_true", + help="Approve the given user", required=False) +Mutually.add_argument("--verify", default=True, action="store_false", + help="Turns off value checks", + required=False) + +ArgParser.add_argument('--sshpubkey', type=str, default=None, + help="Stores the new given SSH-Key in given user", required=False) +ArgParser.add_argument('--name', type=str, default=None, + help="Sets the stored name of the given user") +ArgParser.add_argument('--username', type=str, default=None, + help="Rename given User") +ArgParser.add_argument('--email', type=str, default=None, + help="Set new email address for given user") +ArgParser.add_argument('--status', type=int, default=None, + help="Set status of given user") +args = ArgParser.parse_args() +config = configparser.ConfigParser() config.read(args.config) + + +if __name__ == "__main__": + try: + db = config['DEFAULT']['applications_db'] + if not args.sshpubkey and not args.name and not args.username and not args.email and not args.status \ + and not args.approve and not args.remove: + print(f"Well, SOMETHING must be done with {args.user} ;-)") + exit(1) + if not lib.validator.checkUserInDB(args.user, db): + print(f"User {args.user} doesn't exist in the database.") + exit(1) + import lib.sqlitedb + import lib.System + import lib.UserExceptions + DB = lib.sqlitedb.SQLitedb(db) + Sysctl = lib.System.System() + if not DB: + print("Couldn't establish connection to database") + exit(1) + if args.sshpubkey: + if not lib.validator.checkSSHKey(args.sshpubkey): + print(f"Pubkey {args.sshpubkey} isn't valid.") + exit(1) + try: + DB.safequery("UPDATE `applications` SET `pubkey`=? WHERE `username`=?", + tuple([args.sshpubkey, args.user])) + except sqlite3.Error as e: + print(f"Something unexpected happened! {e}") + exit(1) + fetch = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user])) + if int(fetch[0]["status"]) == 1: + try: + Sysctl.make_ssh_usable(args.user, args.sshpubkey) + except lib.UserExceptions.ModifyFilesystem as e: + print(f"One action failed during writing the ssh key back into the authorization file") + print(f"{args.user} updated successfully.") + + if args.name: + pass + if args.username: + print(f"{args.username}") + if args.email: + print(f"{args.email}") + if args.status: + print(f"{args.status}") + exit(0) + except KeyboardInterrupt as e: + pass From 786b652b2189cfb640cde305df6a233c3a837a0b Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Sun, 20 Oct 2019 15:37:03 +0200 Subject: [PATCH 52/79] update dev environment with disable possiblities --- private/scripts/disable.sh | 5 +++++ private/scripts/shortcuts.sh | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 private/scripts/disable.sh diff --git a/private/scripts/disable.sh b/private/scripts/disable.sh new file mode 100644 index 0000000..1ec7fc8 --- /dev/null +++ b/private/scripts/disable.sh @@ -0,0 +1,5 @@ +unalias dev_build +unalias dev_run +unalias dev_bash +unalias dev_stop +unalias dev_disable diff --git a/private/scripts/shortcuts.sh b/private/scripts/shortcuts.sh index 2076bef..7a3a481 100644 --- a/private/scripts/shortcuts.sh +++ b/private/scripts/shortcuts.sh @@ -1,4 +1,5 @@ alias dev_run="docker container run -l dev-ssh-reg --name dev-ssh-reg --rm -itd -v $PWD/private/:/app/admin ssh-reg" -alias dev_stop="docker container stop $(docker ps -a -q --filter ancestor=ssh-reg --format='{{.ID}}')" +alias dev_stop="docker container stop dev-ssh-reg" alias dev_build="docker build -t ssh-reg:latest --force-rm ." alias dev_bash="docker container exec -it dev-ssh-reg bash -c 'cd /app/admin; exec bash --login -i'" +alias dev_disable="source private/scripts/disable.sh" From 7091cbcbd270e05652b17e3494aab910f3912e02 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Mon, 21 Oct 2019 13:07:30 +0200 Subject: [PATCH 53/79] Validator: checkName taken from registration form --- private/lib/validator.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/private/lib/validator.py b/private/lib/validator.py index e1e58f9..5678416 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -3,7 +3,7 @@ import pwd import lib.sqlitedb -def checkUsernameCharacters(username: str): +def checkUsernameCharacters(username: str) -> bool: if " " not in username and "_" not in username and username.isascii() and username[:1].islower() and \ not username[0].isnumeric(): if not re.search(r"\W+", username): @@ -12,7 +12,7 @@ def checkUsernameCharacters(username: str): return False -def checkUsernameLength(username: str): +def checkUsernameLength(username: str) -> bool: if len(username) > 16: return False if len(username) < 3: @@ -20,7 +20,7 @@ def checkUsernameLength(username: str): return True -def checkUserExists(username: str): +def checkUserExists(username: str) -> bool: try: pwd.getpwnam(username) except KeyError: @@ -29,7 +29,7 @@ def checkUserExists(username: str): return False -def checkUserInDB(username: str, db: str): +def checkUserInDB(username: str, db: str) -> bool: try: ldb = lib.sqlitedb.SQLitedb(db) fetched = ldb.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) @@ -40,7 +40,7 @@ def checkUserInDB(username: str, db: str): return False -def checkSSHKey(key: str): +def checkSSHKey(key: str) -> bool: # taken from https://github.com/hashbang/provisor/blob/master/provisor/utils.py, all belongs to them! ;) import base64 if len(key) > 8192 or len(key) < 80: @@ -59,14 +59,14 @@ def checkSSHKey(key: str): return True -def checkEmail(mail: str): +def checkEmail(mail: str) -> bool: if not re.match("(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", mail): return False else: return True -def checkDatetimeFormat(form: str): +def checkDatetimeFormat(form: str) -> bool : import datetime try: datetime.datetime.strptime(form, "%Y-%m-%d %H:%M:%S") @@ -75,7 +75,14 @@ def checkDatetimeFormat(form: str): return True -def checkImportFile(fname: str, db: str): +def checkName(name: str) -> bool: + if not re.match("\w+\s*\w", name): + return False + else: + return True + + +def checkImportFile(fname: str, db: str) -> bool: error_list = str() valid = True ln = 1 # line number @@ -84,6 +91,10 @@ def checkImportFile(fname: str, db: str): reador = csv.DictReader(f) for row in reador: # if any of this fails move on to the next user, just print a relatively helpful message lel + if not lib.validator.checkName(row["name"]): + error_list += f"Line{ln}: {row['name']} seems not legit. Character followed by character should be " \ + f"correct.\n" + valid = False if not lib.validator.checkUsernameCharacters(row["username"]): error_list += (f"Line {ln}: Username contains unsupported characters or starts with a number: '" f"{row['username']}'.\n") From fb7544bd8c873d20c120c7572b6b84693757fcf9 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Mon, 21 Oct 2019 13:08:00 +0200 Subject: [PATCH 54/79] edit: Added Name, email and first steps for status changes --- private/editUsers.py | 34 +++++++++++++++++++++++++--------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/private/editUsers.py b/private/editUsers.py index 3137a51..4bb1970 100644 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -1,6 +1,9 @@ import configparser import lib.uis.config_ui # only follow -c flag import lib.validator +import lib.sqlitedb +import lib.System +import lib.UserExceptions import sqlite3 lib.uis.config_ui.argparser.description += " - Edit Tilde Users" @@ -43,9 +46,6 @@ if __name__ == "__main__": if not lib.validator.checkUserInDB(args.user, db): print(f"User {args.user} doesn't exist in the database.") exit(1) - import lib.sqlitedb - import lib.System - import lib.UserExceptions DB = lib.sqlitedb.SQLitedb(db) Sysctl = lib.System.System() if not DB: @@ -66,17 +66,33 @@ if __name__ == "__main__": try: Sysctl.make_ssh_usable(args.user, args.sshpubkey) except lib.UserExceptions.ModifyFilesystem as e: - print(f"One action failed during writing the ssh key back into the authorization file") + print(f"One action failed during writing the ssh key back to the authorization file. {e}") print(f"{args.user} updated successfully.") if args.name: - pass - if args.username: - print(f"{args.username}") + if not lib.validator.checkName(args.name): + print(f"{args.name} is not a valid Name.") + exit(1) + try: + DB.safequery("UPDATE `applications` SET `name` =? WHERE `username` =?", tuple([args.name, args.user])) + except sqlite3.Error as e: + print(f"Couldn't write {args.name} to database: {e}") if args.email: - print(f"{args.email}") + if not lib.validator.checkEmail(args.email): + print(f"{args.email} is not a valid Mail address!") + exit(1) + try: + DB.safequery("UPDATE `applications` SET `email` =? WHERE `username` =?", tuple([args.email])) + except sqlite3.Error as e: + print(f"Couldn't write {args.email} to the database. {e}") if args.status: - print(f"{args.status}") + if args.status != 0 and args.status != 1: + print("Only 0 and 1 are valid status, where 1 is activated and 0 is unapproved.") + exit(0) + # @TODO: Get Users current status and purge him from the disk if neccessary + # @TODO: When the User had 0 and got 1 he should be created as well + if args.username: + print(f"{args.username}") exit(0) except KeyboardInterrupt as e: pass From eb6f3da3d790fc520b15b5000073123da8b58323 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Mon, 21 Oct 2019 18:33:14 +0200 Subject: [PATCH 55/79] System: Account for userdel returning 6 on success... just because it couldnt delete the users mail. --- private/editUsers.py | 98 ------------------------------------------- private/lib/System.py | 2 +- 2 files changed, 1 insertion(+), 99 deletions(-) delete mode 100644 private/editUsers.py diff --git a/private/editUsers.py b/private/editUsers.py deleted file mode 100644 index 4bb1970..0000000 --- a/private/editUsers.py +++ /dev/null @@ -1,98 +0,0 @@ -import configparser -import lib.uis.config_ui # only follow -c flag -import lib.validator -import lib.sqlitedb -import lib.System -import lib.UserExceptions -import sqlite3 - -lib.uis.config_ui.argparser.description += " - Edit Tilde Users" -ArgParser = lib.uis.config_ui.argparser -ArgParser.add_argument('--user', type=str, - help='Tilde users name to edit', required=True) - -Mutually = ArgParser.add_mutually_exclusive_group() -Mutually.add_argument('-r', '--remove', default=False, action="store_true", - help='Remove an approved/unapproved User from the system. Effectively purges him.', - required=False) -Mutually.add_argument('-a', '--approve', default=False, action="store_true", - help="Approve the given user", required=False) -Mutually.add_argument("--verify", default=True, action="store_false", - help="Turns off value checks", - required=False) - -ArgParser.add_argument('--sshpubkey', type=str, default=None, - help="Stores the new given SSH-Key in given user", required=False) -ArgParser.add_argument('--name', type=str, default=None, - help="Sets the stored name of the given user") -ArgParser.add_argument('--username', type=str, default=None, - help="Rename given User") -ArgParser.add_argument('--email', type=str, default=None, - help="Set new email address for given user") -ArgParser.add_argument('--status', type=int, default=None, - help="Set status of given user") -args = ArgParser.parse_args() -config = configparser.ConfigParser() -config.read(args.config) - - -if __name__ == "__main__": - try: - db = config['DEFAULT']['applications_db'] - if not args.sshpubkey and not args.name and not args.username and not args.email and not args.status \ - and not args.approve and not args.remove: - print(f"Well, SOMETHING must be done with {args.user} ;-)") - exit(1) - if not lib.validator.checkUserInDB(args.user, db): - print(f"User {args.user} doesn't exist in the database.") - exit(1) - DB = lib.sqlitedb.SQLitedb(db) - Sysctl = lib.System.System() - if not DB: - print("Couldn't establish connection to database") - exit(1) - if args.sshpubkey: - if not lib.validator.checkSSHKey(args.sshpubkey): - print(f"Pubkey {args.sshpubkey} isn't valid.") - exit(1) - try: - DB.safequery("UPDATE `applications` SET `pubkey`=? WHERE `username`=?", - tuple([args.sshpubkey, args.user])) - except sqlite3.Error as e: - print(f"Something unexpected happened! {e}") - exit(1) - fetch = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user])) - if int(fetch[0]["status"]) == 1: - try: - Sysctl.make_ssh_usable(args.user, args.sshpubkey) - except lib.UserExceptions.ModifyFilesystem as e: - print(f"One action failed during writing the ssh key back to the authorization file. {e}") - print(f"{args.user} updated successfully.") - - if args.name: - if not lib.validator.checkName(args.name): - print(f"{args.name} is not a valid Name.") - exit(1) - try: - DB.safequery("UPDATE `applications` SET `name` =? WHERE `username` =?", tuple([args.name, args.user])) - except sqlite3.Error as e: - print(f"Couldn't write {args.name} to database: {e}") - if args.email: - if not lib.validator.checkEmail(args.email): - print(f"{args.email} is not a valid Mail address!") - exit(1) - try: - DB.safequery("UPDATE `applications` SET `email` =? WHERE `username` =?", tuple([args.email])) - except sqlite3.Error as e: - print(f"Couldn't write {args.email} to the database. {e}") - if args.status: - if args.status != 0 and args.status != 1: - print("Only 0 and 1 are valid status, where 1 is activated and 0 is unapproved.") - exit(0) - # @TODO: Get Users current status and purge him from the disk if neccessary - # @TODO: When the User had 0 and got 1 he should be created as well - if args.username: - print(f"{args.username}") - exit(0) - except KeyboardInterrupt as e: - pass diff --git a/private/lib/System.py b/private/lib/System.py index 39ec9f7..2cf660f 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -181,7 +181,7 @@ class System: return True else: ret = subprocess.call(cc) - if ret != 0: + if ret != 0 or ret != 6: raise lib.UserExceptions.UnknownReturnCode( f"Could not delete user with command {cc}. Return code: {ret}") return True From fb9a98fb81e280e7a7afaaa43b1c5a5ad3ec6ad3 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Mon, 21 Oct 2019 18:34:41 +0200 Subject: [PATCH 56/79] Edit: Did delete, but here it is back! It can now create users on disk, delete from disk(by changing the status), change everything that really matters, except the username yet --- private/editUsers.py | 138 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 private/editUsers.py diff --git a/private/editUsers.py b/private/editUsers.py new file mode 100644 index 0000000..feb41b6 --- /dev/null +++ b/private/editUsers.py @@ -0,0 +1,138 @@ +import configparser +import lib.uis.config_ui # only follow -c flag +import lib.validator +import lib.sqlitedb +import lib.System +import lib.UserExceptions +import sqlite3 + +lib.uis.config_ui.argparser.description += " - Edit Tilde Users" +ArgParser = lib.uis.config_ui.argparser +ArgParser.add_argument('--user', type=str, + help='Tilde users name to edit', required=True) + +Mutually = ArgParser.add_mutually_exclusive_group() +Mutually.add_argument('-r', '--remove', default=False, action="store_true", + help='Remove an approved/unapproved User from the system. Effectively purges him.', + required=False) +Mutually.add_argument('-a', '--approve', default=False, action="store_true", + help="Approve the given user", required=False) +Mutually.add_argument("--verify", default=True, action="store_false", + help="Turns off value checks", + required=False) + +ArgParser.add_argument('--sshpubkey', type=str, default=None, + help="Stores the new given SSH-Key in given user", required=False) +ArgParser.add_argument('--name', type=str, default=None, + help="Sets the stored name of the given user") +ArgParser.add_argument('--username', type=str, default=None, + help="Rename given User") +ArgParser.add_argument('--email', type=str, default=None, + help="Set new email address for given user") +ArgParser.add_argument('--status', type=int, default=None, + help="Set status of given user") +args = ArgParser.parse_args() +config = configparser.ConfigParser() +config.read(args.config) + + +if __name__ == "__main__": + try: + db = config['DEFAULT']['applications_db'] + if not args.sshpubkey and not args.name and not args.username and not args.email and args.status is None \ + and not args.approve and not args.remove: + print(f"Well, SOMETHING must be done with {args.user} ;-)") + exit(1) + if not lib.validator.checkUserInDB(args.user, db): + print(f"User {args.user} doesn't exist in the database.") + exit(1) + DB = lib.sqlitedb.SQLitedb(db) + Sysctl = lib.System.System() + if not DB: + print("Couldn't establish connection to database") + exit(1) + if args.sshpubkey: + if not lib.validator.checkSSHKey(args.sshpubkey): + print(f"Pubkey {args.sshpubkey} isn't valid.") + exit(1) + try: + DB.safequery("UPDATE `applications` SET `pubkey`=? WHERE `username`=?", + tuple([args.sshpubkey, args.user])) + except sqlite3.Error as e: + print(f"Something unexpected happened! {e}") + exit(1) + fetch = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user])) + if int(fetch[0]["status"]) == 1: + try: + Sysctl.make_ssh_usable(args.user, args.sshpubkey) + except lib.UserExceptions.ModifyFilesystem as e: + print(f"One action failed during writing the ssh key back to the authorization file. {e}") + print(f"{args.user} updated successfully.") + + if args.name: + if not lib.validator.checkName(args.name): + print(f"{args.name} is not a valid Name.") + exit(1) + try: + DB.safequery("UPDATE `applications` SET `name` =? WHERE `username` =?", tuple([args.name, args.user])) + except sqlite3.Error as e: + print(f"Couldn't write {args.name} to database: {e}") + if args.email: + if not lib.validator.checkEmail(args.email): + print(f"{args.email} is not a valid Mail address!") + exit(1) + try: + DB.safequery("UPDATE `applications` SET `email` =? WHERE `username` =?", tuple([args.email])) + except sqlite3.Error as e: + print(f"Couldn't write {args.email} to the database. {e}") + if args.status is not None: + if args.status != 0 and args.status != 1: + print("Only 0 and 1 are valid status, where 1 is activated and 0 is unapproved.") + exit(0) + CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0] + # just takes first result out of the dict + if args.status == int(CurrentUser["status"]): + print(f"Old and new Status matches, didn't change") + if args.status == 0 and int(CurrentUser["status"]) == 1: + try: + Sysctl.removeUser(args.user) + except lib.UserExceptions.UnknownReturnCode as e: + print(f"Couldn't remove {args.user} from the system, unknown return code: {e}") + exit(1) + try: + DB.safequery("UPDATE `applications` SET `status` =? WHERE `id`=?", + tuple([args.status, CurrentUser["id"]])) + except sqlite3.Error as e: + print(f"Did purge from disk but couldnt update database for {args.user}") + exit(1) + if args.status == 1 and int(CurrentUser["status"]) == 0: + try: + DB.safequery("UPDATE `applications` SET `status`=? WHERE `username`=?", + tuple([args.status, args.user])) + except sqlite3.Error as e: + print(f"Couldn't update Users status in database") + exit(1) + try: + Sysctl.register(args.user) + Sysctl.lock_user_pw(args.user) + Sysctl.add_to_usergroup(args.user) + Sysctl.make_ssh_usable(args.user, CurrentUser["pubkey"]) + except lib.UserExceptions.UserExistsAlready as UEA: + print(f"Somehow the user exists already on the system! {UEA}") + exit(1) + except lib.UserExceptions.UnknownReturnCode as URC: + print(f"Unknown return code: {URC}") + exit(1) + except lib.UserExceptions.SSHDirUncreatable as SDU: + print(f"Couldnt create ssh directory for {args.user}, exception: {SDU}") + exit(1) + except lib.UserExceptions.ModifyFilesystem as MFS: + pass + # @TODO: Get Users current status and purge him from the disk if neccessary + # @TODO: When the User had 0 and got 1 he should be created as well + print(f"Success! {args.user}") + if args.username: + print(f"{args.username}") + exit(0) + except KeyboardInterrupt as e: + pass From 6ea679cb602ad9095231800947a5eeec7a16bbbb Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Tue, 22 Oct 2019 13:50:14 +0200 Subject: [PATCH 57/79] System: userdel returns 6 when no maildir is deleted but homedir is So we agree on that one! --- private/lib/System.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/private/lib/System.py b/private/lib/System.py index 2cf660f..17de845 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -181,7 +181,7 @@ class System: return True else: ret = subprocess.call(cc) - if ret != 0 or ret != 6: + if ret != 0 and ret != 6: raise lib.UserExceptions.UnknownReturnCode( f"Could not delete user with command {cc}. Return code: {ret}") return True From 24eee6e84e0f5105773bed802097f4c0684c95ab Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Tue, 22 Oct 2019 13:51:01 +0200 Subject: [PATCH 58/79] Shebang/env for script execution --- private/Backup.py | 0 private/Import.py | 2 ++ private/ListUsers.py | 0 3 files changed, 2 insertions(+) mode change 100644 => 100755 private/Backup.py mode change 100644 => 100755 private/Import.py mode change 100644 => 100755 private/ListUsers.py diff --git a/private/Backup.py b/private/Backup.py old mode 100644 new mode 100755 diff --git a/private/Import.py b/private/Import.py old mode 100644 new mode 100755 index 20a7a7c..5e266ba --- a/private/Import.py +++ b/private/Import.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import csv import os import configparser diff --git a/private/ListUsers.py b/private/ListUsers.py old mode 100644 new mode 100755 From c46adc284953d3663b438ab0d86c7febf65fcea5 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Tue, 22 Oct 2019 13:52:41 +0200 Subject: [PATCH 59/79] editUsers: Description, remove username change and so on --- private/editUsers.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) mode change 100644 => 100755 private/editUsers.py diff --git a/private/editUsers.py b/private/editUsers.py old mode 100644 new mode 100755 index feb41b6..36bf62e --- a/private/editUsers.py +++ b/private/editUsers.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python3 + import configparser import lib.uis.config_ui # only follow -c flag import lib.validator @@ -13,10 +15,8 @@ ArgParser.add_argument('--user', type=str, Mutually = ArgParser.add_mutually_exclusive_group() Mutually.add_argument('-r', '--remove', default=False, action="store_true", - help='Remove an approved/unapproved User from the system. Effectively purges him.', + help='Remove an approved/unapproved User from the system(and DB). Effectively purges him.', required=False) -Mutually.add_argument('-a', '--approve', default=False, action="store_true", - help="Approve the given user", required=False) Mutually.add_argument("--verify", default=True, action="store_false", help="Turns off value checks", required=False) @@ -40,7 +40,7 @@ if __name__ == "__main__": try: db = config['DEFAULT']['applications_db'] if not args.sshpubkey and not args.name and not args.username and not args.email and args.status is None \ - and not args.approve and not args.remove: + and not args.remove: print(f"Well, SOMETHING must be done with {args.user} ;-)") exit(1) if not lib.validator.checkUserInDB(args.user, db): @@ -51,6 +51,8 @@ if __name__ == "__main__": if not DB: print("Couldn't establish connection to database") exit(1) + + CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0] if args.sshpubkey: if not lib.validator.checkSSHKey(args.sshpubkey): print(f"Pubkey {args.sshpubkey} isn't valid.") @@ -89,21 +91,20 @@ if __name__ == "__main__": if args.status != 0 and args.status != 1: print("Only 0 and 1 are valid status, where 1 is activated and 0 is unapproved.") exit(0) - CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0] # just takes first result out of the dict if args.status == int(CurrentUser["status"]): print(f"Old and new Status matches, didn't change") if args.status == 0 and int(CurrentUser["status"]) == 1: - try: - Sysctl.removeUser(args.user) - except lib.UserExceptions.UnknownReturnCode as e: - print(f"Couldn't remove {args.user} from the system, unknown return code: {e}") - exit(1) try: DB.safequery("UPDATE `applications` SET `status` =? WHERE `id`=?", tuple([args.status, CurrentUser["id"]])) except sqlite3.Error as e: - print(f"Did purge from disk but couldnt update database for {args.user}") + print(f"Couldn't update database entry for {args.user}, didn't touch the system") + exit(1) + try: + Sysctl.removeUser(args.user) + except lib.UserExceptions.UnknownReturnCode as e: + print(f"Couldn't remove {args.user} from the system, unknown return code: {e}. DB is modified.") exit(1) if args.status == 1 and int(CurrentUser["status"]) == 0: try: @@ -128,11 +129,7 @@ if __name__ == "__main__": exit(1) except lib.UserExceptions.ModifyFilesystem as MFS: pass - # @TODO: Get Users current status and purge him from the disk if neccessary - # @TODO: When the User had 0 and got 1 he should be created as well - print(f"Success! {args.user}") - if args.username: - print(f"{args.username}") + print(f"Success! {args.user}") exit(0) except KeyboardInterrupt as e: pass From 452204a5e98ad387ef7e3477e1511a25ff2cf39a Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Tue, 22 Oct 2019 15:26:52 +0200 Subject: [PATCH 60/79] ListUsers: --list sorts all approved users ascending --- private/ListUsers.py | 50 ++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 48fcdc8..2b96b7d 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -5,6 +5,8 @@ import configparser import lib.uis.default as default_cmd # Follows -u, -a, -f flags default_cmd.argparser.description += " - Lists Users from the Tilde database." +default_cmd.argparser.add_argument('--list', default=False, action="store_true", + help='Output a newline seperated list of users', required=False) args = default_cmd.argparser.parse_args() config = configparser.ConfigParser() config.read(args.config) @@ -17,13 +19,21 @@ class ListUsers: def __init__(self, db: str, uap: bool = False, app: bool = True): self.db = SQLitedb(db) if uap: # only unapproved users - query = "SELECT * FROM `applications` WHERE status = '0'" + query = "SELECT * FROM `applications` WHERE `status` = '0'" elif app: # Approved users - query = "SELECT * FROM `applications` WHERE status = '1'" + query = "SELECT * FROM `applications` WHERE `status` = '1'" else: # All users query = "SELECT * FROM `applications`" self.usersFetch = self.db.query(query) + def outputaslist(self) -> str: + list_str: str = "" + query = "SELECT `username` FROM `applications` WHERE `status` = '1' ORDER BY timestamp ASC" + self.usersFetch = self.db.query(query) + for users in self.usersFetch: + list_str += users["username"]+"\n" + return list_str + def prettyPrint(self) -> None: pass # see below why not implemented yet, texttable... @@ -36,14 +46,9 @@ class ListUsers: return self.usersFetch -if __name__ == "__main__": - try: - ret = "" - L = ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) - fetch = L.getFetch() - # @TODO MAYBE best solution: https://pypi.org/project/texttable/ - # examle: - """ +# @TODO MAYBE best solution: https://pypi.org/project/texttable/ +# examle: +""" from texttable import Texttable t = Texttable() t.add_rows([['Name', 'Age'], ['Alice', 24], ['Bob', 19]]) @@ -61,15 +66,24 @@ print(t.draw()) for user in fetch: print("ID: {}; Username: \"{}\"; Mail: {}; Name: \"{}\"; Registered: {}; Status: {}".format( user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] - ))""" - ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % ( - " ", " ", " ", " ", " " - ) - ret += 102 * "-" + "\n" - for user in fetch: - ret += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % ( - user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] + )) +""" +if __name__ == "__main__": + try: + ret = "" + L = ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) + if args.list: + ret = L.outputaslist() + else: + fetch = L.getFetch() + ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % ( + " ", " ", " ", " ", " " ) + ret += 102 * "-" + "\n" + for user in fetch: + ret += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % ( + user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] + ) if args.file != "stdout": with open(args.file, 'w') as f: print(ret, file=f) From 84cc83db3c4bd1e7afcc0f50c1518a77a7eb880e Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Tue, 22 Oct 2019 15:47:17 +0200 Subject: [PATCH 61/79] ListUsers: UAP and APP renamed, function updates /home/users.txt On every register() call the System rewrites the /home/users.txt to reflect currently active users. Will fall apart when something unexpected happened, but that's @helix responsibility. --- private/Backup.py | 2 +- private/Import.py | 2 +- private/ListUsers.py | 21 ++++++++++----------- private/editUsers.py | 2 +- private/lib/System.py | 13 ++++++++++++- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index bd324e2..97e10f6 100755 --- a/private/Backup.py +++ b/private/Backup.py @@ -53,7 +53,7 @@ class Backup: if __name__ == "__main__": try: - L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) + L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) fetch = L.getFetch() B = Backup(args.file) B.BackupToFile(fetch) diff --git a/private/Import.py b/private/Import.py index 5e266ba..28f15c7 100755 --- a/private/Import.py +++ b/private/Import.py @@ -37,7 +37,7 @@ def ImportFromFile(fname: str, db: str, userids: tuple = tuple([])): exit(0) import lib.sqlitedb import lib.System - sysctl = lib.System.System() + sysctl = lib.System.System(db) reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: if row["status"] == "1": diff --git a/private/ListUsers.py b/private/ListUsers.py index 2b96b7d..e7c5e7b 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -4,23 +4,16 @@ from lib.sqlitedb import SQLitedb import configparser import lib.uis.default as default_cmd # Follows -u, -a, -f flags -default_cmd.argparser.description += " - Lists Users from the Tilde database." -default_cmd.argparser.add_argument('--list', default=False, action="store_true", - help='Output a newline seperated list of users', required=False) -args = default_cmd.argparser.parse_args() -config = configparser.ConfigParser() -config.read(args.config) - class ListUsers: db = None usersFetch = None - def __init__(self, db: str, uap: bool = False, app: bool = True): + def __init__(self, db: str, unapproved: bool = False, approved: bool = True): self.db = SQLitedb(db) - if uap: # only unapproved users + if unapproved: # only unapproved users query = "SELECT * FROM `applications` WHERE `status` = '0'" - elif app: # Approved users + elif approved: # Approved users query = "SELECT * FROM `applications` WHERE `status` = '1'" else: # All users query = "SELECT * FROM `applications`" @@ -69,9 +62,15 @@ print(t.draw()) )) """ if __name__ == "__main__": + default_cmd.argparser.description += " - Lists Users from the Tilde database." + default_cmd.argparser.add_argument('--list', default=False, action="store_true", + help='Output a newline seperated list of users', required=False) + args = default_cmd.argparser.parse_args() + config = configparser.ConfigParser() + config.read(args.config) try: ret = "" - L = ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) + L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) if args.list: ret = L.outputaslist() else: diff --git a/private/editUsers.py b/private/editUsers.py index 36bf62e..76e0d6f 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -47,7 +47,7 @@ if __name__ == "__main__": print(f"User {args.user} doesn't exist in the database.") exit(1) DB = lib.sqlitedb.SQLitedb(db) - Sysctl = lib.System.System() + Sysctl = lib.System.System(db) if not DB: print("Couldn't establish connection to database") exit(1) diff --git a/private/lib/System.py b/private/lib/System.py index 17de845..fa8dcd6 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -10,8 +10,9 @@ class System: dry = False create_command = [] home = "" + db = None - def __init__(self, dryrun: bool = False, home: str = "/home/"): + def __init__(self, db: str, dryrun: bool = False, home: str = "/home/"): """Creates an objects. Can set dry run. :param dryrun: Run all command in a dry-run? When enabled, doesn't make any changes to the system (defaults to @@ -21,6 +22,7 @@ class System: :type home: str """ + self.db = db self.dry = dryrun if not home.endswith("/"): home += "/" @@ -47,6 +49,7 @@ class System: rt = subprocess.call(cc) if rt != 0: raise lib.UserExceptions.UserExistsAlready(f"User {username} exists already") + self._createUserIndex() return True def unregister(self, username: str): @@ -184,8 +187,16 @@ class System: if ret != 0 and ret != 6: raise lib.UserExceptions.UnknownReturnCode( f"Could not delete user with command {cc}. Return code: {ret}") + self._createUserIndex() return True + def _createUserIndex(self, index_file: str = "/home/users.txt"): + import ListUsers + L = ListUsers.ListUsers(self.db, unapproved=False, approved=True) + user_list = L.outputaslist() + with open(index_file, "w") as f: + print(user_list, file=f) + def AIO(username, pubkey, group="tilde"): syst = System(dryrun=False) From 389b614de97909bce966a1b15cb412b539aec200 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Tue, 22 Oct 2019 15:52:00 +0200 Subject: [PATCH 62/79] Revert "ListUsers: UAP and APP renamed, function updates /home/users.txt" This reverts commit 84cc83db3c4bd1e7afcc0f50c1518a77a7eb880e. --- private/Backup.py | 2 +- private/Import.py | 2 +- private/ListUsers.py | 21 +++++++++++---------- private/editUsers.py | 2 +- private/lib/System.py | 13 +------------ 5 files changed, 15 insertions(+), 25 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index 97e10f6..bd324e2 100755 --- a/private/Backup.py +++ b/private/Backup.py @@ -53,7 +53,7 @@ class Backup: if __name__ == "__main__": try: - L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) + L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) fetch = L.getFetch() B = Backup(args.file) B.BackupToFile(fetch) diff --git a/private/Import.py b/private/Import.py index 28f15c7..5e266ba 100755 --- a/private/Import.py +++ b/private/Import.py @@ -37,7 +37,7 @@ def ImportFromFile(fname: str, db: str, userids: tuple = tuple([])): exit(0) import lib.sqlitedb import lib.System - sysctl = lib.System.System(db) + sysctl = lib.System.System() reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: if row["status"] == "1": diff --git a/private/ListUsers.py b/private/ListUsers.py index e7c5e7b..2b96b7d 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -4,16 +4,23 @@ from lib.sqlitedb import SQLitedb import configparser import lib.uis.default as default_cmd # Follows -u, -a, -f flags +default_cmd.argparser.description += " - Lists Users from the Tilde database." +default_cmd.argparser.add_argument('--list', default=False, action="store_true", + help='Output a newline seperated list of users', required=False) +args = default_cmd.argparser.parse_args() +config = configparser.ConfigParser() +config.read(args.config) + class ListUsers: db = None usersFetch = None - def __init__(self, db: str, unapproved: bool = False, approved: bool = True): + def __init__(self, db: str, uap: bool = False, app: bool = True): self.db = SQLitedb(db) - if unapproved: # only unapproved users + if uap: # only unapproved users query = "SELECT * FROM `applications` WHERE `status` = '0'" - elif approved: # Approved users + elif app: # Approved users query = "SELECT * FROM `applications` WHERE `status` = '1'" else: # All users query = "SELECT * FROM `applications`" @@ -62,15 +69,9 @@ print(t.draw()) )) """ if __name__ == "__main__": - default_cmd.argparser.description += " - Lists Users from the Tilde database." - default_cmd.argparser.add_argument('--list', default=False, action="store_true", - help='Output a newline seperated list of users', required=False) - args = default_cmd.argparser.parse_args() - config = configparser.ConfigParser() - config.read(args.config) try: ret = "" - L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) + L = ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) if args.list: ret = L.outputaslist() else: diff --git a/private/editUsers.py b/private/editUsers.py index 76e0d6f..36bf62e 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -47,7 +47,7 @@ if __name__ == "__main__": print(f"User {args.user} doesn't exist in the database.") exit(1) DB = lib.sqlitedb.SQLitedb(db) - Sysctl = lib.System.System(db) + Sysctl = lib.System.System() if not DB: print("Couldn't establish connection to database") exit(1) diff --git a/private/lib/System.py b/private/lib/System.py index fa8dcd6..17de845 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -10,9 +10,8 @@ class System: dry = False create_command = [] home = "" - db = None - def __init__(self, db: str, dryrun: bool = False, home: str = "/home/"): + def __init__(self, dryrun: bool = False, home: str = "/home/"): """Creates an objects. Can set dry run. :param dryrun: Run all command in a dry-run? When enabled, doesn't make any changes to the system (defaults to @@ -22,7 +21,6 @@ class System: :type home: str """ - self.db = db self.dry = dryrun if not home.endswith("/"): home += "/" @@ -49,7 +47,6 @@ class System: rt = subprocess.call(cc) if rt != 0: raise lib.UserExceptions.UserExistsAlready(f"User {username} exists already") - self._createUserIndex() return True def unregister(self, username: str): @@ -187,16 +184,8 @@ class System: if ret != 0 and ret != 6: raise lib.UserExceptions.UnknownReturnCode( f"Could not delete user with command {cc}. Return code: {ret}") - self._createUserIndex() return True - def _createUserIndex(self, index_file: str = "/home/users.txt"): - import ListUsers - L = ListUsers.ListUsers(self.db, unapproved=False, approved=True) - user_list = L.outputaslist() - with open(index_file, "w") as f: - print(user_list, file=f) - def AIO(username, pubkey, group="tilde"): syst = System(dryrun=False) From a17d0ed30f6a3b8ccb9d0635873f5c77de8a1ff0 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Tue, 22 Oct 2019 15:56:05 +0200 Subject: [PATCH 63/79] ListUsers: UAP and APP => Unapproved and Approved replaced --- private/Backup.py | 3 ++- private/ListUsers.py | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index bd324e2..21d131c 100755 --- a/private/Backup.py +++ b/private/Backup.py @@ -53,7 +53,8 @@ class Backup: if __name__ == "__main__": try: - L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) + L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], + unapproved=args.unapproved, approved=args.approved) fetch = L.getFetch() B = Backup(args.file) B.BackupToFile(fetch) diff --git a/private/ListUsers.py b/private/ListUsers.py index 2b96b7d..d59afca 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -4,29 +4,22 @@ from lib.sqlitedb import SQLitedb import configparser import lib.uis.default as default_cmd # Follows -u, -a, -f flags -default_cmd.argparser.description += " - Lists Users from the Tilde database." -default_cmd.argparser.add_argument('--list', default=False, action="store_true", - help='Output a newline seperated list of users', required=False) -args = default_cmd.argparser.parse_args() -config = configparser.ConfigParser() -config.read(args.config) - class ListUsers: db = None usersFetch = None - def __init__(self, db: str, uap: bool = False, app: bool = True): + def __init__(self, db: str, unapproved: bool = False, approved: bool = True): self.db = SQLitedb(db) - if uap: # only unapproved users + if unapproved: # only unapproved users query = "SELECT * FROM `applications` WHERE `status` = '0'" - elif app: # Approved users + elif approved: # Approved users query = "SELECT * FROM `applications` WHERE `status` = '1'" else: # All users query = "SELECT * FROM `applications`" self.usersFetch = self.db.query(query) - def outputaslist(self) -> str: + def output_as_list(self) -> str: list_str: str = "" query = "SELECT `username` FROM `applications` WHERE `status` = '1' ORDER BY timestamp ASC" self.usersFetch = self.db.query(query) @@ -69,11 +62,18 @@ print(t.draw()) )) """ if __name__ == "__main__": + default_cmd.argparser.description += " - Lists Users from the Tilde database." + default_cmd.argparser.add_argument('--list', default=False, action="store_true", + help='Output a newline seperated list of users', required=False) + args = default_cmd.argparser.parse_args() + config = configparser.ConfigParser() + config.read(args.config) + try: ret = "" - L = ListUsers(config['DEFAULT']['applications_db'], uap=args.unapproved, app=args.approved) + L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) if args.list: - ret = L.outputaslist() + ret = L.output_as_list() else: fetch = L.getFetch() ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % ( From 65c7bb6b3fdf920282c1c3f8005dca0853142890 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Tue, 22 Oct 2019 16:13:57 +0200 Subject: [PATCH 64/79] Move argparser into mains function body --- private/Backup.py | 10 ++++----- private/Import.py | 20 ++++++++--------- private/editUsers.py | 51 ++++++++++++++++++++++---------------------- 3 files changed, 39 insertions(+), 42 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index 21d131c..9b69299 100755 --- a/private/Backup.py +++ b/private/Backup.py @@ -7,12 +7,6 @@ import configparser import lib.uis.default as default_cmd # Follows -u, -a, -f flags -default_cmd.argparser.description += " - Backups Tilde Users to stdout or a file." -args = default_cmd.argparser.parse_args() -config = configparser.ConfigParser() -config.read(args.config) - - class Backup: filename: str quoting: int @@ -52,6 +46,10 @@ class Backup: if __name__ == "__main__": + default_cmd.argparser.description += " - Backups Tilde Users to stdout or a file." + args = default_cmd.argparser.parse_args() + config = configparser.ConfigParser() + config.read(args.config) try: L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) diff --git a/private/Import.py b/private/Import.py index 5e266ba..39e0c2e 100755 --- a/private/Import.py +++ b/private/Import.py @@ -6,16 +6,6 @@ import configparser import lib.UserExceptions import lib.uis.config_ui # dont go to default, just following -c flag -ArgParser = lib.uis.config_ui.argparser -ArgParser.description += "- Imports a CSV file consisting of user specific details to the database" -ArgParser.add_argument('-f', '--file', default="stdout", - type=str, help='Import from CSV file', required=True) -ArgParser.add_argument('--Import', default=False, action="store_true", - help="Import Users.", required=True) -args = ArgParser.parse_args() -config = configparser.ConfigParser() -config.read(args.config) - def ImportFromFile(fname: str, db: str, userids: tuple = tuple([])): if not os.path.isfile(fname): @@ -75,6 +65,16 @@ def ImportFromFile(fname: str, db: str, userids: tuple = tuple([])): if __name__ == "__main__": + + ArgParser = lib.uis.config_ui.argparser + ArgParser.description += "- Imports a CSV file consisting of user specific details to the database" + ArgParser.add_argument('-f', '--file', default="stdout", + type=str, help='Import from CSV file', required=True) + ArgParser.add_argument('--Import', default=False, action="store_true", + help="Import Users.", required=True) + args = ArgParser.parse_args() + config = configparser.ConfigParser() + config.read(args.config) try: if not args.Import: print("Error, need the import flag") diff --git a/private/editUsers.py b/private/editUsers.py index 36bf62e..9d7f6a6 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -8,35 +8,34 @@ import lib.System import lib.UserExceptions import sqlite3 -lib.uis.config_ui.argparser.description += " - Edit Tilde Users" -ArgParser = lib.uis.config_ui.argparser -ArgParser.add_argument('--user', type=str, - help='Tilde users name to edit', required=True) -Mutually = ArgParser.add_mutually_exclusive_group() -Mutually.add_argument('-r', '--remove', default=False, action="store_true", - help='Remove an approved/unapproved User from the system(and DB). Effectively purges him.', - required=False) -Mutually.add_argument("--verify", default=True, action="store_false", - help="Turns off value checks", - required=False) - -ArgParser.add_argument('--sshpubkey', type=str, default=None, - help="Stores the new given SSH-Key in given user", required=False) -ArgParser.add_argument('--name', type=str, default=None, - help="Sets the stored name of the given user") -ArgParser.add_argument('--username', type=str, default=None, - help="Rename given User") -ArgParser.add_argument('--email', type=str, default=None, - help="Set new email address for given user") -ArgParser.add_argument('--status', type=int, default=None, - help="Set status of given user") -args = ArgParser.parse_args() -config = configparser.ConfigParser() -config.read(args.config) +if __name__ == "__main__": + lib.uis.config_ui.argparser.description += " - Edit Tilde Users" + ArgParser = lib.uis.config_ui.argparser + ArgParser.add_argument('--user', type=str, + help='Tilde users name to edit', required=True) + Mutually = ArgParser.add_mutually_exclusive_group() + Mutually.add_argument('-r', '--remove', default=False, action="store_true", + help='Remove an approved/unapproved User from the system(and DB). Effectively purges him.', + required=False) + Mutually.add_argument("--verify", default=True, action="store_false", + help="Turns off value checks", + required=False) -if __name__ == "__main__": + ArgParser.add_argument('--sshpubkey', type=str, default=None, + help="Stores the new given SSH-Key in given user", required=False) + ArgParser.add_argument('--name', type=str, default=None, + help="Sets the stored name of the given user") + ArgParser.add_argument('--username', type=str, default=None, + help="Rename given User") + ArgParser.add_argument('--email', type=str, default=None, + help="Set new email address for given user") + ArgParser.add_argument('--status', type=int, default=None, + help="Set status of given user") + args = ArgParser.parse_args() + config = configparser.ConfigParser() + config.read(args.config) try: db = config['DEFAULT']['applications_db'] if not args.sshpubkey and not args.name and not args.username and not args.email and args.status is None \ From 0eeafa626e79c1bea73c40ea9df4db34ec55c176 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 14:27:48 +0200 Subject: [PATCH 65/79] Documentation work all over the place Renamed also some paramters --- private/Backup.py | 80 +++++++++++++++++++--- private/Import.py | 19 ++++-- private/ListUsers.py | 21 +++++- private/editUsers.py | 2 +- private/lib/System.py | 64 ++++++++++++++---- private/lib/validator.py | 140 ++++++++++++++++++++++++++++++++------- 6 files changed, 274 insertions(+), 52 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index 9b69299..ef5e13f 100755 --- a/private/Backup.py +++ b/private/Backup.py @@ -8,30 +8,91 @@ import lib.uis.default as default_cmd # Follows -u, -a, -f flags class Backup: + """Backups a Tilde database to an CSV file + + :Example: + >>> from Backup import Backup + >>> from ListUsers import ListUsers + >>> L = ListUsers.ListUsers("/path/to/sqlite").get_fetch() + >>> backup_db = Backup("stdout") + >>> backup_db.backup_to_file(L) + CSV-Separated list with headers in first row + + """ + filename: str quoting: int dialect: str field_names: tuple - def __init__(self, fname: str, quoting: int = csv.QUOTE_NONNUMERIC, dialect: str = "excel"): - self.setFilename(fname) + def __init__(self, output: str, quoting: int = csv.QUOTE_NONNUMERIC, dialect: str = "excel"): + """ Constructs the Backup object + + :param output: File name to backup to(set to stdout for stdout) + :type output: str + :param quoting: Set quoting for CSV Module + :type quoting: int + :param dialect: Set the CSV-Dialect. Defaults to excel, which is the classic CSV + :type dialect: str + """ + + self.setFilename(output) self.setQuoting(quoting) self.setDialect(dialect) self.setFieldnames(tuple(['id', 'username', 'email', 'name', 'pubkey', 'timestamp', 'status'])) - def setDialect(self, dialect: str): + def setDialect(self, dialect: str) -> None: + """ Set dialect for Object + + :param dialect: Dialect to set for Object + :type dialect: str + :return: None + :rtype: None + """ + self.dialect = dialect - def setQuoting(self, quoting: int): + def setQuoting(self, quoting: int) -> None: + """ Set quoting in the CSV(must be supported by the CSV Module!) + + :param quoting: Quoting Integer given by csv.QUOTE_* constants + :type quoting: int + :return: None + :rtype: None + """ + self.quoting = quoting - def setFilename(self, filename: str): + def setFilename(self, filename: str) -> None: + """ Sets Filename to output to + + :param filename: Filename to output to(set stdout for stdout) + :type filename: str + :return: None + :rtype: None + """ + self.filename = filename - def setFieldnames(self, f_names: tuple): + def setFieldnames(self, f_names: tuple) -> None: + """ Set fieldname to process + + :param f_names: Fieldnames-Tuple + :type f_names: tuple + :return: None + :rtype: None + """ + self.field_names = f_names - def BackupToFile(self, fetched: list): + def backup_to_file(self, fetched: list) -> bool: + """Backup Userlist to File(or stdout) + + :param fetched: List of values to write out CSV-formatted + :return: True, if success, None when not. + :rtype: bool + """ + returner = io.StringIO() write_csv = csv.DictWriter(returner, fieldnames=self.field_names, quoting=self.quoting, dialect=self.dialect) write_csv.writeheader() @@ -39,6 +100,7 @@ class Backup: if self.filename == "stdout": print(returner.getvalue()) + return True else: with open(self.filename, "w") as f: print(returner.getvalue(), file=f) @@ -53,9 +115,9 @@ if __name__ == "__main__": try: L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) - fetch = L.getFetch() + fetch = L.get_fetch() B = Backup(args.file) - B.BackupToFile(fetch) + B.backup_to_file(fetch) exit(0) except KeyboardInterrupt as e: pass diff --git a/private/Import.py b/private/Import.py index 39e0c2e..3d4d88e 100755 --- a/private/Import.py +++ b/private/Import.py @@ -7,13 +7,24 @@ import lib.UserExceptions import lib.uis.config_ui # dont go to default, just following -c flag -def ImportFromFile(fname: str, db: str, userids: tuple = tuple([])): +def import_from_file(fname: str, db: str, userids: tuple = tuple([])) -> bool: + """ Imports Users from a given CSV-file to the system and DB + + :param fname: + :type fname: str + :param db: Path to the sqlite db + :type db: str + :param userids: FIXME: Tuple which userids should we write + :type userids: tuple + :return: True on success, False when not + :rtype: bool + """ if not os.path.isfile(fname): print(f"File {fname} don't exist") - return None + return False if not os.path.isfile(db): print(f"The database file {db} don't exist") - return None + return False if userids: pass # empty tuple means everything # noinspection PyBroadException @@ -83,7 +94,7 @@ if __name__ == "__main__": if not args.file: print("You MUST set a CSV-file with the -f/--file flag that already exist") exit(1) - ImportFromFile(args.file, config['DEFAULT']['applications_db']) + import_from_file(args.file, config['DEFAULT']['applications_db']) exit(0) except KeyboardInterrupt as e: pass diff --git a/private/ListUsers.py b/private/ListUsers.py index d59afca..e1d3c7a 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -10,6 +10,16 @@ class ListUsers: usersFetch = None def __init__(self, db: str, unapproved: bool = False, approved: bool = True): + """Constructs ListUsers + + :param db: Database to access + :type db: str + :param unapproved: only List unapproved users + :type unapproved: bool + :param approved: only list approved users + :type approved: bool + """ + self.db = SQLitedb(db) if unapproved: # only unapproved users query = "SELECT * FROM `applications` WHERE `status` = '0'" @@ -20,6 +30,12 @@ class ListUsers: self.usersFetch = self.db.query(query) def output_as_list(self) -> str: + """Generates a string with one (approved) user per line and one newline at the end + + :rtype: str + :return: String consisting with one(activated) user per line + """ + list_str: str = "" query = "SELECT `username` FROM `applications` WHERE `status` = '1' ORDER BY timestamp ASC" self.usersFetch = self.db.query(query) @@ -30,12 +46,13 @@ class ListUsers: def prettyPrint(self) -> None: pass # see below why not implemented yet, texttable... - def getFetch(self) -> list: + def get_fetch(self) -> list: """ Returns a complete fetch done by the sqlitedb-class :return: Complete fetchall() in a dict-factory :rtype: list """ + return self.usersFetch @@ -75,7 +92,7 @@ if __name__ == "__main__": if args.list: ret = L.output_as_list() else: - fetch = L.getFetch() + fetch = L.get_fetch() ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % ( " ", " ", " ", " ", " " ) diff --git a/private/editUsers.py b/private/editUsers.py index 9d7f6a6..eaa6ddc 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -101,7 +101,7 @@ if __name__ == "__main__": print(f"Couldn't update database entry for {args.user}, didn't touch the system") exit(1) try: - Sysctl.removeUser(args.user) + Sysctl.remove_user(args.user) except lib.UserExceptions.UnknownReturnCode as e: print(f"Couldn't remove {args.user} from the system, unknown return code: {e}. DB is modified.") exit(1) diff --git a/private/lib/System.py b/private/lib/System.py index 17de845..311e693 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -5,11 +5,19 @@ import lib.UserExceptions class System: - """Class to interact with the system specifically to support our needs 0w0""" - - dry = False + """Class to interact with the system specifically to support our needs 0w0 + :Example: + >>> from lib.System import System as System + >>> Sys_ctl = System(dryrun=True) + >>> Sys_ctl.register("Bob") + >>> Sys_ctl.lock_user_pw("Bob") + >>> Sys_ctl.add_to_usergroup("Bob") + >>> Sys_ctl.make_ssh_usable("Bob", "sshkey") + """ + + dry: bool = False create_command = [] - home = "" + home: str = "" def __init__(self, dryrun: bool = False, home: str = "/home/"): """Creates an objects. Can set dry run. @@ -19,6 +27,8 @@ class System: :type dryrun: bool :param home: Standard directory to search for the home directories of your users(default is /home/) :type home: str + :raises: + ValueError: if homedir can not be found """ self.dry = dryrun @@ -37,6 +47,8 @@ class System: :type cc: tuple :return: True, if worked, raises lib.UserExceptions.UserExistsAlready when not :rtype: bool + :raises: + lib.UserExceptions.UserExistsAlready: when username specified already exists on the system """ create_command = cc cc = create_command + tuple([username]) @@ -49,8 +61,15 @@ class System: raise lib.UserExceptions.UserExistsAlready(f"User {username} exists already") return True - def unregister(self, username: str): - self.removeUser(username) + def unregister(self, username: str) -> bool: + """ Just an alias function for removeUser + + :param username: username to remove + :type username: str + :return: True, when success, False(or exception) when not + :rtype: bool + """ + return self.remove_user(username) def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/") -> bool: """ Make SSH usable for our newly registered user @@ -64,6 +83,10 @@ class System: :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode, lib.UserExceptions.HomeDirExistsAlready or lib.UserExceptions.ModifyFilesystem when not :rtype: bool + :raises: + lib.UserExceptions.SSHDirUncreatable: if the ssh-dir couldnt be created nor exist + lib.UserExceptions.ModifyFilesystem: When chmod to .ssh and authorized_keys failed + lib.UserExceptions.General: if PWD cant find the specified user """ if self.dry: @@ -95,7 +118,16 @@ class System: raise lib.UserExceptions.General(f"PWD can't find {username}: {e}") return True - def write_ssh(self, key: str, ssh_dir: str): + @staticmethod + def write_ssh(key: str, ssh_dir: str) -> None: + """ Write SSH key to a specified directory(appends authorized_keys itself!) + + :param key: Key to write + :type key: str + :param ssh_dir: SSH Directory to write to + :type ssh_dir: str + :return: None + """ with open(ssh_dir + "authorized_keys", "w") as f: print(key, file=f) f.close() @@ -110,6 +142,8 @@ class System: :type cc: tuple :rtype: bool :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode when not + :raises: + lib.UserExceptions.UnknownReturnCode: When cc returns something else then 0 """ lock_command = cc @@ -134,6 +168,8 @@ class System: :type cc: tuple :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode when not :rtype bool + :raises: + lib.UserExceptions.UnknownReturnCode: if cc returned something else then 0 """ add_command = cc @@ -163,7 +199,7 @@ class System: pp += i + " " print(pp) - def removeUser(self, username: str, cc: tuple = tuple(["userdel", "-r"])) -> bool: + def remove_user(self, username: str, cc: tuple = tuple(["userdel", "-r"])) -> bool: """Removes the specified user from the system :param username: The username you want to delete from the system. @@ -172,6 +208,8 @@ class System: :type cc: tuple :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode when not :rtype: bool + :raises: + lib.UserExceptions.UnknownReturnCode: When cc returns something else then 0 or 6 """ remove_command = cc @@ -188,11 +226,11 @@ class System: def AIO(username, pubkey, group="tilde"): - syst = System(dryrun=False) - syst.register(username) - syst.lock_user_pw(username) - syst.add_to_usergroup(username, group) - syst.make_ssh_usable(username, pubkey) + sys_ctl = System(dryrun=False) + sys_ctl.register(username) + sys_ctl.lock_user_pw(username) + sys_ctl.add_to_usergroup(username, group) + sys_ctl.make_ssh_usable(username, pubkey) if __name__ == "__main__": diff --git a/private/lib/validator.py b/private/lib/validator.py index 5678416..ede8c13 100644 --- a/private/lib/validator.py +++ b/private/lib/validator.py @@ -1,9 +1,19 @@ import re import pwd import lib.sqlitedb +import csv def checkUsernameCharacters(username: str) -> bool: + """ Checks the Username for invalid characters. Allow only alphanumerical characters, a lower alpha one first, + Followed by any sequence of digits and characters + + :param username: String to check for validity + :type username: str + :return: True when valid, False when not + :rtype: bool + """ + if " " not in username and "_" not in username and username.isascii() and username[:1].islower() and \ not username[0].isnumeric(): if not re.search(r"\W+", username): @@ -12,15 +22,35 @@ def checkUsernameCharacters(username: str) -> bool: return False -def checkUsernameLength(username: str) -> bool: - if len(username) > 16: +def checkUsernameLength(username: str, upper_limit: int = 16, lower_limit: int = 3) -> bool: + """ Checks username for an upper and lower bounds limit character count + + :param username: Username to check + :type username: str + :param upper_limit: Upper limit bounds to check for(default is 16) + :type upper_limit: int + :param lower_limit: Lower limit bounds to check for(default is 3) + :return: True, when all bounds are in, False when one or both aren't. + :rtype: bool + """ + + if len(username) > upper_limit: return False - if len(username) < 3: + if len(username) < lower_limit: return False return True def checkUserExists(username: str) -> bool: + """ Checks if the User exists on the **SYSTEM** by calling PWD on it. + **Note**: You might want to use this in conjunction with checkUserInDB + + :param username: + :type username: str + :return: True when exists, False when not + :rtype: bool + """ + try: pwd.getpwnam(username) except KeyError: @@ -30,6 +60,16 @@ def checkUserExists(username: str) -> bool: def checkUserInDB(username: str, db: str) -> bool: + """ Checks users existence in the **DATABASE**. + :Note: You might want to use this in conjunction with `checkUserExists` + + :param username: Username to check existence in database + :type username: str + :param db: Path to database to check in + :type db: str + :return: True, when User exists, False when not + """ + try: ldb = lib.sqlitedb.SQLitedb(db) fetched = ldb.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) @@ -41,6 +81,16 @@ def checkUserInDB(username: str, db: str) -> bool: def checkSSHKey(key: str) -> bool: + """ Checks SSH Key for meta-data that we accept. + :Note: We currently only allow ssh keys without options but with a mail address at the end in b64 encoded. + The currently supported algorithms are: ecdfsa-sha2-nistp256, 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', + 'ssh-rsa', 'ssh-dss' and 'ssh-ed25519' + + :param key: Key to check + :return: True, when Key is valid, False when not + :rtype: bool + """ + # taken from https://github.com/hashbang/provisor/blob/master/provisor/utils.py, all belongs to them! ;) import base64 if len(key) > 8192 or len(key) < 80: @@ -60,66 +110,110 @@ def checkSSHKey(key: str) -> bool: def checkEmail(mail: str) -> bool: + """ Checks Mail against a relatively simple REgex Pattern. + + :param mail: Mail to check + :type mail: str + :return: False, when the Mail is invalid, True when valid. + :rtype: bool + """ + if not re.match("(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", mail): return False else: return True -def checkDatetimeFormat(form: str) -> bool : +def checkDatetimeFormat(datetime_str: str) -> bool: + """ Checks a Strings format on date time. + + :param datetime_str: String to check + :type datetime_str: str + :return: True when valid, False when not. + :rtype: bool + """ + import datetime try: - datetime.datetime.strptime(form, "%Y-%m-%d %H:%M:%S") + datetime.datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S") except ValueError: return False return True def checkName(name: str) -> bool: + """ Checks a users (real) Name against a real simple REgex Pattern. + + :param name: Name/String to check + :type name: str + :return: True when valid, False when not. + :rtype: bool + """ + if not re.match("\w+\s*\w", name): return False else: return True -def checkImportFile(fname: str, db: str) -> bool: - error_list = str() +def checkImportFile(path: str, db: str): + """ Checks an CSV file against most of the validators and prints an Error message with the line number corresponding + to said failure.. Those includes: checkName, checkUsernameCharacters, + ckeckUsernameLength, duplicate usernames(in the CSV), checkSSHKey, checkEmail, checkUserExists, checkUserInDB, + checkDatetimeformat and if the status is 1 or 0. + + :param path: Path to file to check + :type path: str + :param db: Path to database file(SQLite) + :type db: str + :return: Str when Failure, True when success(All tests passed) + :rtype: Str or None + """ + + errstr = "" valid = True ln = 1 # line number - with open(fname, 'r', newline='') as f: - import csv - reador = csv.DictReader(f) - for row in reador: + valid_names_list = [] + with open(path, 'r', newline='') as f: + reader = csv.DictReader(f) + for row in reader: # if any of this fails move on to the next user, just print a relatively helpful message lel if not lib.validator.checkName(row["name"]): - error_list += f"Line{ln}: {row['name']} seems not legit. Character followed by character should be " \ - f"correct.\n" + errstr += f"Line {ln}: Name: '{row['name']}' seems not legit. Character followed by character should" \ + f" be correct.\n" valid = False if not lib.validator.checkUsernameCharacters(row["username"]): - error_list += (f"Line {ln}: Username contains unsupported characters or starts with a number: '" - f"{row['username']}'.\n") + errstr += (f"Line {ln}: Username contains unsupported characters or starts with a number: '" + f"{row['username']}'.\n") valid = False if not lib.validator.checkUsernameLength(row["username"]): - error_list += f"Line {ln}: Username '{row['username']}' is either too long(>16) or short(<3)\n" + errstr += f"Line {ln}: Username '{row['username']}' is either too long(>16) or short(<3)\n" + valid = False + # dup checking + if row["username"] in valid_names_list: + errstr += f"Line {ln}: Duplicate Username {row['username']}!\n" valid = False + else: + valid_names_list.append(row["username"]) + # dup end if not lib.validator.checkSSHKey(row["pubkey"]): - error_list += f"Line {ln}: Following SSH-Key of user '{row['username']}' isn't valid: '{row['pubkey']}'."\ - f"\n" + errstr += f"Line {ln}: Following SSH-Key of user '{row['username']}' isn't valid: " \ + f"'{row['pubkey']}'.\n" valid = False if not lib.validator.checkEmail(row["email"]): - error_list += f"Line {ln}: E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" + errstr += f"Line {ln}: E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" valid = False if not lib.validator.checkUserExists(row["username"]) or checkUserInDB(row["username"], db): - error_list += f"Line {ln}: User '{row['username']}' already exists.\n" + errstr += f"Line {ln}: User '{row['username']}' already exists.\n" valid = False if not lib.validator.checkDatetimeFormat(row["timestamp"]): - error_list += f"Line {ln}: Timestamp '{row['timestamp']}' from user '{row['username']}' is invalid.\n" + errstr += f"Line {ln}: Timestamp '{row['timestamp']}' from user '{row['username']}' is invalid.\n" valid = False if int(row["status"]) > 1 or int(row["status"]) < 0: - error_list += f"Line {ln}: Status '{row['status']}' MUST be either 0 or 1.\n" + errstr += f"Line {ln}: Status '{row['status']}' MUST be either 0 or 1.\n" valid = False ln += 1 if valid: return True else: - return error_list + return errstr From 7d5ef3b493ba77438735fc23e772be9e7a5e8caf Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 15:01:44 +0200 Subject: [PATCH 66/79] Wording fix in docstring --- private/ListUsers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index e1d3c7a..3682c07 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -47,9 +47,9 @@ class ListUsers: pass # see below why not implemented yet, texttable... def get_fetch(self) -> list: - """ Returns a complete fetch done by the sqlitedb-class + """ Returns a complete fetch done by the lib.sqlitedb-class - :return: Complete fetchall() in a dict-factory + :return: Complete fetchall() in a dict :rtype: list """ From b4d4fd9ad42a73daf8389ccfee1431a827612f37 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 15:02:21 +0200 Subject: [PATCH 67/79] System: Improved return calls at remove_user --- private/lib/System.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/private/lib/System.py b/private/lib/System.py index 311e693..25cc861 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -50,6 +50,7 @@ class System: :raises: lib.UserExceptions.UserExistsAlready: when username specified already exists on the system """ + create_command = cc cc = create_command + tuple([username]) if self.dry: @@ -128,6 +129,7 @@ class System: :type ssh_dir: str :return: None """ + with open(ssh_dir + "authorized_keys", "w") as f: print(key, file=f) f.close() @@ -218,10 +220,13 @@ class System: self.printTuple(cc) return True else: - ret = subprocess.call(cc) - if ret != 0 and ret != 6: + ret = subprocess.Popen(cc, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + ret.wait() # wait for cc + stdio, err_io = ret.communicate() # get stdout as well as stderr + if ret.returncode != 0 and ret.returncode != 6: # userdel returns 6 when no mail dir was found but success raise lib.UserExceptions.UnknownReturnCode( - f"Could not delete user with command {cc}. Return code: {ret}") + f"Could not delete user with command {cc}. Return code: {ret.returncode}," + f" stdout/stderr: {stdio+err_io}") return True From 7ebabbb5f6144ce73d78fceb1ed342074faea279 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 15:02:48 +0200 Subject: [PATCH 68/79] EditUsers: Implement --remove flag --- private/editUsers.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/private/editUsers.py b/private/editUsers.py index eaa6ddc..2483ca8 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -52,6 +52,21 @@ if __name__ == "__main__": exit(1) CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0] + if args.remove: + print(f"Removing {args.user} from the system and the database...") + try: + DB.removeApplicantFromDBperUsername(args.user) + print(f"Purged from the DB") + if CurrentUser["status"] == 1: + Sysctl.remove_user(args.user) + print(f"Purged from the system") + else: + print(f"{args.user} wasn't approved before, therefore not deleting from system itself.") + except lib.UserExceptions.General as e: + print(f"{e}") + exit(1) + print("Success.") + exit(0) if args.sshpubkey: if not lib.validator.checkSSHKey(args.sshpubkey): print(f"Pubkey {args.sshpubkey} isn't valid.") From f204a74f9854239709d311b234934f0fff9ed5a1 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 17:37:45 +0200 Subject: [PATCH 69/79] Refactor some variable names --- private/Import.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/private/Import.py b/private/Import.py index 3d4d88e..1010fb1 100755 --- a/private/Import.py +++ b/private/Import.py @@ -7,46 +7,47 @@ import lib.UserExceptions import lib.uis.config_ui # dont go to default, just following -c flag -def import_from_file(fname: str, db: str, userids: tuple = tuple([])) -> bool: +def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bool: """ Imports Users from a given CSV-file to the system and DB - :param fname: - :type fname: str + :param file_path: + :type file_path: str :param db: Path to the sqlite db :type db: str - :param userids: FIXME: Tuple which userids should we write - :type userids: tuple + :param user_ids: FIXME: Tuple which user_ids should we write + :type user_ids: tuple :return: True on success, False when not :rtype: bool """ - if not os.path.isfile(fname): - print(f"File {fname} don't exist") + if not os.path.isfile(file_path): + print(f"File {file_path} don't exist") return False if not os.path.isfile(db): print(f"The database file {db} don't exist") return False - if userids: + if user_ids: pass # empty tuple means everything # noinspection PyBroadException try: - with open(fname, 'r', newline='') as f: + with open(file_path, 'r', newline='') as f: import lib.validator sql = lib.sqlitedb.SQLitedb(db) - err = lib.validator.checkImportFile(fname, db) + err = lib.validator.checkImportFile(file_path, db) if err is not True: print(err) exit(0) import lib.sqlitedb import lib.System - sysctl = lib.System.System() + sys_ctl = lib.System.System("root") reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option for row in reader: if row["status"] == "1": try: - sysctl.register(row["username"]) - sysctl.lock_user_pw(row["username"]) - sysctl.add_to_usergroup(row["username"]) - sysctl.make_ssh_usable(row["username"], row["pubkey"]) + sys_ctl.setUser(row["username"]) + sys_ctl.register() + sys_ctl.lock_user_pw() + sys_ctl.add_to_usergroup() + sys_ctl.make_ssh_usable(row["pubkey"]) print(row['username'], "====> Registered.") except lib.UserExceptions.UserExistsAlready as UEA: pass # @TODO User was determined to exists already, shouldn't happen but is possible @@ -69,7 +70,7 @@ def import_from_file(fname: str, db: str, userids: tuple = tuple([])) -> bool: row["email"], row["pubkey"], row["status"]])) except OSError as E: pass - print(f"UUFFF, something went WRONG with the file {fname}: {E}") + print(f"UUFFF, something went WRONG with the file {file_path}: {E}") except Exception as didntCatch: print(f"Exception! UNCATCHED! {type(didntCatch)}: {didntCatch}") return True From 05f27078f67da6982f12c9c5791ea0f6258786c1 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 17:37:59 +0200 Subject: [PATCH 70/79] System: Refactors username given in constructor deleted all references and added one class which accepts to set a username --- private/lib/System.py | 88 +++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 46 deletions(-) diff --git a/private/lib/System.py b/private/lib/System.py index 25cc861..aba22f2 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -8,20 +8,26 @@ class System: """Class to interact with the system specifically to support our needs 0w0 :Example: >>> from lib.System import System as System - >>> Sys_ctl = System(dryrun=True) - >>> Sys_ctl.register("Bob") - >>> Sys_ctl.lock_user_pw("Bob") - >>> Sys_ctl.add_to_usergroup("Bob") - >>> Sys_ctl.make_ssh_usable("Bob", "sshkey") + >>> Sys_ctl = System("Test", dryrun=True) + >>> Sys_ctl.register() + >>> Sys_ctl.lock_user_pw() + >>> Sys_ctl.add_to_usergroup() + >>> Sys_ctl.make_ssh_usable("sshkey") """ dry: bool = False create_command = [] home: str = "" + user: str - def __init__(self, dryrun: bool = False, home: str = "/home/"): + def setUser(self, username: str): + self.user = username + + def __init__(self, username: str, dryrun: bool = False, home: str = "/home/"): """Creates an objects. Can set dry run. + :param username: Username to manipulate + :type username: str :param dryrun: Run all command in a dry-run? When enabled, doesn't make any changes to the system (defaults to false) :type dryrun: bool @@ -37,12 +43,11 @@ class System: if not os.path.isdir(home): raise ValueError("home should be an existent directory...") self.home = home + self.user = username - def register(self, username: str, cc: tuple = tuple(["useradd", "-m"])) -> bool: + def register(self, cc: tuple = tuple(["useradd", "-m"])) -> bool: """Creates an local account for the given username - :param username: Username to create - :type username: str :param cc: Tuple with commands separated to execute on the machine. (defaults to useradd -m) :type cc: tuple :return: True, if worked, raises lib.UserExceptions.UserExistsAlready when not @@ -52,31 +57,28 @@ class System: """ create_command = cc - cc = create_command + tuple([username]) + cc = create_command + tuple([self.user]) if self.dry: self.printTuple(cc) return True elif not self.dry: rt = subprocess.call(cc) if rt != 0: - raise lib.UserExceptions.UserExistsAlready(f"User {username} exists already") + raise lib.UserExceptions.UserExistsAlready(f"User {self.user} exists already") return True - def unregister(self, username: str) -> bool: + def unregister(self) -> bool: """ Just an alias function for removeUser - :param username: username to remove - :type username: str :return: True, when success, False(or exception) when not :rtype: bool """ - return self.remove_user(username) - def make_ssh_usable(self, username: str, pubkey: str, sshdir: str = ".ssh/") -> bool: + return self.remove_user() + + def make_ssh_usable(self, pubkey: str, sshdir: str = ".ssh/") -> bool: """ Make SSH usable for our newly registered user - :param username: Username you want to affect with it, casually used directly after register() - :type username: str :param pubkey: Public SSH Key for the User you want accessible by SSH :type pubkey: str :param sshdir: Directory to write the authorized_keys File to. PWD is $HOME of said user. (defaults to ".ssh/") @@ -95,7 +97,7 @@ class System: return True if not sshdir.endswith("/"): sshdir += "/" - ssh_dir = self.home + username + "/" + sshdir + ssh_dir = self.home + self.user + "/" + sshdir try: os.mkdir(ssh_dir) except FileExistsError: @@ -109,14 +111,14 @@ class System: raise lib.UserExceptions.ModifyFilesystem( f"Could not write and/or chmod 0700 {ssh_dir} or {ssh_dir}/authorized_keys, Exception: {e}") try: - pwdnam = pwd.getpwnam(username) + pwdnam = pwd.getpwnam(self.user) os.chown(ssh_dir, pwdnam[2], pwdnam[3]) # 2=>uid, 3=>gid - os.chown(ssh_dir + "authorized_keys", pwd.getpwnam(username)[2], pwd.getpwnam(username)[3]) + os.chown(ssh_dir + "authorized_keys", pwd.getpwnam(self.user)[2], pwd.getpwnam(self.user)[3]) except OSError as e: # by os.chown raise lib.UserExceptions.ModifyFilesystem( - f"Could not chown {ssh_dir} and/or authorized_keys to {username} and their group, Exception: {e}",) + f"Could not chown {ssh_dir} and/or authorized_keys to {self.user} and their group, Exception: {e}",) except KeyError as e: # by PWD - raise lib.UserExceptions.General(f"PWD can't find {username}: {e}") + raise lib.UserExceptions.General(f"PWD can't find {self.user}: {e}") return True @staticmethod @@ -135,11 +137,9 @@ class System: f.close() os.chmod(ssh_dir + "authorized_keys", 0o700) # we dont care about the directory here - def lock_user_pw(self, username: str, cc: tuple = tuple(["usermod", "--lock"])) -> bool: + def lock_user_pw(self, cc: tuple = tuple(["usermod", "--lock"])) -> bool: """Lock a users password so it stays empty - :param username: Username of the user which accounts password you want to lock - :type username: str :param cc: Commands to run in the subprocess to lock it down(defaults to usermod --lock) :type cc: tuple :rtype: bool @@ -149,21 +149,19 @@ class System: """ lock_command = cc - cc = lock_command + tuple([username]) + cc = lock_command + tuple([self.user]) if self.dry: self.printTuple(cc) return True elif not self.dry: rt = subprocess.call(cc) if rt != 0: - raise lib.UserExceptions.UnknownReturnCode(f"Could not lock user '{username}'; '{cc}' returned '{rt}'") + raise lib.UserExceptions.UnknownReturnCode(f"Could not lock user '{self.user}'; '{cc}' returned '{rt}'") return True - def add_to_usergroup(self, username: str, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])) -> bool: + def add_to_usergroup(self, group: str = "tilde", cc: tuple = tuple(["usermod", "-a", "-G"])) -> bool: """ Adds a given user to a given group - :param username: Username to add to your wanted group - :type username: str :param group: Groupname where you want to add your user to :type group: str :param cc: Commands to execute that adds your user to said specific group(defaults to usermod -a -G") @@ -175,7 +173,7 @@ class System: """ add_command = cc - cc = add_command + tuple([group, username]) + cc = add_command + tuple([group, self.user]) if self.dry: self.printTuple(cc) return True @@ -183,7 +181,7 @@ class System: rt = subprocess.call(cc) if rt != 0: raise lib.UserExceptions.UnknownReturnCode( - f"Could not add user '{username}' to group '{group}' with command '{cc}', returned '{rt}'",) + f"Could not add user '{self.user}' to group '{group}' with command '{cc}', returned '{rt}'",) return True @staticmethod @@ -201,11 +199,9 @@ class System: pp += i + " " print(pp) - def remove_user(self, username: str, cc: tuple = tuple(["userdel", "-r"])) -> bool: + def remove_user(self, cc: tuple = tuple(["userdel", "-r"])) -> bool: """Removes the specified user from the system - :param username: The username you want to delete from the system. - :type username: str :param cc: Commands to execute to delete the user from the System(defaults to userdel -r) :type cc: tuple :return: True, if worked, raises lib.UserExceptions.UnknownReturnCode when not @@ -215,7 +211,7 @@ class System: """ remove_command = cc - cc = remove_command + tuple([username]) + cc = remove_command + tuple([self.user]) if self.dry: self.printTuple(cc) return True @@ -231,19 +227,19 @@ class System: def AIO(username, pubkey, group="tilde"): - sys_ctl = System(dryrun=False) - sys_ctl.register(username) - sys_ctl.lock_user_pw(username) - sys_ctl.add_to_usergroup(username, group) - sys_ctl.make_ssh_usable(username, pubkey) + sys_ctl = System(username, dryrun=False) + sys_ctl.register() + sys_ctl.lock_user_pw() + sys_ctl.add_to_usergroup(group) + sys_ctl.make_ssh_usable(pubkey) if __name__ == "__main__": try: - S = System(dryrun=True) - S.register("dar") - S.lock_user_pw("dar") - S.add_to_usergroup("dar") + S = System("dar", dryrun=True) + S.register() + S.lock_user_pw() + S.add_to_usergroup() # if not S.make_ssh_usable("dar", "SSHpub"): # print("Huh, error :shrug:") exit(0) From dd01acf801232d46cfb1caad2fb3232af386bda6 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 17:38:43 +0200 Subject: [PATCH 71/79] editUsers: Refactor from System taken --- private/editUsers.py | 63 ++++++++++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/private/editUsers.py b/private/editUsers.py index 2483ca8..5075847 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -42,34 +42,38 @@ if __name__ == "__main__": and not args.remove: print(f"Well, SOMETHING must be done with {args.user} ;-)") exit(1) + # --> --user if not lib.validator.checkUserInDB(args.user, db): - print(f"User {args.user} doesn't exist in the database.") + print(f"User {args.user} does not exist in the database.") exit(1) DB = lib.sqlitedb.SQLitedb(db) - Sysctl = lib.System.System() + sys_ctl = lib.System.System(args.user) if not DB: - print("Couldn't establish connection to database") + print("Could not establish connection to database") exit(1) - CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0] + + # --> --remove if args.remove: print(f"Removing {args.user} from the system and the database...") try: DB.removeApplicantFromDBperUsername(args.user) print(f"Purged from the DB") if CurrentUser["status"] == 1: - Sysctl.remove_user(args.user) + sys_ctl.remove_user() print(f"Purged from the system") else: - print(f"{args.user} wasn't approved before, therefore not deleting from system itself.") + print(f"'{args.user}' was not approved before, therefore not deleting from system itself.") except lib.UserExceptions.General as e: print(f"{e}") exit(1) - print("Success.") + print(f"Successfully removed '{args.user}'.") exit(0) + + # --> --sshpubkey if args.sshpubkey: if not lib.validator.checkSSHKey(args.sshpubkey): - print(f"Pubkey {args.sshpubkey} isn't valid.") + print(f"Pubkey '{args.sshpubkey}' isn't valid.") exit(1) try: DB.safequery("UPDATE `applications` SET `pubkey`=? WHERE `username`=?", @@ -80,58 +84,67 @@ if __name__ == "__main__": fetch = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user])) if int(fetch[0]["status"]) == 1: try: - Sysctl.make_ssh_usable(args.user, args.sshpubkey) + sys_ctl.make_ssh_usable(args.sshpubkey) except lib.UserExceptions.ModifyFilesystem as e: print(f"One action failed during writing the ssh key back to the authorization file. {e}") - print(f"{args.user} updated successfully.") + print(f"'{args.user}'s SSH-Key updated successfully.") + # --> --name if args.name: if not lib.validator.checkName(args.name): - print(f"{args.name} is not a valid Name.") + print(f"'{args.name}' is not a valid Name.") exit(1) try: DB.safequery("UPDATE `applications` SET `name` =? WHERE `username` =?", tuple([args.name, args.user])) except sqlite3.Error as e: - print(f"Couldn't write {args.name} to database: {e}") + print(f"Could not write '{args.name}' to database: {e}") + print(f"'{args.user}'s Name changed to '{args.name}'.") + + # --> --email if args.email: if not lib.validator.checkEmail(args.email): - print(f"{args.email} is not a valid Mail address!") + print(f"'{args.email}' is not a valid Mail address!") exit(1) try: DB.safequery("UPDATE `applications` SET `email` =? WHERE `username` =?", tuple([args.email])) except sqlite3.Error as e: - print(f"Couldn't write {args.email} to the database. {e}") + print(f"Could not write '{args.email}' to the database. {e}") + print(f"'{args.user}' Mail changed to '{args.email}'.") + + # --> --status if args.status is not None: if args.status != 0 and args.status != 1: print("Only 0 and 1 are valid status, where 1 is activated and 0 is unapproved.") exit(0) # just takes first result out of the dict if args.status == int(CurrentUser["status"]): - print(f"Old and new Status matches, didn't change") + print(f"New and old status are the same.") if args.status == 0 and int(CurrentUser["status"]) == 1: try: DB.safequery("UPDATE `applications` SET `status` =? WHERE `id`=?", tuple([args.status, CurrentUser["id"]])) except sqlite3.Error as e: - print(f"Couldn't update database entry for {args.user}, didn't touch the system") + print(f"Could not update database entry for '{args.user}', did not touch the system") exit(1) try: - Sysctl.remove_user(args.user) + sys_ctl.remove_user() except lib.UserExceptions.UnknownReturnCode as e: - print(f"Couldn't remove {args.user} from the system, unknown return code: {e}. DB is modified.") + print(f"Could not remove '{args.user}' from the system, unknown return code: {e}. DB is modified.") exit(1) + + print(f"Successfully changed '{args.user}'s status to 0 and cleared from the system.") if args.status == 1 and int(CurrentUser["status"]) == 0: try: DB.safequery("UPDATE `applications` SET `status`=? WHERE `username`=?", tuple([args.status, args.user])) except sqlite3.Error as e: - print(f"Couldn't update Users status in database") + print(f"Could not update Users status in database") exit(1) try: - Sysctl.register(args.user) - Sysctl.lock_user_pw(args.user) - Sysctl.add_to_usergroup(args.user) - Sysctl.make_ssh_usable(args.user, CurrentUser["pubkey"]) + sys_ctl.register() + sys_ctl.lock_user_pw() + sys_ctl.add_to_usergroup() + sys_ctl.make_ssh_usable(CurrentUser["pubkey"]) except lib.UserExceptions.UserExistsAlready as UEA: print(f"Somehow the user exists already on the system! {UEA}") exit(1) @@ -139,11 +152,11 @@ if __name__ == "__main__": print(f"Unknown return code: {URC}") exit(1) except lib.UserExceptions.SSHDirUncreatable as SDU: - print(f"Couldnt create ssh directory for {args.user}, exception: {SDU}") + print(f"Could not create ssh directory for {args.user}, exception: {SDU}") exit(1) except lib.UserExceptions.ModifyFilesystem as MFS: pass - print(f"Success! {args.user}") + print(f"Successfully changed '{args.user}'s status to 1 and created on the system.") exit(0) except KeyboardInterrupt as e: pass From f31117e940a1da5942ddd4773dfd196f39421c33 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 17:50:13 +0200 Subject: [PATCH 72/79] System: aio_register, performs all neccessary steps to create user... BUT will NOT catch ANY exceptions. Therefore the caller script itself is REQUIRED to catch. --- private/Import.py | 5 +---- private/editUsers.py | 5 +---- private/lib/System.py | 28 ++++++++++++++++++++-------- 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/private/Import.py b/private/Import.py index 1010fb1..0859d27 100755 --- a/private/Import.py +++ b/private/Import.py @@ -44,10 +44,7 @@ def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bo if row["status"] == "1": try: sys_ctl.setUser(row["username"]) - sys_ctl.register() - sys_ctl.lock_user_pw() - sys_ctl.add_to_usergroup() - sys_ctl.make_ssh_usable(row["pubkey"]) + sys_ctl.aio_register(row["pubkey"]) print(row['username'], "====> Registered.") except lib.UserExceptions.UserExistsAlready as UEA: pass # @TODO User was determined to exists already, shouldn't happen but is possible diff --git a/private/editUsers.py b/private/editUsers.py index 5075847..8754952 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -141,10 +141,7 @@ if __name__ == "__main__": print(f"Could not update Users status in database") exit(1) try: - sys_ctl.register() - sys_ctl.lock_user_pw() - sys_ctl.add_to_usergroup() - sys_ctl.make_ssh_usable(CurrentUser["pubkey"]) + sys_ctl.aio_register(CurrentUser["pubkey"]) except lib.UserExceptions.UserExistsAlready as UEA: print(f"Somehow the user exists already on the system! {UEA}") exit(1) diff --git a/private/lib/System.py b/private/lib/System.py index aba22f2..467a02b 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -45,6 +45,26 @@ class System: self.home = home self.user = username + def aio_register(self, pubkey, group="tilde"): + """ Executes all neccessary steps to create a user from itself. Raises ALOT of possible exceptions + + :Note: CAREFULL! You MUST except the exceptions! + :param pubkey: Users public ssh key + :type pubkey: str + :param group: User-group. Defaults to tilde + :type group: str + :return: None + :raises: + lib.UserExceptions.UserExistsAlready: User Exists already on system + lib.UserExceptions.UnknownReturnCode: Unknown Return Code from useradd + lib.UserExceptions.SSHDirUncreatable: Users SSH Dir couldnt be created + lib.UserExceptions.ModifyFilesystem: Something with CHMOD failed + """ + self.register() + self.lock_user_pw() + self.add_to_usergroup(group) + self.make_ssh_usable(pubkey) + def register(self, cc: tuple = tuple(["useradd", "-m"])) -> bool: """Creates an local account for the given username @@ -226,14 +246,6 @@ class System: return True -def AIO(username, pubkey, group="tilde"): - sys_ctl = System(username, dryrun=False) - sys_ctl.register() - sys_ctl.lock_user_pw() - sys_ctl.add_to_usergroup(group) - sys_ctl.make_ssh_usable(pubkey) - - if __name__ == "__main__": try: S = System("dar", dryrun=True) From fb4b577eb8eb25c899093e472cf4d1e2d257315d Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 18:01:13 +0200 Subject: [PATCH 73/79] minor adjustemenets --- private/Import.py | 18 ++------- private/editUsers.py | 44 +++++++++------------- private/lib/{validator.py => Validator.py} | 14 +++---- 3 files changed, 28 insertions(+), 48 deletions(-) rename private/lib/{validator.py => Validator.py} (94%) diff --git a/private/Import.py b/private/Import.py index 0859d27..a86ee8f 100755 --- a/private/Import.py +++ b/private/Import.py @@ -30,9 +30,9 @@ def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bo # noinspection PyBroadException try: with open(file_path, 'r', newline='') as f: - import lib.validator + import lib.Validator sql = lib.sqlitedb.SQLitedb(db) - err = lib.validator.checkImportFile(file_path, db) + err = lib.Validator.checkImportFile(file_path, db) if err is not True: print(err) exit(0) @@ -46,18 +46,8 @@ def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bo sys_ctl.setUser(row["username"]) sys_ctl.aio_register(row["pubkey"]) print(row['username'], "====> Registered.") - except lib.UserExceptions.UserExistsAlready as UEA: - pass # @TODO User was determined to exists already, shouldn't happen but is possible - except lib.UserExceptions.UnknownReturnCode as URC: - pass # @TODO Unknown Return Codes. Can happen in various function - except lib.UserExceptions.SSHDirUncreatable as SDU: - pass # @TODO SSH Directory doesn't exist AND couldn't be created. Inherently wrong design! - except lib.UserExceptions.ModifyFilesystem as MFS: - pass # @TODO Same as SSH Dir but more general, same problem: Wrong Permissions, - # Missing Dirs etc - except Exception as E: # @TODO well less broad is hard to achieve Kappa - print(E) - continue + except lib.UserExceptions.General as GeneralExcept: + print(f"Something didnt work out! {GeneralExcept}") elif row["status"] == "0": print(row['username'] + " not approved, therefore not registered.") try: diff --git a/private/editUsers.py b/private/editUsers.py index 8754952..c3e200d 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -2,13 +2,12 @@ import configparser import lib.uis.config_ui # only follow -c flag -import lib.validator +import lib.Validator import lib.sqlitedb import lib.System import lib.UserExceptions import sqlite3 - if __name__ == "__main__": lib.uis.config_ui.argparser.description += " - Edit Tilde Users" ArgParser = lib.uis.config_ui.argparser @@ -43,7 +42,7 @@ if __name__ == "__main__": print(f"Well, SOMETHING must be done with {args.user} ;-)") exit(1) # --> --user - if not lib.validator.checkUserInDB(args.user, db): + if not lib.Validator.checkUserInDB(args.user, db): print(f"User {args.user} does not exist in the database.") exit(1) DB = lib.sqlitedb.SQLitedb(db) @@ -72,26 +71,25 @@ if __name__ == "__main__": # --> --sshpubkey if args.sshpubkey: - if not lib.validator.checkSSHKey(args.sshpubkey): + if not lib.Validator.checkSSHKey(args.sshpubkey): print(f"Pubkey '{args.sshpubkey}' isn't valid.") exit(1) try: DB.safequery("UPDATE `applications` SET `pubkey`=? WHERE `username`=?", tuple([args.sshpubkey, args.user])) + CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user]))[0] + if int(CurrentUser["status"]) == 1: + sys_ctl.make_ssh_usable(args.sshpubkey) except sqlite3.Error as e: print(f"Something unexpected happened! {e}") exit(1) - fetch = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user])) - if int(fetch[0]["status"]) == 1: - try: - sys_ctl.make_ssh_usable(args.sshpubkey) - except lib.UserExceptions.ModifyFilesystem as e: - print(f"One action failed during writing the ssh key back to the authorization file. {e}") + except lib.UserExceptions.ModifyFilesystem as e: + print(f"One action failed during writing the ssh key back to the authorization file. {e}") print(f"'{args.user}'s SSH-Key updated successfully.") # --> --name if args.name: - if not lib.validator.checkName(args.name): + if not lib.Validator.checkName(args.name): print(f"'{args.name}' is not a valid Name.") exit(1) try: @@ -102,7 +100,7 @@ if __name__ == "__main__": # --> --email if args.email: - if not lib.validator.checkEmail(args.email): + if not lib.Validator.checkEmail(args.email): print(f"'{args.email}' is not a valid Mail address!") exit(1) try: @@ -116,43 +114,35 @@ if __name__ == "__main__": if args.status != 0 and args.status != 1: print("Only 0 and 1 are valid status, where 1 is activated and 0 is unapproved.") exit(0) + # just takes first result out of the dict if args.status == int(CurrentUser["status"]): print(f"New and old status are the same.") + if args.status == 0 and int(CurrentUser["status"]) == 1: try: DB.safequery("UPDATE `applications` SET `status` =? WHERE `id`=?", tuple([args.status, CurrentUser["id"]])) + sys_ctl.remove_user() except sqlite3.Error as e: print(f"Could not update database entry for '{args.user}', did not touch the system") exit(1) - try: - sys_ctl.remove_user() except lib.UserExceptions.UnknownReturnCode as e: print(f"Could not remove '{args.user}' from the system, unknown return code: {e}. DB is modified.") exit(1) - print(f"Successfully changed '{args.user}'s status to 0 and cleared from the system.") + if args.status == 1 and int(CurrentUser["status"]) == 0: try: DB.safequery("UPDATE `applications` SET `status`=? WHERE `username`=?", tuple([args.status, args.user])) + sys_ctl.aio_register(CurrentUser["pubkey"]) except sqlite3.Error as e: print(f"Could not update Users status in database") exit(1) - try: - sys_ctl.aio_register(CurrentUser["pubkey"]) - except lib.UserExceptions.UserExistsAlready as UEA: - print(f"Somehow the user exists already on the system! {UEA}") - exit(1) - except lib.UserExceptions.UnknownReturnCode as URC: - print(f"Unknown return code: {URC}") - exit(1) - except lib.UserExceptions.SSHDirUncreatable as SDU: - print(f"Could not create ssh directory for {args.user}, exception: {SDU}") + except lib.UserExceptions.General as ChangeUser: + print(f"Some chain in the cattle just slipped away, my lord! {ChangeUser}") exit(1) - except lib.UserExceptions.ModifyFilesystem as MFS: - pass print(f"Successfully changed '{args.user}'s status to 1 and created on the system.") exit(0) except KeyboardInterrupt as e: diff --git a/private/lib/validator.py b/private/lib/Validator.py similarity index 94% rename from private/lib/validator.py rename to private/lib/Validator.py index ede8c13..2ff39cc 100644 --- a/private/lib/validator.py +++ b/private/lib/Validator.py @@ -178,15 +178,15 @@ def checkImportFile(path: str, db: str): reader = csv.DictReader(f) for row in reader: # if any of this fails move on to the next user, just print a relatively helpful message lel - if not lib.validator.checkName(row["name"]): + if not lib.Validator.checkName(row["name"]): errstr += f"Line {ln}: Name: '{row['name']}' seems not legit. Character followed by character should" \ f" be correct.\n" valid = False - if not lib.validator.checkUsernameCharacters(row["username"]): + if not lib.Validator.checkUsernameCharacters(row["username"]): errstr += (f"Line {ln}: Username contains unsupported characters or starts with a number: '" f"{row['username']}'.\n") valid = False - if not lib.validator.checkUsernameLength(row["username"]): + if not lib.Validator.checkUsernameLength(row["username"]): errstr += f"Line {ln}: Username '{row['username']}' is either too long(>16) or short(<3)\n" valid = False # dup checking @@ -196,17 +196,17 @@ def checkImportFile(path: str, db: str): else: valid_names_list.append(row["username"]) # dup end - if not lib.validator.checkSSHKey(row["pubkey"]): + if not lib.Validator.checkSSHKey(row["pubkey"]): errstr += f"Line {ln}: Following SSH-Key of user '{row['username']}' isn't valid: " \ f"'{row['pubkey']}'.\n" valid = False - if not lib.validator.checkEmail(row["email"]): + if not lib.Validator.checkEmail(row["email"]): errstr += f"Line {ln}: E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" valid = False - if not lib.validator.checkUserExists(row["username"]) or checkUserInDB(row["username"], db): + if not lib.Validator.checkUserExists(row["username"]) or checkUserInDB(row["username"], db): errstr += f"Line {ln}: User '{row['username']}' already exists.\n" valid = False - if not lib.validator.checkDatetimeFormat(row["timestamp"]): + if not lib.Validator.checkDatetimeFormat(row["timestamp"]): errstr += f"Line {ln}: Timestamp '{row['timestamp']}' from user '{row['username']}' is invalid.\n" valid = False if int(row["status"]) > 1 or int(row["status"]) < 0: From b7ffedfba25e874a635ae41f78a60bae3a452fdd Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Wed, 23 Oct 2019 18:13:42 +0200 Subject: [PATCH 74/79] Rename SQLitedb to SQLiteDB --- private/Import.py | 2 +- private/ListUsers.py | 4 ++-- private/editUsers.py | 2 +- private/lib/Validator.py | 2 +- private/lib/sqlitedb.py | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/private/Import.py b/private/Import.py index a86ee8f..09bd049 100755 --- a/private/Import.py +++ b/private/Import.py @@ -31,7 +31,7 @@ def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bo try: with open(file_path, 'r', newline='') as f: import lib.Validator - sql = lib.sqlitedb.SQLitedb(db) + sql = lib.sqlitedb.SQLiteDB(db) err = lib.Validator.checkImportFile(file_path, db) if err is not True: print(err) diff --git a/private/ListUsers.py b/private/ListUsers.py index 3682c07..6f4df02 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -from lib.sqlitedb import SQLitedb +from lib.sqlitedb import SQLiteDB import configparser import lib.uis.default as default_cmd # Follows -u, -a, -f flags @@ -20,7 +20,7 @@ class ListUsers: :type approved: bool """ - self.db = SQLitedb(db) + self.db = SQLiteDB(db) if unapproved: # only unapproved users query = "SELECT * FROM `applications` WHERE `status` = '0'" elif approved: # Approved users diff --git a/private/editUsers.py b/private/editUsers.py index c3e200d..77e6096 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -45,7 +45,7 @@ if __name__ == "__main__": if not lib.Validator.checkUserInDB(args.user, db): print(f"User {args.user} does not exist in the database.") exit(1) - DB = lib.sqlitedb.SQLitedb(db) + DB = lib.sqlitedb.SQLiteDB(db) sys_ctl = lib.System.System(args.user) if not DB: print("Could not establish connection to database") diff --git a/private/lib/Validator.py b/private/lib/Validator.py index 2ff39cc..967ce47 100644 --- a/private/lib/Validator.py +++ b/private/lib/Validator.py @@ -71,7 +71,7 @@ def checkUserInDB(username: str, db: str) -> bool: """ try: - ldb = lib.sqlitedb.SQLitedb(db) + ldb = lib.sqlitedb.SQLiteDB(db) fetched = ldb.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) if fetched: return True diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py index c51fb2d..1dc1aa0 100644 --- a/private/lib/sqlitedb.py +++ b/private/lib/sqlitedb.py @@ -11,7 +11,7 @@ def dict_factory(cursor, row): return d -class SQLitedb: +class SQLiteDB: """SQLitedb handles EVERYTHING directly related to our Database.""" db = "" @@ -156,7 +156,7 @@ class SQLitedb: if __name__ == "__main__": try: - SQLitedb("bla.db") + SQLiteDB("bla.db") print("hi") exit(0) except KeyboardInterrupt: From 84508820a6d1541f7bfc5175c7302b2c371d85eb Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Fri, 25 Oct 2019 01:57:29 +0200 Subject: [PATCH 75/79] ListUsers: Implement --user flag, rename --list to --list-asc --- private/ListUsers.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/private/ListUsers.py b/private/ListUsers.py index 6f4df02..d19864f 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -9,7 +9,7 @@ class ListUsers: db = None usersFetch = None - def __init__(self, db: str, unapproved: bool = False, approved: bool = True): + def __init__(self, db: str, unapproved: bool = False, approved: bool = True, single_user: str = None): """Constructs ListUsers :param db: Database to access @@ -25,6 +25,10 @@ class ListUsers: query = "SELECT * FROM `applications` WHERE `status` = '0'" elif approved: # Approved users query = "SELECT * FROM `applications` WHERE `status` = '1'" + elif single_user is not None: + query = "SELECT * FROM `applications` WHERE `username` = ?" + self.usersFetch = self.db.safequery(query, tuple([single_user])) + return else: # All users query = "SELECT * FROM `applications`" self.usersFetch = self.db.query(query) @@ -80,16 +84,22 @@ print(t.draw()) """ if __name__ == "__main__": default_cmd.argparser.description += " - Lists Users from the Tilde database." - default_cmd.argparser.add_argument('--list', default=False, action="store_true", - help='Output a newline seperated list of users', required=False) + default_cmd.argparser.add_argument('--list-asc', default=False, action="store_true", + help='Output a newline seperated list of users', required=False, dest="args_asc") + default_cmd.argparser.add_argument('--user', default=None, type=str, + help="Just show a specific user by it's name", required=False) args = default_cmd.argparser.parse_args() config = configparser.ConfigParser() config.read(args.config) try: ret = "" - L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) - if args.list: + if args.user is not None: + L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved, + single_user=args.user) + else: + L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) + if args.args_asc: ret = L.output_as_list() else: fetch = L.get_fetch() From 8d4d2038d3ac5305847f27a4e2348ab07b8e413b Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Fri, 25 Oct 2019 03:03:47 +0200 Subject: [PATCH 76/79] Typing support and sqlite3.Row! Typing will be handy in further development, as well will i settle down to sqlite3.Row. Fetchall() returns now a list(grr) with dict-imitating/sumating objects, which you can call keys() on and will return them, what we now already use for Backups, which comes handy. Also Typing gives the ability to let the code even more documentate itself. It's planned to use all over the place but i've to read myself into it yet more to get started on that one. It's a step in the right direction! --- private/Backup.py | 14 +++++++++++--- private/ListUsers.py | 18 ++++++++++-------- private/lib/sqlitedb.py | 15 ++++++++------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index ef5e13f..95df494 100755 --- a/private/Backup.py +++ b/private/Backup.py @@ -96,7 +96,10 @@ class Backup: returner = io.StringIO() write_csv = csv.DictWriter(returner, fieldnames=self.field_names, quoting=self.quoting, dialect=self.dialect) write_csv.writeheader() - write_csv.writerows(fetched) + for row in fetched: + write_csv.writerow(dict(row)) + # sqlite3.Row doesn't "easily" convert to a dict itself sadly, so just a quick help from us here + # it actually even delivers a list(sqlite3.Row) also, which doesnt make the life a whole lot easier if self.filename == "stdout": print(returner.getvalue()) @@ -116,8 +119,13 @@ if __name__ == "__main__": L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) fetch = L.get_fetch() - B = Backup(args.file) - B.backup_to_file(fetch) + if fetch: + B = Backup(args.file) + B.setFieldnames(fetch[0].keys()) # sqlite3.row delivers its keys for us! SO NICE! + B.backup_to_file(fetch) + else: + print("nothing to backup!") + exit(1) exit(0) except KeyboardInterrupt as e: pass diff --git a/private/ListUsers.py b/private/ListUsers.py index d19864f..df7283a 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -1,9 +1,12 @@ #!/usr/bin/env python3 from lib.sqlitedb import SQLiteDB -import configparser import lib.uis.default as default_cmd # Follows -u, -a, -f flags +from typing import List # Typing support! +import sqlite3 # sqlite3.Row-Object +import configparser + class ListUsers: db = None @@ -25,13 +28,12 @@ class ListUsers: query = "SELECT * FROM `applications` WHERE `status` = '0'" elif approved: # Approved users query = "SELECT * FROM `applications` WHERE `status` = '1'" - elif single_user is not None: - query = "SELECT * FROM `applications` WHERE `username` = ?" - self.usersFetch = self.db.safequery(query, tuple([single_user])) - return else: # All users query = "SELECT * FROM `applications`" self.usersFetch = self.db.query(query) + if single_user is not None: + query = "SELECT * FROM `applications` WHERE `username` = ?" + self.usersFetch = self.db.safequery(query, tuple([single_user])) def output_as_list(self) -> str: """Generates a string with one (approved) user per line and one newline at the end @@ -50,11 +52,11 @@ class ListUsers: def prettyPrint(self) -> None: pass # see below why not implemented yet, texttable... - def get_fetch(self) -> list: + def get_fetch(self) -> List[sqlite3.Row]: """ Returns a complete fetch done by the lib.sqlitedb-class - :return: Complete fetchall() in a dict - :rtype: list + :return: Complete fetchall(). A List[sqlite3.Row] with dict-emulation objects. + :rtype: List[sqlite3.Row] """ return self.usersFetch diff --git a/private/lib/sqlitedb.py b/private/lib/sqlitedb.py index 1dc1aa0..d131e00 100644 --- a/private/lib/sqlitedb.py +++ b/private/lib/sqlitedb.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 import sqlite3 from sys import stderr as stderr +from typing import List # Typing support! # create dictionary out of sqlite results def dict_factory(cursor, row): - d = {} + d: dict = {} for idx, col in enumerate(cursor.description): d[col[0]] = row[idx] return d @@ -34,7 +35,7 @@ class SQLiteDB: except sqlite3.Error as e: print("Connection error: %s" % e, file=stderr) - self.cursor.row_factory = dict_factory # every result will be a dict now + self.cursor.row_factory = sqlite3.Row # every result will be a dict now def __del__(self): try: @@ -43,7 +44,7 @@ class SQLiteDB: except sqlite3.Error as e: print("Couldn't gracefully close db: %s" % e, file=stderr) - def query(self, qq: str) -> list: + def query(self, qq: str) -> List[sqlite3.Row]: """Do a query and automagically get the fetched results in a list :param qq: Query to execute :type qq: str @@ -71,14 +72,14 @@ class SQLiteDB: # we could try to utilise that ourselfs in a function. Be c a r e f u l, these values in the tuple MUST HAVE # THE RIGHT TYPE - def safequery(self, qq: str, deliver: tuple) -> list: + def safequery(self, qq: str, deliver: tuple) -> List[sqlite3.Row]: """ Shall handle any query that has user input in it as an alternative to self.query :param qq: Query to execute :type qq: str :param deliver: User inputs marked with the placeholder(`?`) in the str :type deliver: tuple :returns: A tuple(/list) consisting with any fetched results - :rtype: list + :rtype: List[sqlite3.Row] """ try: @@ -88,7 +89,7 @@ class SQLiteDB: except TypeError as e: print("Types in given tuple doesnt match to execute query \"%s\": %s" % (qq, e), file=stderr) self.last_result = [] - except sqlite3.OperationalError as e: + except sqlite3.OperationalError: self._createTable() return self.safequery(qq, deliver) except sqlite3.Error as e: @@ -138,7 +139,7 @@ class SQLiteDB: return False return True - def _createTable(self): + def _createTable(self) -> None: try: self.cursor.execute( "CREATE TABLE IF NOT EXISTS applications(" From 32c6ea7e079a75b9ebae05767a4001ab25270fa0 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Fri, 25 Oct 2019 06:49:43 +0200 Subject: [PATCH 77/79] System: Rename aio_register to aio_approve. --- private/Import.py | 2 +- private/editUsers.py | 2 +- private/lib/System.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/private/Import.py b/private/Import.py index 09bd049..9e16ebf 100755 --- a/private/Import.py +++ b/private/Import.py @@ -44,7 +44,7 @@ def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bo if row["status"] == "1": try: sys_ctl.setUser(row["username"]) - sys_ctl.aio_register(row["pubkey"]) + sys_ctl.aio_approve(row["pubkey"]) print(row['username'], "====> Registered.") except lib.UserExceptions.General as GeneralExcept: print(f"Something didnt work out! {GeneralExcept}") diff --git a/private/editUsers.py b/private/editUsers.py index 77e6096..c6c47fe 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -136,7 +136,7 @@ if __name__ == "__main__": try: DB.safequery("UPDATE `applications` SET `status`=? WHERE `username`=?", tuple([args.status, args.user])) - sys_ctl.aio_register(CurrentUser["pubkey"]) + sys_ctl.aio_approve(CurrentUser["pubkey"]) except sqlite3.Error as e: print(f"Could not update Users status in database") exit(1) diff --git a/private/lib/System.py b/private/lib/System.py index 467a02b..e9e1313 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -45,7 +45,7 @@ class System: self.home = home self.user = username - def aio_register(self, pubkey, group="tilde"): + def aio_approve(self, pubkey, group="tilde"): """ Executes all neccessary steps to create a user from itself. Raises ALOT of possible exceptions :Note: CAREFULL! You MUST except the exceptions! From 8008fa36b9df17ac783894488600b3b2851c9d50 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Fri, 25 Oct 2019 10:17:39 +0200 Subject: [PATCH 78/79] General overhaul, better messages, ordered imports etc --- private/Backup.py | 30 +++-- private/Import.py | 23 ++-- private/ListUsers.py | 61 +++++----- private/editUsers.py | 204 +++++++++++++++++----------------- private/lib/System.py | 9 +- private/lib/UserExceptions.py | 18 +-- private/lib/Validator.py | 10 +- 7 files changed, 175 insertions(+), 180 deletions(-) diff --git a/private/Backup.py b/private/Backup.py index 95df494..570f11b 100755 --- a/private/Backup.py +++ b/private/Backup.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 -import ListUsers +import configparser import csv import io -import configparser + +import ListUsers import lib.uis.default as default_cmd # Follows -u, -a, -f flags @@ -115,17 +116,14 @@ if __name__ == "__main__": args = default_cmd.argparser.parse_args() config = configparser.ConfigParser() config.read(args.config) - try: - L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], - unapproved=args.unapproved, approved=args.approved) - fetch = L.get_fetch() - if fetch: - B = Backup(args.file) - B.setFieldnames(fetch[0].keys()) # sqlite3.row delivers its keys for us! SO NICE! - B.backup_to_file(fetch) - else: - print("nothing to backup!") - exit(1) - exit(0) - except KeyboardInterrupt as e: - pass + L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], + unapproved=args.unapproved, approved=args.approved) + fetch = L.get_fetch() + if fetch: + B = Backup(args.file) + B.setFieldnames(fetch[0].keys()) # sqlite3.row delivers its keys for us! SO NICE! + B.backup_to_file(fetch) + else: + print("nothing to backup!") + exit(1) + exit(0) diff --git a/private/Import.py b/private/Import.py index 9e16ebf..0d57471 100755 --- a/private/Import.py +++ b/private/Import.py @@ -1,8 +1,9 @@ #!/usr/bin/env python3 +import configparser import csv import os -import configparser + import lib.UserExceptions import lib.uis.config_ui # dont go to default, just following -c flag @@ -74,15 +75,13 @@ if __name__ == "__main__": args = ArgParser.parse_args() config = configparser.ConfigParser() config.read(args.config) - try: - if not args.Import: - print("Error, need the import flag") + + if not args.Import: + print("Error, need the import flag") + if not args.file: + print("Error, need the import file") if not args.file: - print("Error, need the import file") - if not args.file: - print("You MUST set a CSV-file with the -f/--file flag that already exist") - exit(1) - import_from_file(args.file, config['DEFAULT']['applications_db']) - exit(0) - except KeyboardInterrupt as e: - pass + print("You MUST set a CSV-file with the -f/--file flag that already exist") + exit(1) + import_from_file(args.file, config['DEFAULT']['applications_db']) + exit(0) diff --git a/private/ListUsers.py b/private/ListUsers.py index df7283a..3d36770 100755 --- a/private/ListUsers.py +++ b/private/ListUsers.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 -from lib.sqlitedb import SQLiteDB -import lib.uis.default as default_cmd # Follows -u, -a, -f flags - -from typing import List # Typing support! -import sqlite3 # sqlite3.Row-Object import configparser +import sqlite3 # sqlite3.Row-Object +from typing import List # Typing support! + +import lib.uis.default as default_cmd # Follows -u, -a, -f flags +from lib.sqlitedb import SQLiteDB class ListUsers: @@ -46,7 +46,7 @@ class ListUsers: query = "SELECT `username` FROM `applications` WHERE `status` = '1' ORDER BY timestamp ASC" self.usersFetch = self.db.query(query) for users in self.usersFetch: - list_str += users["username"]+"\n" + list_str += users["username"] + "\n" return list_str def prettyPrint(self) -> None: @@ -94,30 +94,27 @@ if __name__ == "__main__": config = configparser.ConfigParser() config.read(args.config) - try: - ret = "" - if args.user is not None: - L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved, - single_user=args.user) - else: - L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) - if args.args_asc: - ret = L.output_as_list() - else: - fetch = L.get_fetch() - ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % ( - " ", " ", " ", " ", " " + ret = "" + if args.user is not None: + L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved, + single_user=args.user) + else: + L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) + if args.args_asc: + ret = L.output_as_list() + else: + fetch = L.get_fetch() + ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % ( + " ", " ", " ", " ", " " + ) + ret += 102 * "-" + "\n" + for user in fetch: + ret += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % ( + user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] ) - ret += 102 * "-" + "\n" - for user in fetch: - ret += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % ( - user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] - ) - if args.file != "stdout": - with open(args.file, 'w') as f: - print(ret, file=f) - else: - print(ret) - exit(0) - except KeyboardInterrupt: - pass + if args.file != "stdout": + with open(args.file, 'w') as f: + print(ret, file=f) + else: + print(ret) + exit(0) diff --git a/private/editUsers.py b/private/editUsers.py index c6c47fe..19cf856 100755 --- a/private/editUsers.py +++ b/private/editUsers.py @@ -1,12 +1,13 @@ #!/usr/bin/env python3 import configparser -import lib.uis.config_ui # only follow -c flag -import lib.Validator -import lib.sqlitedb +import sqlite3 + import lib.System import lib.UserExceptions -import sqlite3 +import lib.Validator +import lib.sqlitedb +import lib.uis.config_ui # only follow -c flag if __name__ == "__main__": lib.uis.config_ui.argparser.description += " - Edit Tilde Users" @@ -35,115 +36,112 @@ if __name__ == "__main__": args = ArgParser.parse_args() config = configparser.ConfigParser() config.read(args.config) - try: - db = config['DEFAULT']['applications_db'] - if not args.sshpubkey and not args.name and not args.username and not args.email and args.status is None \ - and not args.remove: - print(f"Well, SOMETHING must be done with {args.user} ;-)") + db = config['DEFAULT']['applications_db'] + if not args.sshpubkey and not args.name and not args.username and not args.email and args.status is None \ + and not args.remove: + print(f"Well, SOMETHING must be done with {args.user} ;-)") + exit(1) + # --> --user + if not lib.Validator.checkUserInDB(args.user, db): + print(f"User {args.user} does not exist in the database.") + exit(1) + DB = lib.sqlitedb.SQLiteDB(db) + sys_ctl = lib.System.System(args.user) + if not DB: + print("Could not establish connection to database") + exit(1) + CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0] + + # --> --remove + if args.remove: + print(f"Removing {args.user} from the system and the database...") + try: + DB.removeApplicantFromDBperUsername(args.user) + print(f"Purged from the DB") + if CurrentUser["status"] == 1: + sys_ctl.remove_user() + print(f"Purged from the system") + else: + print(f"'{args.user}' was not approved before, therefore not deleting from system itself.") + except lib.UserExceptions.General as e: + print(f"{e}") + exit(1) + print(f"Successfully removed '{args.user}'.") + exit(0) + + # --> --sshpubkey + if args.sshpubkey: + if not lib.Validator.checkSSHKey(args.sshpubkey): + print(f"Pubkey '{args.sshpubkey}' isn't valid.") exit(1) - # --> --user - if not lib.Validator.checkUserInDB(args.user, db): - print(f"User {args.user} does not exist in the database.") + try: + DB.safequery("UPDATE `applications` SET `pubkey`=? WHERE `username`=?", + tuple([args.sshpubkey, args.user])) + CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user]))[0] + if int(CurrentUser["status"]) == 1: + sys_ctl.make_ssh_usable(args.sshpubkey) + except sqlite3.Error as e: + print(f"Something unexpected happened! {e}") exit(1) - DB = lib.sqlitedb.SQLiteDB(db) - sys_ctl = lib.System.System(args.user) - if not DB: - print("Could not establish connection to database") + except lib.UserExceptions.ModifyFilesystem as e: + print(f"One action failed during writing the ssh key back to the authorization file. {e}") + print(f"'{args.user}'s SSH-Key updated successfully.") + + # --> --name + if args.name: + if not lib.Validator.checkName(args.name): + print(f"'{args.name}' is not a valid Name.") exit(1) - CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0] + try: + DB.safequery("UPDATE `applications` SET `name` =? WHERE `username` =?", tuple([args.name, args.user])) + except sqlite3.Error as e: + print(f"Could not write '{args.name}' to database: {e}") + print(f"'{args.user}'s Name changed to '{args.name}'.") - # --> --remove - if args.remove: - print(f"Removing {args.user} from the system and the database...") - try: - DB.removeApplicantFromDBperUsername(args.user) - print(f"Purged from the DB") - if CurrentUser["status"] == 1: - sys_ctl.remove_user() - print(f"Purged from the system") - else: - print(f"'{args.user}' was not approved before, therefore not deleting from system itself.") - except lib.UserExceptions.General as e: - print(f"{e}") - exit(1) - print(f"Successfully removed '{args.user}'.") + # --> --email + if args.email: + if not lib.Validator.checkEmail(args.email): + print(f"'{args.email}' is not a valid Mail address!") + exit(1) + try: + DB.safequery("UPDATE `applications` SET `email` =? WHERE `username` =?", tuple([args.email])) + except sqlite3.Error as e: + print(f"Could not write '{args.email}' to the database. {e}") + print(f"'{args.user}' Mail changed to '{args.email}'.") + + # --> --status + if args.status is not None: + if args.status != 0 and args.status != 1: + print("Only 0 and 1 are valid status, where 1 is activated and 0 is unapproved.") exit(0) - # --> --sshpubkey - if args.sshpubkey: - if not lib.Validator.checkSSHKey(args.sshpubkey): - print(f"Pubkey '{args.sshpubkey}' isn't valid.") - exit(1) + # just takes first result out of the dict + if args.status == int(CurrentUser["status"]): + print(f"New and old status are the same.") + + if args.status == 0 and int(CurrentUser["status"]) == 1: try: - DB.safequery("UPDATE `applications` SET `pubkey`=? WHERE `username`=?", - tuple([args.sshpubkey, args.user])) - CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user]))[0] - if int(CurrentUser["status"]) == 1: - sys_ctl.make_ssh_usable(args.sshpubkey) + DB.safequery("UPDATE `applications` SET `status` =? WHERE `id`=?", + tuple([args.status, CurrentUser["id"]])) + sys_ctl.remove_user() except sqlite3.Error as e: - print(f"Something unexpected happened! {e}") + print(f"Could not update database entry for '{args.user}', did not touch the system") exit(1) - except lib.UserExceptions.ModifyFilesystem as e: - print(f"One action failed during writing the ssh key back to the authorization file. {e}") - print(f"'{args.user}'s SSH-Key updated successfully.") - - # --> --name - if args.name: - if not lib.Validator.checkName(args.name): - print(f"'{args.name}' is not a valid Name.") + except lib.UserExceptions.UnknownReturnCode as e: + print(f"Could not remove '{args.user}' from the system, unknown return code: {e}. DB is modified.") exit(1) - try: - DB.safequery("UPDATE `applications` SET `name` =? WHERE `username` =?", tuple([args.name, args.user])) - except sqlite3.Error as e: - print(f"Could not write '{args.name}' to database: {e}") - print(f"'{args.user}'s Name changed to '{args.name}'.") + print(f"Successfully changed '{args.user}'s status to 0 and cleared from the system.") - # --> --email - if args.email: - if not lib.Validator.checkEmail(args.email): - print(f"'{args.email}' is not a valid Mail address!") - exit(1) + if args.status == 1 and int(CurrentUser["status"]) == 0: try: - DB.safequery("UPDATE `applications` SET `email` =? WHERE `username` =?", tuple([args.email])) + DB.safequery("UPDATE `applications` SET `status`=? WHERE `username`=?", + tuple([args.status, args.user])) + sys_ctl.aio_approve(CurrentUser["pubkey"]) except sqlite3.Error as e: - print(f"Could not write '{args.email}' to the database. {e}") - print(f"'{args.user}' Mail changed to '{args.email}'.") - - # --> --status - if args.status is not None: - if args.status != 0 and args.status != 1: - print("Only 0 and 1 are valid status, where 1 is activated and 0 is unapproved.") - exit(0) - - # just takes first result out of the dict - if args.status == int(CurrentUser["status"]): - print(f"New and old status are the same.") - - if args.status == 0 and int(CurrentUser["status"]) == 1: - try: - DB.safequery("UPDATE `applications` SET `status` =? WHERE `id`=?", - tuple([args.status, CurrentUser["id"]])) - sys_ctl.remove_user() - except sqlite3.Error as e: - print(f"Could not update database entry for '{args.user}', did not touch the system") - exit(1) - except lib.UserExceptions.UnknownReturnCode as e: - print(f"Could not remove '{args.user}' from the system, unknown return code: {e}. DB is modified.") - exit(1) - print(f"Successfully changed '{args.user}'s status to 0 and cleared from the system.") - - if args.status == 1 and int(CurrentUser["status"]) == 0: - try: - DB.safequery("UPDATE `applications` SET `status`=? WHERE `username`=?", - tuple([args.status, args.user])) - sys_ctl.aio_approve(CurrentUser["pubkey"]) - except sqlite3.Error as e: - print(f"Could not update Users status in database") - exit(1) - except lib.UserExceptions.General as ChangeUser: - print(f"Some chain in the cattle just slipped away, my lord! {ChangeUser}") - exit(1) - print(f"Successfully changed '{args.user}'s status to 1 and created on the system.") - exit(0) - except KeyboardInterrupt as e: - pass + print(f"Could not update Users status in database") + exit(1) + except lib.UserExceptions.General as ChangeUser: + print(f"Some chain in the cattle just slipped away, my lord! {ChangeUser}") + exit(1) + print(f"Successfully changed '{args.user}'s status to 1 and created on the system.") + exit(0) diff --git a/private/lib/System.py b/private/lib/System.py index e9e1313..58aec01 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -1,6 +1,7 @@ import os -import subprocess import pwd +import subprocess + import lib.UserExceptions @@ -136,7 +137,7 @@ class System: os.chown(ssh_dir + "authorized_keys", pwd.getpwnam(self.user)[2], pwd.getpwnam(self.user)[3]) except OSError as e: # by os.chown raise lib.UserExceptions.ModifyFilesystem( - f"Could not chown {ssh_dir} and/or authorized_keys to {self.user} and their group, Exception: {e}",) + f"Could not chown {ssh_dir} and/or authorized_keys to {self.user} and their group, Exception: {e}", ) except KeyError as e: # by PWD raise lib.UserExceptions.General(f"PWD can't find {self.user}: {e}") return True @@ -201,7 +202,7 @@ class System: rt = subprocess.call(cc) if rt != 0: raise lib.UserExceptions.UnknownReturnCode( - f"Could not add user '{self.user}' to group '{group}' with command '{cc}', returned '{rt}'",) + f"Could not add user '{self.user}' to group '{group}' with command '{cc}', returned '{rt}'", ) return True @staticmethod @@ -242,7 +243,7 @@ class System: if ret.returncode != 0 and ret.returncode != 6: # userdel returns 6 when no mail dir was found but success raise lib.UserExceptions.UnknownReturnCode( f"Could not delete user with command {cc}. Return code: {ret.returncode}," - f" stdout/stderr: {stdio+err_io}") + f" stdout/stderr: {stdio + err_io}") return True diff --git a/private/lib/UserExceptions.py b/private/lib/UserExceptions.py index a3e4637..b41a699 100644 --- a/private/lib/UserExceptions.py +++ b/private/lib/UserExceptions.py @@ -2,15 +2,21 @@ class General(Exception): pass -class UnknownUser(General): +class User(General): pass -class UnknownReturnCode(General): - pass +class UnknownUser(User): + def __init__(self, name): + Exception.__init__(self, f"Tried to perform action on unknown user '{name}'") + + +class UserExistsAlready(User): + def __init__(self, name): + Exception.__init__(self, f"User '{name}' is already registered") -class UserExistsAlready(UnknownReturnCode): +class UnknownReturnCode(General): pass @@ -26,10 +32,6 @@ class SQLiteDatabaseDoesntExistYet(General): pass -class User(Exception): - pass - - class UsernameLength(User): pass diff --git a/private/lib/Validator.py b/private/lib/Validator.py index 967ce47..154e7d8 100644 --- a/private/lib/Validator.py +++ b/private/lib/Validator.py @@ -1,7 +1,8 @@ -import re +import csv import pwd +import re + import lib.sqlitedb -import csv def checkUsernameCharacters(username: str) -> bool: @@ -54,9 +55,8 @@ def checkUserExists(username: str) -> bool: try: pwd.getpwnam(username) except KeyError: - return True # User already exists - else: return False + return True # User already exists def checkUserInDB(username: str, db: str) -> bool: @@ -203,7 +203,7 @@ def checkImportFile(path: str, db: str): if not lib.Validator.checkEmail(row["email"]): errstr += f"Line {ln}: E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n" valid = False - if not lib.Validator.checkUserExists(row["username"]) or checkUserInDB(row["username"], db): + if lib.Validator.checkUserExists(row["username"]) or checkUserInDB(row["username"], db): errstr += f"Line {ln}: User '{row['username']}' already exists.\n" valid = False if not lib.Validator.checkDatetimeFormat(row["timestamp"]): From df1d09a155a417727c996711258038c728c9fa51 Mon Sep 17 00:00:00 2001 From: Darksider3 Date: Fri, 25 Oct 2019 10:19:54 +0200 Subject: [PATCH 79/79] Forgot that message. x_X --- private/lib/System.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/private/lib/System.py b/private/lib/System.py index 58aec01..1929909 100644 --- a/private/lib/System.py +++ b/private/lib/System.py @@ -85,7 +85,7 @@ class System: elif not self.dry: rt = subprocess.call(cc) if rt != 0: - raise lib.UserExceptions.UserExistsAlready(f"User {self.user} exists already") + raise lib.UserExceptions.UserExistsAlready(self.user) return True def unregister(self) -> bool: