From e5aae6bdc333b6435f612fc37b34198fd9df6fcf Mon Sep 17 00:00:00 2001 From: n1trux Date: Thu, 15 Aug 2019 14:06:07 +0200 Subject: [PATCH] initial commit --- admin/administrate.py | 384 +++++++++++++++++++++++++++++++++ application/userapplication.py | 164 ++++++++++++++ 2 files changed, 548 insertions(+) create mode 100755 admin/administrate.py create mode 100755 application/userapplication.py diff --git a/admin/administrate.py b/admin/administrate.py new file mode 100755 index 0000000..3469bbd --- /dev/null +++ b/admin/administrate.py @@ -0,0 +1,384 @@ +#!/usr/bin/env python3 + +import configparser, logging, sqlite3, argparse, pwd +import os +import subprocess + +# Clear shell +def clear(): + os.system('cls' if os.name == 'nt' else 'clear') + +# 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 +# prints command(but doesnt execute them) +# need this for work, just convenience +def debugExec(commands): + print("Commands: {!s} -> Returns 0".format(commands)) + return 0 + + +# @TODO hardcoded config? +cwd = os.getcwd() + "/applicationsconfig.ini" +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'] + +# Does everything related to applicants, i.e. creating, manipulations... +class applicants(): + # User identifier + identifier = "username" + # SQLite DB Path + sourceDB = "" + # another sqlite to batch-recreate users + differentDB = "" + def __init__(self, lident, sourceDB=REG_FILE): + self.identifier = lident + self.sourceDB = sourceDB + self.__connectToDB__("source") + # all results shall be done with dict_factory! Makes everything so much simpler + self.sdbCursor.row_factory = dict_factory + + def __del__(self): + self.__closeDB__("source") + + def __connectToDB__(self, which): + if which == "source": + try: + self.sdbConnection = sqlite3.connect(self.sourceDB) + self.sdbCursor = self.sdbConnection.cursor() + except sqlite3.Error as e: + logging.exception("Database: Couldn't open database and get cursor: %s" % e) + else: + self.ddbConnection = sqlite3.connect(self.differentDB) + self.ddbCursor = self.ddbConnection.cursor() + + def __closeDB__(self, which): + if(which == "source"): + try: + self.sdbConnection.close() + except sqlite3.Error as e: + logging.exception("Couldn't close database! Error: %s" % e) # @TODO: Dump full db with query or just the executed querys to file + else: + self.ddbConnection.close() # @TODO: Evaluate getting rid of ddb(differentDB)? + + # get List of all applications(not accepted yet) + def getApplicationsList(self): + query = "SELECT * FROM `applications` WHERE `status` = '0'" + try: + self.sdbCursor.execute(query) + rows = self.sdbCursor.fetchall() + except sqlite3.Error as e: + logging.exception("Database Error: %s" % e) + rows=[] + return rows + + def getApprovedApplicantsList(self): + query = "SELECT * From `applications` WHERE `status` = '1'" + try: + self.sdbCursor.execute(query) + rows = self.sdbCursor.fetchall() + except sqlite3.Error as e: + logging.exception("Database Error: %s" % e) + rows=[] + return rows + + # edit aproved users + def editApprovedApplicant(self, term, updaterow): + try: + self.sdbCursor.execute( + "UPDATE `applications` SET ? WHERE id=?", + ( str(term), ) + ) + self.sdbConnection.commit() + except sqlite3.Error as e: + logging.exception("Database Error: %s" % e) + + # set user to aproved + def setApprovedApplication(self, selectterm): + query = "SELECT `username` FROM `applications` WHERE `username` = `{0!s}`".format(selectterm) + + # get applicants data + def getApplicantsData(self, term): + # @TODO: Use shorthand if for the correct query, directly into sqlite + if self.identifier == "id": + try: + self.sdbCursor.execute( + "SELECT * FROM `applications` WHERE id = ?", + ( str(term), ) + ) + except sqlite3.Error as e: + logging.exception("Database Error: %s" % e) + + else: + self.sdbCursor.execute( + "SELECT * FROM `applications` WHERE username = ?", + ( str(term), ) + ) + result = self.sdbCursor.fetchone() + return result + + # @TODO: migrade just approved users to some new/another sqlitedb + def migrateApprovedData(self, different_db): + pass + + # @TODO: delete migrated data + def deleteMigratedDataSet(self, selectterm): + pass + + # Applicants whom doesnt got approved should get removed + def removeApplicant(self, term): + if self.identifier == "id": + try: + self.sdbCursor.execute( + "DELETE FROM `applications` WHERE id = ?", + ( str(term), ) + ) + self.sdbConnection.commit() + except sqlite3.Error as e: + logging.exception("Database Error: %s" % e) + + else: + self.sdbCursor.execute( + "DELETE FROM `applications` WHERE username = ?", + ( str(term), ) + ) + self.sdbConnection.commit() + + #@TODO: Possibility to work without passing users manually + def selectedUser(userid, username = False): + pass + + # Print out a list of aprovable users + def printApprovableUsers(self, users): + i=0 + for user in users: + print("ID: {0!s}, Status: {0!s}, Name: {0!s}".format(i, user["status"], user["username"])) + i += 1 + return i + + # Get List of users + def userPrint(self, fetched, userid): + print("ID: {0!s}".format(fetched[int(userid)]["id"])) + print("Username: {0!s}".format(fetched[int(userid)]["username"])) + print("Mail: {0!s}".format(fetched[int(userid)]["email"])) + print("SSH: {0!s}".format(fetched[int(userid)]["pubkey"])) + print("Registrated time: {0!s}".format(fetched[int(userid)]["timestamp"])) + + # Approve an applicant. Handles everything related, like create home dir, set flags blabla + def approveApplicant(self, term): + user = self.getApplicantsData(term) + ret = self.__execScript(user) + if ret[0] != 0: # @DEBUG: Change to == 0 + print("Something went wrong in the user creation! Exiting without deleting users record in database!") + print("Last executed commands: {0!s}\nreturn code: {1!s}".format(ret[-1][1], ret[-1][0])) + exit(0) + + if self.identifier == "id": + try: + self.sdbCursor.execute( + "UPDATE `applications` SET `status`=1 WHERE `id`=?", + ( str(term), ) + ) + self.sdbConnection.commit() + except sqlite3.Error as e: + logging.exception("Database Error: %s" % e) + + else: + self.sdbCursor.execute( + "UPDATE `applications` SET `status`=1 WHERE `username`=?" + ( str(term), ) + ) + self.sdbConnection.commit() + + # Script execution, handles everything done with the shell/commands themselves + def __execScript(self, user): + # @TODO: omfg just write some wrapper-class/lib... sucks hard! + username=user["username"] + homeDir="/home/"+username+"/" + sshDir=homeDir+".ssh/" + executed=[] + + executed.append(["useradd", "-m", username]) + rcode = subprocess.call(executed[0]) + if rcode != 0: + return [rcode,executed,] + + executed.append(["usermod", "--lock", username]) + rcode = subprocess.call(executed[1]) #empty pw + if rcode != 0: + return [rcode,executed,] + + executed.append(["usermod", "-a", "-G", "tilde", username]) + rcode = subprocess.call(executed[2]) # add to usergroup + if rcode != 0: + return [rcode,executed,] + + executed.append(["mkdir", sshDir]) + try: + # @TODO: use config variable(chmodPerms) + ret = os.mkdir(sshDir, 0o777) #create sshdir + rcode = 0 + except OSError as e: + logging.exception(e.strerror) + rcode = e.errno # False, couldn't create. + return [rcode,executed,] + + executed.append(["write(sshkey) to", sshDir+"authorized_keys"]) + with open(sshDir+"authorized_keys", "w") as f: + f.write(user["pubkey"]) + if f.closed != True: + logging.exception("Could'nt write to authorized_keys!") + return [rcode,executed,] + + executed.append(["chmod", "-Rv", "700", sshDir]) + + try: + os.chmod(sshDir+"authorized_keys", 0o700) # directory is already 700 + rcode = 0 + except OSError as e: + logging.exception(e.strerror) + rcode = e.errno + return [rcode, executed,] + + + try: + executed.append(["chown", "-Rv", username+":"+username, sshDir]) + os.chown(sshDir, pwd.getpwnam(username)[2], pwd.getpwnam(username)[3]) #2=>uid, 3=>gid + executed.append(["chown", "-v", username+":"+username, sshDir+"authorized_keys"]) + os.chown(sshDir+"authorized_keys", pwd.getpwnam(username)[2], pwd.getpwnam(username)[3]) + rcode = 0 + except OSError as e: + logging.exception(e.strerror) # @TODO: maybe append strerror to executed instead of printing it + rcode = e.errno + return [rcode, executed,] + + return [rcode,executed,] + """ +{'id': 7, 'username': 'testuser47', 'email': '47test@testmail.com', 'name': +'test Name', 'pubkey': 'ssh-rsa [...]', 'timestamp': '2018-08-22 13:31:16', 'status': 0} + + """ + + +def main(): + # how many times the Seperator/Delimiter? + delcount = 40 + # The seperator for the menu + Seperator = "="*delcount + Menu = Seperator+"\n\t\t Main-Menu:\n\n" \ + "\t 1) list and edit pending users\n"\ + "\t 2) list applicants\n"\ + "\t 3) edit applicant\n"\ + "\t 4) quit\n"+Seperator+"\n" + + # Identify by ID + applications = applicants(lident = "id") + while 1 != 0: + print(Menu) + + command = input("Please select, what you want to do: \n -> ") + # User shouldnt be able to type something in that isnt a number + if command.isalpha() or command == '': + clear() + print("!!! invalid input, please try again. !!!") + continue + + # convert + command=int(command) + + if command == 4 or command == "q": + exit(0) + # Edit and list pending users/applicants @TODO Wording: Users or applicants? + elif command == 1: + users = applications.getApplicationsList() + i=applications.printApprovableUsers(users) + + if i == 0 : + print("No pending users") + # giving some time to aknowledge that something WRONG happened + input("Continue with Keypress...") + clear() + continue + + usersel = 0 + UserMax = i + print("Menu:\n r=>return to main") + + # Edit Menue + while 1 != 0 or usersel != "r": + i = applications.printApprovableUsers(users) + if usersel == "r": + break # break when user presses r + + usersel = input("Which user( ID ) do you want to change? ->") + if len(usersel) > 1 or usersel.isalpha(): + usersel = "" + # convert to int if input isnt an r + usersel = int(usersel) if usersel != '' and usersel != 'r' else 0 + if usersel > UserMax - 1: + print("User {0!s} doesn't exist!".format(usersel)) + continue + # Show the user his chosen user and ask what to do + applications.userPrint(users, usersel) + print("You chosed ID No. {0!s}, what do you like to do?".format(usersel)) + + chosenUser = usersel + usersel = "" + + # Finally down the edit menue! + while usersel != "e": + usersel = input("User: {0!s}\n \t\t(A)ctivate \n\t\t(R)emove \n\t\tR(e)turn\n -> ".format(chosenUser)) + if usersel == "A": + applications.approveApplicant(users[chosenUser]['id']) + print("User {0!s} has been successfully approved!".format(users[chosenUser]['username'])) + input("waiting for input...") + clear() + usersel="e" # remove for being able to continue editing? + continue + elif usersel == "R": + applications.removeApplicant(users[chosenUser]['id']) + print("User {0!s} successfully deleted!".format(user[chosenUser]['username'])) + input("waiting for input...") + clear() + continue + elif usersel == "e": + clear() + continue + + elif int(command) == 2: + users = applications.getApprovedApplicantsList() + + if users == []: + print("no activate users yet!") + i=0 + for user in users: + print("ID: {0!s}, Status: {1!s}, Name: {2!s}".format(user["id"], user["status"], user["username"])) + continue + elif command == str(3): + pass + else: + exit(0) + +if __name__ == "__main__": + try: + main() + exit(0) + except KeyboardInterrupt: + pass + #print("Exception occured. View log file for details.") + #logging.exception("Some exception occured") diff --git a/application/userapplication.py b/application/userapplication.py new file mode 100755 index 0000000..24c60a6 --- /dev/null +++ b/application/userapplication.py @@ -0,0 +1,164 @@ +#!/usr/bin/env python3 + +import re, configparser, logging, sqlite3, argparse +from os import getcwd + +import re, configparser, logging, sqlite3 + + +try: + cwd=getcwd()+"/applicationsconfig.ini" + 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: + logging.exception("Argumentparser-Exception: ") + +try: + config = configparser.ConfigParser() + config.read(CONF_FILE) + logging.basicConfig(format="%(asctime)s: %(message)s", filename=config['DEFAULT']['log_file'],level=int(config['LOG_LEVEL']['log_level'])) + del(cwd) + REG_FILE=config['DEFAULT']['applications_db'] +except: + logging.exception("logging or configparser-Exception: ") + +VALID_SSH=False +VALID_USER=False + + +def __createTable(cursor, connection): + try: + 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, status INTEGER NOT NULL DEFAULT 0);") + connection.commit() + except: + logging.exception("Couldn't create needed SQLite Table!") + +def addtotable(cursor, connection, username, name, email, pubkey): + try: + cursor.execute("INSERT INTO 'applications'(username, name, email, pubkey)VALUES("\ + "?,?,?,?)", [username, name, email, pubkey]) + connection.commit() + except: + logging.exception("Couldn't insert user into the db") + +# check if sqlite file does exists or already and has our structure +def __checkSQLite(cursor, connection): + #SELECT name FROM sqlite_master WHERE type='table' AND name='{table_name}'; + cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='applications'") + res=cursor.fetchall() + + if res== []: + try: + __createTable(cursor, connection) + except: + logging.exception("couldn't create table on given database. Exception: ") + else: + pass + return True + +def check_username(value): + global VALID_USER + if len(value) < 3: + VALID_USER=False + return False + try: + from pwd import getpwnam + getpwnam(value) + VALID_USER=False + except: + VALID_USER=True + return True + return False + + +# taken from https://github.com/hashbang/provisor/blob/master/provisor/utils.py, all belongs to them! ;) +def validate_pubkey(value): + global VALID_SSH + import base64 + if len(value) > 8192 or len(value) < 80: + 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' ] + if value[0] not in types: + VALID_SSH=False + return False + + try: + base64.decodebytes(bytes(value[1], "utf-8")) + except TypeError: + VALID_SSH=False + return False + + 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") + 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." \ + "\nWhat is your desired username? [a-z0-9] allowed:\n") + + + fullname = input("\nPlease enter your full name:\n") + while not re.match("\w+\s*\w*", username): + fullname = input("\nThat is not your real name.\nPlease enter your full name:\n") + + + email = input("\nPlease enter your email address:\n") + while not re.match("(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)", email): + email = input("\nThat is not a valid mail address.\nPlease enter your email address:\n") + + + pubkey = input("\nPlease paste your ssh public key:\n") + while (not re.match("ssh-(\w)+\s(\w+)(\s*)([a-zA-Z0-9@]*)", pubkey)) or (not validate_pubkey(pubkey)): + pubkey = input("\nPlease enter a valid public key. You can show it with ssh-keygen -f .ssh/id_rsa -y on your local machine.\nPlease enter your pubkey:\n") + validate_pubkey(pubkey) + + + print("\nUsername: {0!s}".format(username)) + print("Full Name: {0!s}".format(fullname)) + print("Email: {0!s}".format(email)) + print("Public {0!s}".format(pubkey)) + + + validation = input("\nIs this information correct? [y/N]") + while not re.match("[yYnN\n]", validation): + print("Please answer y for yes or n for no") + validation = input("Is this information correct? [y/N]") + 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() + __checkSQLite(cursor, connection) + addtotable(cursor, connection, username, fullname, email, pubkey) + connection.commit() + connection.close() + except: + logging.exception("Database {0!s} couldnt be accessed or created. Exception:".format(config['DEFAULT']['applications_db'])) + connection.close() + exit(1) + pass + return 0 + +if __name__ == "__main__": + try: + main() + exit(0) + except KeyboardInterrupt: + pass