Compare commits

...

15 Commits

Author SHA1 Message Date
Darksider3 f803b1acb4 testcfg: Add genRandomString, which automates away a few cases
... of actual value testing which i won't write manually then! :)

Also implements them on ListUsers
4 years ago
Darksider3 cabe14d41d tests-ListUsers: Remove useless printing to dev/null
Which again allows me to actually debug with print statements.
Dont know which dragon ride gave me the desillusional idea to actually
do it that way
4 years ago
Darksider3 c954940bec tests: Adds verificator-tests & lib: __init__ file
Also more strings to operate on for the tests, obviously.
4 years ago
Darksider3 b4d52b5376 tests: lib.ListUsers tests done
Even more coverage is on it's way. Have still to figure out in which
manner i'm going to test the things i want to.
It's actually not as easy as i expected...
4 years ago
Darksider3 c79edbfc84 tests: backup: Even more! Now with deletion of tested tmp!
Now we also test for everything in backup.py! EVERYTHING!
We even delete the old file that we ourselfs created to test the
export/backup function.
4 years ago
Darksider3 3a6a92267b tests: sample csv files for tests
Enables faster integration of tests.
4 years ago
Darksider3 f201ab603d tests: Add initial infrastructure!
Tests now can be done with `python -m test.test_backup --verbose`!
Im going to add more tests as coverage as soon as i've got time! :)
4 years ago
Darksider3 b8d4dac5da Allow test directories in gitignore 4 years ago
Darksider3 e2c2bcb0c2 import.py: Dont check for users existence while just sanitizing.
This ends the context of commit b4bb45ef85
and 108492db3f with an awesome little
detail, which provides SO MUCH value.
4 years ago
Darksider3 108492db3f private/lib/Validator.py: Further code quality enhancements
Namely:
* f => file_handle
* errstr => err_str
* intendantion adjustements.
* factor out uneccessary if-return-else-return-clause.

Also a new functionality to NOT check for users existence but everything
else. Needed to sanitize in case it's a backup of the current running
instance already. This further adds to commit
b4bb45ef85.
4 years ago
Darksider3 d2f99d95d0 sqlitedb.py: Refactor naming
safequery->safe_query, more snake_case all over the place enforced.
4 years ago
Darksider3 fb620447c7 config_ui: Always point out that the script can operate on the ENV 4 years ago
Darksider3 b4bb45ef85 Import.py: Sanitize given csv in case --Import isnt given and...
... fixup some variable names, also a `pass` that i forget to delete
beforehand.

Also, as you can see in the title, i added the ability to sanitize a
given csv file in case the user didnt supply the --Import flag. Thus, it
isnt required anymore.
4 years ago
Darksider3 aae7504e13 ListUsers.py: Externalize class & rename some variables
This actually makes the whole file easier to read and maintain. I've
still to refactor the output to something.
4 years ago
Darksider3 b5c11455d0 Backup.py -> backup.py, also some PEP conformity-changes
Which also includes two TODO-Tasks.
We had not proven ourselfs worthy of the holy-PEP-construct yet, so we
have to rename the whole file to backu.py, which follows snake_case, and
it's classes members.
They now embrace it as they should've done in the first place!

The dragon which we just encountered isnt as big as we thought and we
can still fight it with our as-easy-as-it-gets task to just snip out the
class and give it it's own residence under lib/backup.py-File!
4 years ago

3
.gitignore vendored

@ -1,4 +1,5 @@
__pycache__/ __pycache__/
.idea/ .idea/
test* test*
!test/
!test_*.py

@ -1,120 +1,41 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import configparser 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 import lib.uis.default as default_cmd # Follows -u, -a, -f flags
from lib.sqlitedb import SQLiteDB from lib.ListUsers import ListUsers
class ListUsers:
db = None
usersFetch = None
def __init__(self, db: str, unapproved: bool = False, approved: bool = True, single_user: str = None):
"""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'"
elif approved: # Approved users
query = "SELECT * FROM `applications` WHERE `status` = '1'"
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
: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)
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...
def get_fetch(self) -> List[sqlite3.Row]:
""" Returns a complete fetch done by the lib.sqlitedb-class
:return: Complete fetchall(). A List[sqlite3.Row] with dict-emulation objects.
:rtype: List[sqlite3.Row]
"""
return self.usersFetch
# @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]])
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"]
))
"""
if __name__ == "__main__": if __name__ == "__main__":
default_cmd.argparser.description += " - Lists Users from the Tilde database." default_cmd.argparser.description += " - Lists Users from the Tilde database."
default_cmd.argparser.add_argument('--list-asc', default=False, action="store_true", 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") help='Output a newline seperated list of users', required=False, dest="args_asc")
default_cmd.argparser.add_argument('--user', default=None, type=str, default_cmd.argparser.add_argument('--single_user', default=None, type=str,
help="Just show a specific user by it's name", required=False) help="Just show a specific single_user by it's name", required=False)
args = default_cmd.argparser.parse_args() args = default_cmd.argparser.parse_args()
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(args.config) config.read(args.config)
ret = "" OUTPUT = ""
if args.user is not None: if args.single_user is not None:
L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved, L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved,
single_user=args.user) single_user=args.single_user)
else: else:
L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved) L = ListUsers(config['DEFAULT']['applications_db'], unapproved=args.unapproved, approved=args.approved)
if args.args_asc: if args.args_asc:
ret = L.output_as_list() OUTPUT = L.output_as_list()
else: else:
fetch = L.get_fetch() users = L.get_fetch()
ret += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % ( OUTPUT += "ID %-1s| Username %-5s| Mail %-20s| Name %-17s| Registered %-8s | State |\n" % (
" ", " ", " ", " ", " " " ", " ", " ", " ", " "
) )
ret += 102 * "-" + "\n" OUTPUT += 102 * "-" + "\n"
for user in fetch: for single_user in users:
ret += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % ( OUTPUT += "%-4i| %-14s| %-25s| %-22s| %-8s | %-5i |\n" % (
user["id"], user["username"], user["email"], user["name"], user["timestamp"], user["status"] single_user["id"], single_user["username"], single_user["email"],
single_user["name"], single_user["timestamp"], single_user["status"]
) )
if args.file != "stdout": if args.file != "stdout":
with open(args.file, 'w') as f: with open(args.file, 'w') as f:
print(ret, file=f) print(OUTPUT, file=f)
else: else:
print(ret) print(OUTPUT)
exit(0) exit(0)

@ -1,20 +1,26 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
""" This module is thought to be the main point to export and import users.
It's actually not really a module but a script ought to be run from the command line
@TODO: Wording of module header...
"""
import configparser import configparser
import csv import csv
import io import io
import ListUsers from lib.ListUsers import ListUsers
import lib.uis.default as default_cmd # Follows -u, -a, -f flags import lib.uis.default as default_cmd # Follows -u, -a, -f flags
class Backup: class Backup:
"""Backups a Tilde database to an CSV file """Backups a Tilde database to an CSV file
@TODO: Move class into own file
:Example: :Example:
>>> from Backup import Backup >>> from backup import Backup
>>> from ListUsers import ListUsers >>> from ListUsers import ListUsers
>>> L = ListUsers.ListUsers("/path/to/sqlite").get_fetch() >>> L = ListUsers.list_users("/path/to/sqlite").get_fetch()
>>> backup_db = Backup("stdout") >>> backup_db = Backup("stdout")
>>> backup_db.backup_to_file(L) >>> backup_db.backup_to_file(L)
CSV-Separated list with headers in first row CSV-Separated list with headers in first row
@ -37,12 +43,13 @@ class Backup:
:type dialect: str :type dialect: str
""" """
self.setFilename(output) self.set_filename(output)
self.setQuoting(quoting) self.set_quoting(quoting)
self.setDialect(dialect) self.set_dialect(dialect)
self.setFieldnames(tuple(['id', 'username', 'email', 'name', 'pubkey', 'timestamp', 'status'])) self.set_field_names(tuple(['id', 'username', 'email', 'name',
'pubkey', 'timestamp', 'status']))
def setDialect(self, dialect: str) -> None: def set_dialect(self, dialect: str) -> None:
""" Set dialect for Object """ Set dialect for Object
:param dialect: Dialect to set for Object :param dialect: Dialect to set for Object
@ -53,7 +60,7 @@ class Backup:
self.dialect = dialect self.dialect = dialect
def setQuoting(self, quoting: int) -> None: def set_quoting(self, quoting: int) -> None:
""" Set quoting in the CSV(must be supported by the CSV Module!) """ Set quoting in the CSV(must be supported by the CSV Module!)
:param quoting: Quoting Integer given by csv.QUOTE_* constants :param quoting: Quoting Integer given by csv.QUOTE_* constants
@ -64,10 +71,10 @@ class Backup:
self.quoting = quoting self.quoting = quoting
def setFilename(self, filename: str) -> None: def set_filename(self, filename: str) -> None:
""" Sets Filename to output to """ Sets Filename to OUTPUT to
:param filename: Filename to output to(set stdout for stdout) :param filename: Filename to OUTPUT to(set stdout for stdout)
:type filename: str :type filename: str
:return: None :return: None
:rtype: None :rtype: None
@ -75,8 +82,8 @@ class Backup:
self.filename = filename self.filename = filename
def setFieldnames(self, f_names: tuple) -> None: def set_field_names(self, f_names: tuple) -> None:
""" Set fieldname to process """ Set field name to process
:param f_names: Fieldnames-Tuple :param f_names: Fieldnames-Tuple
:type f_names: tuple :type f_names: tuple
@ -95,20 +102,22 @@ class Backup:
""" """
returner = io.StringIO() returner = io.StringIO()
write_csv = csv.DictWriter(returner, fieldnames=self.field_names, quoting=self.quoting, dialect=self.dialect) write_csv = csv.DictWriter(returner, fieldnames=self.field_names,
quoting=self.quoting, dialect=self.dialect)
write_csv.writeheader() write_csv.writeheader()
for row in fetched: for row in fetched:
write_csv.writerow(dict(row)) write_csv.writerow(dict(row))
# sqlite3.Row doesn't "easily" convert to a dict itself sadly, so just a quick help from us here # sqlite3.Row doesn't "easily" convert to a dict itself sadly,
# it actually even delivers a list(sqlite3.Row) also, which doesnt make the life a whole lot easier # 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": if self.filename == "stdout":
print(returner.getvalue()) print(returner.getvalue())
return True
else: else:
with open(self.filename, "w") as f: with open(self.filename, "w") as f:
print(returner.getvalue(), file=f) print(returner.getvalue(), file=f)
return True return True
if __name__ == "__main__": if __name__ == "__main__":
@ -116,12 +125,12 @@ if __name__ == "__main__":
args = default_cmd.argparser.parse_args() args = default_cmd.argparser.parse_args()
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(args.config) config.read(args.config)
L = ListUsers.ListUsers(config['DEFAULT']['applications_db'], L = ListUsers(config['DEFAULT']['applications_db'],
unapproved=args.unapproved, approved=args.approved) unapproved=args.unapproved, approved=args.approved)
fetch = L.get_fetch() fetch = L.get_fetch()
if fetch: if fetch:
B = Backup(args.file) B = Backup(args.file)
B.setFieldnames(fetch[0].keys()) # sqlite3.row delivers its keys for us! SO NICE! B.set_field_names(fetch[0].keys()) # sqlite3.row delivers its keys for us! SO NICE!
B.backup_to_file(fetch) B.backup_to_file(fetch)
else: else:
print("nothing to backup!") print("nothing to backup!")

@ -50,7 +50,7 @@ if __name__ == "__main__":
if not DB: if not DB:
print("Could not establish connection to database") print("Could not establish connection to database")
exit(1) exit(1)
CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0] CurrentUser = DB.safe_query("SELECT * FROM `applications` WHERE `username`=?", tuple([args.user]))[0]
# --> --remove # --> --remove
if args.remove: if args.remove:
@ -75,9 +75,9 @@ if __name__ == "__main__":
print(f"Pubkey '{args.sshpubkey}' isn't valid.") print(f"Pubkey '{args.sshpubkey}' isn't valid.")
exit(1) exit(1)
try: try:
DB.safequery("UPDATE `applications` SET `pubkey`=? WHERE `username`=?", DB.safe_query("UPDATE `applications` SET `pubkey`=? WHERE `username`=?",
tuple([args.sshpubkey, args.user])) tuple([args.sshpubkey, args.user]))
CurrentUser = DB.safequery("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user]))[0] CurrentUser = DB.safe_query("SELECT * FROM `applications` WHERE `username` = ? ", tuple([args.user]))[0]
if int(CurrentUser["status"]) == 1: if int(CurrentUser["status"]) == 1:
sys_ctl.make_ssh_usable(args.sshpubkey) sys_ctl.make_ssh_usable(args.sshpubkey)
except sqlite3.Error as e: except sqlite3.Error as e:
@ -93,7 +93,7 @@ if __name__ == "__main__":
print(f"'{args.name}' is not a valid Name.") print(f"'{args.name}' is not a valid Name.")
exit(1) exit(1)
try: try:
DB.safequery("UPDATE `applications` SET `name` =? WHERE `username` =?", tuple([args.name, args.user])) DB.safe_query("UPDATE `applications` SET `name` =? WHERE `username` =?", tuple([args.name, args.user]))
except sqlite3.Error as e: except sqlite3.Error as e:
print(f"Could not 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}'.") print(f"'{args.user}'s Name changed to '{args.name}'.")
@ -104,7 +104,7 @@ if __name__ == "__main__":
print(f"'{args.email}' is not a valid Mail address!") print(f"'{args.email}' is not a valid Mail address!")
exit(1) exit(1)
try: try:
DB.safequery("UPDATE `applications` SET `email` =? WHERE `username` =?", tuple([args.email])) DB.safe_query("UPDATE `applications` SET `email` =? WHERE `username` =?", tuple([args.email]))
except sqlite3.Error as e: except sqlite3.Error as e:
print(f"Could not 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}'.") print(f"'{args.user}' Mail changed to '{args.email}'.")
@ -121,8 +121,8 @@ if __name__ == "__main__":
if args.status == 0 and int(CurrentUser["status"]) == 1: if args.status == 0 and int(CurrentUser["status"]) == 1:
try: try:
DB.safequery("UPDATE `applications` SET `status` =? WHERE `id`=?", DB.safe_query("UPDATE `applications` SET `status` =? WHERE `id`=?",
tuple([args.status, CurrentUser["id"]])) tuple([args.status, CurrentUser["id"]]))
sys_ctl.remove_user() sys_ctl.remove_user()
except sqlite3.Error as e: except sqlite3.Error as e:
print(f"Could not update database entry for '{args.user}', did not touch the system") print(f"Could not update database entry for '{args.user}', did not touch the system")
@ -134,8 +134,8 @@ if __name__ == "__main__":
if args.status == 1 and int(CurrentUser["status"]) == 0: if args.status == 1 and int(CurrentUser["status"]) == 0:
try: try:
DB.safequery("UPDATE `applications` SET `status`=? WHERE `username`=?", DB.safe_query("UPDATE `applications` SET `status`=? WHERE `username`=?",
tuple([args.status, args.user])) tuple([args.status, args.user]))
sys_ctl.aio_approve(CurrentUser["pubkey"]) sys_ctl.aio_approve(CurrentUser["pubkey"])
except sqlite3.Error as e: except sqlite3.Error as e:
print(f"Could not update Users status in database") print(f"Could not update Users status in database")

@ -1,14 +1,17 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""
Import users or sanitize backup files
"""
import configparser import configparser
import csv import csv
import os import os
import lib.UserExceptions import lib.UserExceptions
import lib.uis.config_ui # dont go to default, just following -c flag import lib.uis.config_ui # dont go to default, just following -c flag
import lib.Validator
def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bool: def import_from_file(file_path: str, database_file: str, user_ids: tuple = tuple([])) -> bool:
""" Imports Users from a given CSV-file to the system and DB """ Imports Users from a given CSV-file to the system and DB
:param file_path: :param file_path:
@ -23,22 +26,21 @@ def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bo
if not os.path.isfile(file_path): if not os.path.isfile(file_path):
print(f"File {file_path} don't exist") print(f"File {file_path} don't exist")
return False return False
if not os.path.isfile(db): if not os.path.isfile(database_file):
print(f"The database file {db} don't exist") print(f"The database file {database_file} don't exist")
return False return False
if user_ids: if user_ids:
pass # empty tuple means everything pass # empty tuple means everything
# noinspection PyBroadException # noinspection PyBroadException
try: try:
with open(file_path, 'r', newline='') as f: with open(file_path, 'r', newline='') as f:
import lib.Validator import lib.sqlitedb
sql = lib.sqlitedb.SQLiteDB(db) import lib.System
err = lib.Validator.checkImportFile(file_path, db) sql = lib.sqlitedb.SQLiteDB(database_file)
err = lib.Validator.checkImportFile(file_path, database_file)
if err is not True: if err is not True:
print(err) print(err)
exit(0) exit(0)
import lib.sqlitedb
import lib.System
sys_ctl = lib.System.System("root") sys_ctl = lib.System.System("root")
reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option reader = csv.DictReader(f) # @TODO csv.Sniffer to compare? When yes, give force-accept option
for row in reader: for row in reader:
@ -47,20 +49,19 @@ def import_from_file(file_path: str, db: str, user_ids: tuple = tuple([])) -> bo
sys_ctl.setUser(row["username"]) sys_ctl.setUser(row["username"])
sys_ctl.aio_approve(row["pubkey"]) sys_ctl.aio_approve(row["pubkey"])
print(row['username'], "====> Registered.") print(row['username'], "====> Registered.")
except lib.UserExceptions.General as GeneralExcept: except lib.UserExceptions.General as general_except:
print(f"Something didnt work out! {GeneralExcept}") print(f"Something didnt work out! {general_except}")
elif row["status"] == "0": elif row["status"] == "0":
print(row['username'] + " not approved, therefore not registered.") print(row['username'] + " not approved, therefore not registered.")
try: try:
sql.safequery( sql.safe_query(
"INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) " "INSERT INTO `applications` (username, name, timestamp, email, pubkey, status) "
"VALUES (?,?,?,?,?,?)", tuple([row["username"], row["name"], row["timestamp"], "VALUES (?,?,?,?,?,?)", tuple([row["username"], row["name"], row["timestamp"],
row["email"], row["pubkey"], row["status"]])) row["email"], row["pubkey"], row["status"]]))
except OSError as E: except OSError as os_exception:
pass print(f"UUFFF, something went WRONG with the file {file_path}: {os_exception}")
print(f"UUFFF, something went WRONG with the file {file_path}: {E}") except Exception as didnt_catch:
except Exception as didntCatch: print(f"Exception! UNCATCHED! {type(didnt_catch)}: {didnt_catch}")
print(f"Exception! UNCATCHED! {type(didntCatch)}: {didntCatch}")
return True return True
@ -71,17 +72,24 @@ if __name__ == "__main__":
ArgParser.add_argument('-f', '--file', default="stdout", ArgParser.add_argument('-f', '--file', default="stdout",
type=str, help='Import from CSV file', required=True) type=str, help='Import from CSV file', required=True)
ArgParser.add_argument('--Import', default=False, action="store_true", ArgParser.add_argument('--Import', default=False, action="store_true",
help="Import Users.", required=True) help="Import Users. If not set, just sanitize the supplied csv", required=False)
args = ArgParser.parse_args() args = ArgParser.parse_args()
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read(args.config) config.read(args.config)
if not args.Import:
print("Error, need the import flag")
if not args.file: if not args.file:
print("Error, need the import file") print("Error, need the import file")
if not args.file: if not args.Import:
print("You MUST set a CSV-file with the -f/--file flag that already exist") # we assume that you just want to sanitize the backup
if not os.path.isfile(args.file):
print(f"File {args.file} doesnt exist")
exit(1) exit(1)
import_from_file(args.file, config['DEFAULT']['applications_db']) sanitized = lib.Validator.checkImportFile(args.file, config['DEFAULT']['applications_db'], False)
if sanitized is not True:
print(sanitized)
else:
print(f"{args.file} is valid!")
exit(0)
elif args.Import:
import_from_file(args.file, config['DEFAULT']['applications_db'])
exit(0) exit(0)

@ -0,0 +1,65 @@
from lib.sqlitedb import SQLiteDB
import sqlite3 # sqlite3.Row-Object
from typing import List # Typing support!
class ListUsers:
"""
List tilde users
"""
db = None
users_fetch = None
def __init__(self, db: str, unapproved: bool = False, approved: bool = True, single_user: str = None):
"""Constructs list_users
: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'"
elif approved: # Approved users
query = "SELECT * FROM `applications` WHERE `status` = '1'"
else: # All users
query = "SELECT * FROM `applications`"
self.users_fetch = self.db.query(query)
if single_user is not None:
query = "SELECT * FROM `applications` WHERE `username` = ?"
self.users_fetch = self.db.safe_query(query, tuple([single_user]))
def output_as_list(self) -> str:
"""Generates a string with one (approved) single_user per line and one newline at the end
:rtype: str
:return: String consisting with one(activated) single_user per line
"""
list_str: str = ""
query = "SELECT `username` FROM `applications` WHERE `status` = '1' ORDER BY timestamp ASC"
self.users_fetch = self.db.query(query)
for user in self.users_fetch:
list_str += user["username"] + "\n"
return list_str
def pretty_print(self) -> None:
"""
pretty-print users
:return: None
"""
pass # see below why not implemented yet, texttable...
def get_fetch(self) -> List[sqlite3.Row]:
""" Returns a complete users done by the lib.sqlitedb-class
:return: Complete fetchall(). A List[sqlite3.Row] with dict-emulation objects.
:rtype: List[sqlite3.Row]
"""
return self.users_fetch

@ -72,7 +72,7 @@ def checkUserInDB(username: str, db: str) -> bool:
try: try:
ldb = lib.sqlitedb.SQLiteDB(db) ldb = lib.sqlitedb.SQLiteDB(db)
fetched = ldb.safequery("SELECT * FROM 'applications' WHERE username = ?", tuple([username])) fetched = ldb.safe_query("SELECT * FROM 'applications' WHERE username = ?", tuple([username]))
if fetched: if fetched:
return True return True
except lib.sqlitedb.sqlite3.Error as e: except lib.sqlitedb.sqlite3.Error as e:
@ -156,7 +156,7 @@ def checkName(name: str) -> bool:
return True return True
def checkImportFile(path: str, db: str): def checkImportFile(path: str, db: str, test_existence: bool = True):
""" Checks an CSV file against most of the validators and prints an Error message with the line number corresponding """ 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, to said failure.. Those includes: checkName, checkUsernameCharacters,
ckeckUsernameLength, duplicate usernames(in the CSV), checkSSHKey, checkEmail, checkUserExists, checkUserInDB, ckeckUsernameLength, duplicate usernames(in the CSV), checkSSHKey, checkEmail, checkUserExists, checkUserInDB,
@ -166,54 +166,64 @@ def checkImportFile(path: str, db: str):
:type path: str :type path: str
:param db: Path to database file(SQLite) :param db: Path to database file(SQLite)
:type db: str :type db: str
:param test_existence: Flag, checking users existence while true, won't when set to false. Default's to true.
:type test_existence: bool
:return: Str when Failure, True when success(All tests passed) :return: Str when Failure, True when success(All tests passed)
:rtype: Str or None :rtype: Str or None
""" """
errstr = "" err_str = ""
valid = True valid = True
ln = 1 # line number line = 1 # line number
valid_names_list = [] valid_names_list = []
with open(path, 'r', newline='') as f: with open(path, 'r', newline='') as file_handle:
reader = csv.DictReader(f) reader = csv.DictReader(file_handle)
for row in reader: for row in reader:
# if any of this fails move on to the next user, just print a relatively helpful message lel # 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" \ err_str += f"Line {line}: Name: '{row['name']}' seems not legit. " \
f" be correct.\n" f"Character followed by character should be correct.\n"
valid = False 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: '" err_str += (f"Line {line}: "
f"{row['username']}'.\n") f"Username contains unsupported characters or starts with a number: '"
f"{row['username']}'.\n")
valid = False 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" err_str += f"Line {line}: " \
f"Username '{row['username']}' is either too long(>16) or short(<3)\n"
valid = False valid = False
# dup checking # dup checking
if row["username"] in valid_names_list: if row["username"] in valid_names_list:
errstr += f"Line {ln}: Duplicate Username {row['username']}!\n" err_str += f"Line {line}: Duplicate Username {row['username']}!\n"
valid = False valid = False
else: else:
valid_names_list.append(row["username"]) valid_names_list.append(row["username"])
# dup end # 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: " \ err_str += f"Line {line}: " \
f"'{row['pubkey']}'.\n" f"Following SSH-Key of user '{row['username']}' isn't valid: " \
f"'{row['pubkey']}'.\n"
valid = False 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" err_str += \
f"Line {line}: " \
f"E-Mail address of user '{row['username']}' '{row['email']}' is not valid.\n"
valid = False valid = False
if 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" if test_existence:
valid = False err_str += f"Line {line}: User '{row['username']}' already exists.\n"
valid = False
else:
pass
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" err_str += f"Line {line}: Timestamp '{row['timestamp']}' " \
f"from user '{row['username']}' is invalid.\n"
valid = False valid = False
if int(row["status"]) > 1 or int(row["status"]) < 0: if int(row["status"]) > 1 or int(row["status"]) < 0:
errstr += f"Line {ln}: Status '{row['status']}' MUST be either 0 or 1.\n" err_str += f"Line {line}: Status '{row['status']}' MUST be either 0 or 1.\n"
valid = False valid = False
ln += 1 line += 1
if valid: if valid:
return True err_str = True
else: return err_str
return errstr

@ -1,4 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""
SQLite wrapper which does just some simple wraps, to ease our experience a little.
"""
import sqlite3 import sqlite3
from sys import stderr as stderr from sys import stderr as stderr
from typing import List # Typing support! from typing import List # Typing support!
@ -20,20 +24,18 @@ class SQLiteDB:
connection = None connection = None
last_result = None last_result = None
def __init__(self, dbpath: str): def __init__(self, db_path: str):
""" """
:param dbpath: Path to the database we want to open :param db_path: Path to the database we want to open
:type dbpath: str :type db_path: str
:returns: Object for the SQLitedb-Class. :returns: Object for the SQLitedb-Class.
:rtype: object :rtype: object
""" """
db = dbpath
try: try:
self.connection = sqlite3.connect(db) self.connection = sqlite3.connect(db_path)
self.cursor = self.connection.cursor() self.cursor = self.connection.cursor()
except sqlite3.Error as e: except sqlite3.Error as sql_con:
print("Connection error: %s" % e, file=stderr) print("Connection error: %s" % sql_con, file=stderr)
self.cursor.row_factory = sqlite3.Row # every result will be a dict now self.cursor.row_factory = sqlite3.Row # every result will be a dict now
@ -44,38 +46,39 @@ class SQLiteDB:
except sqlite3.Error as e: 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[sqlite3.Row]: def query(self, q_str: str) -> List[sqlite3.Row]:
"""Do a query and automagically get the fetched results in a list """Do a query and automagically get the fetched results in a list
:param qq: Query to execute :param q_str: Query to execute
:type qq: str :type q_str: str
:returns: A tuple(/list) consisting with any fetched results :returns: A tuple(/list) consisting with any fetched results
:rtype: list :rtype: list
""" """
try: try:
self.cursor.execute(qq) self.cursor.execute(q_str)
self.last_result = self.cursor.fetchall() self.last_result = self.cursor.fetchall()
self.connection.commit() self.connection.commit()
except sqlite3.OperationalError: except sqlite3.OperationalError:
self._createTable() self._createTable()
return self.query(qq) return self.query(q_str)
except sqlite3.Error as e: except sqlite3.Error as sql_query_except:
print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) print("Couldn't execute query %s, exception: %s" % (q_str, sql_query_except),
file=stderr)
self.last_result = [] self.last_result = []
return self.last_result return self.last_result
# sometimes we need the cursor for safety reasons, for example does sqlite3 all the security related # 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 # escaoing in supplied strings for us, when we deliver it to con.execute in the second argument as a tuple
def getCursor(self) -> sqlite3: def get_cursor(self) -> sqlite3:
"""Returns SQLite3 Cursor. Use with **c a u t i o n**... """ """Returns SQLite3 Cursor. Use with **c a u t i o n**... """
return self.cursor 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 # 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 # THE RIGHT TYPE
def safequery(self, qq: str, deliver: tuple) -> List[sqlite3.Row]: def safe_query(self, q_str: str, deliver: tuple) -> List[sqlite3.Row]:
""" Shall handle any query that has user input in it as an alternative to self.query """ Shall handle any query that has user input in it as an alternative to self.query
:param qq: Query to execute :param q_str: Query to execute
:type qq: str :type q_str: str
:param deliver: User inputs marked with the placeholder(`?`) in the str :param deliver: User inputs marked with the placeholder(`?`) in the str
:type deliver: tuple :type deliver: tuple
:returns: A tuple(/list) consisting with any fetched results :returns: A tuple(/list) consisting with any fetched results
@ -83,39 +86,40 @@ class SQLiteDB:
""" """
try: try:
self.cursor.execute(qq, deliver) self.cursor.execute(q_str, deliver)
self.last_result = self.cursor.fetchall() self.last_result = self.cursor.fetchall()
self.connection.commit() self.connection.commit()
except TypeError as e: except TypeError as type_err:
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" % (q_str, type_err), file=stderr)
self.last_result = [] self.last_result = []
except sqlite3.OperationalError: except sqlite3.OperationalError:
self._createTable() self._createTable()
return self.safequery(qq, deliver) return self.safe_query(q_str, deliver)
except sqlite3.Error as e: except sqlite3.Error as sql_query_error:
print("Couldn't execute query %s, exception: %s" % (qq, e), file=stderr) print("Couldn't execute query %s, exception: %s" % (q_str, sql_query_error), file=stderr)
print(deliver) print(deliver)
print(type(e)) print(type(sql_query_error))
self.last_result = [] self.last_result = []
return self.last_result return self.last_result
def removeApplicantFromDB(self, userid: int) -> bool: def removeApplicantFromDB(self, user_id: int) -> bool:
"""Removes Applicants from the DB by ID. Use along System.removeUser() """Removes Applicants from the DB by ID. Use along System.removeUser()
:param userid: User ID to remove from the Database :param user_id: User ID to remove from the Database
:type userid: int :type user_id: int
:returns: True, if removal was successful(from the DB), False when not :returns: True, if removal was successful(from the DB), False when not
:rtype: bool :rtype: bool
""" """
try: try:
self.last_result = self.cursor.execute("DELETE FROM `applications` WHERE id = ? ", [userid]) self.last_result = self.cursor.execute("DELETE FROM `applications` WHERE id = ? ",
[user_id])
self.connection.commit() self.connection.commit()
except sqlite3.OperationalError: except sqlite3.OperationalError:
print("The database has probably not yet seen any users, so it didnt create your table yet. Come back" 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") "when a user tried to register")
return False return False
except sqlite3.Error as e: except sqlite3.Error as query_error:
print(f"Could not delete user with id: {userid}, exception in DB: {e}") # @TODO LOGGING FFS print(f"Could not delete user with id: {user_id}, exception in DB: {query_error}") # @TODO LOGGING FFS
return False return False
return True return True
@ -134,8 +138,8 @@ class SQLiteDB:
print("The database has probably not yet seen any users, so it didnt create your table yet. Come back" 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") "when a user tried to register")
return False return False
except sqlite3.Error as e: except sqlite3.Error as sql_error:
print(f"Could not delete user {username}, exception in DB: {e}") # @TODO LOGGING print(f"Could not delete user {username}, exception in DB: {sql_error}") # @TODO LOGGING
return False return False
return True return True
@ -150,8 +154,8 @@ class SQLiteDB:
"timestamp_valid CHECK( timestamp IS strftime('%Y-%m-%d %H:%M:%S', timestamp))" "timestamp_valid CHECK( timestamp IS strftime('%Y-%m-%d %H:%M:%S', timestamp))"
",status INTEGER NOT NULL DEFAULT 0);") ",status INTEGER NOT NULL DEFAULT 0);")
self.connection.commit() self.connection.commit()
except sqlite3.Error as e: except sqlite3.Error as sql_error:
print(f"The database probably doesn't exist yet, but read the message: {e}") print(f"The database probably doesn't exist yet, but read the message: {sql_error}")
print("The database table didn't exist yet; created it successfully!") print("The database table didn't exist yet; created it successfully!")

@ -1,6 +1,9 @@
import argparse import argparse
import lib.cwd import lib.cwd
argparser = argparse.ArgumentParser(description='Tilde administration tools ', conflict_handler="resolve") argparser = argparse.ArgumentParser(description='Tilde administration tools ',
conflict_handler="resolve")
argparser.add_argument('-c', '--config', default=lib.cwd.cwd, argparser.add_argument('-c', '--config', default=lib.cwd.cwd,
type=str, help='Path to configuration file', required=False) type=str,
help='Path to configuration file. If not set, we look for it in $TILDE_CONF',
required=False)

Binary file not shown.

@ -0,0 +1,47 @@
import unittest
import test.testcfg as testcfg
import sys
import os
from lib.ListUsers import ListUsers
sys.path.append('..')
class TestListUsers(unittest.TestCase):
def setUp(self) -> None:
try:
self.list = ListUsers(testcfg.test_db, unapproved=False, approved=False)
except IOError as fs_err:
self.fail(fs_err)
def test_singleUser(self):
try:
ListUsers(testcfg.test_db, single_user=testcfg.assured_user)
except Exception:
self.fail()
def test_output_as_list(self):
# count newlines in string, everything under 3 is wrong, and above 20 too.
out = self.list.output_as_list().splitlines()
self.assertGreater(len(out), testcfg.ListUsers_output_newlines,
"Newlines in OUTPUT doesn't exceed minimum of at least "
f"{testcfg.ListUsers_output_newlines} "
"lines!")
def test_pretty_print(self):
# wont going to compare the stdout sorry very much
return
def test_get_fetch(self):
fetch = self.list.get_fetch()
self.assertIsInstance(fetch, list)
self.assertGreater(len(fetch),
testcfg.ListUsers_fetch_size_min, "fetch is NOT greater than"
"the configured fetch minimum")
try:
fetch[0]
except (KeyError, IOError) as suddenly_not_there:
self.fail(f"Expected fetch to have at least one argument! {suddenly_not_there}")
if __name__ == '__main__':
unittest.main()

@ -0,0 +1,76 @@
import sys
sys.path.append('..')
import unittest
import lib.Validator
import test.testcfg as testcfg
class TestValidator(unittest.TestCase):
def setUp(self) -> None:
self.validator = lib.Validator
def test_check_username_characters(self):
for name in testcfg.Validator_Valid_Users_Chars_List:
self.assertTrue(self.validator.checkUsernameCharacters(name))
for name in testcfg.Validator_Invalid_Users_Chars_List:
self.assertFalse(self.validator.checkUsernameCharacters(name), name)
for name in testcfg.genRandomString():
self.assertFalse(self.validator.checkUsernameCharacters(name), name)
def test_check_username_length(self):
for name in testcfg.Validator_Valid_Users_Length:
self.assertTrue(self.validator.checkUsernameLength(name))
for name in testcfg.Validator_Invalid_Users_Length:
self.assertFalse(self.validator.checkUsernameLength(name), name)
def test_check_email(self):
for name in testcfg.Validator_Valid_Mail:
self.assertTrue(self.validator.checkEmail(name))
for name in testcfg.Validator_Invalid_Mail:
self.assertFalse(self.validator.checkEmail(name), name)
for name in testcfg.genRandomString():
self.assertFalse(self.validator.checkEmail(name))
def test_check_user_exists(self):
self.assertTrue(self.validator.checkUserExists("root"))
self.assertTrue(self.validator.checkUserExists("nobody")) # remove in case there exists an actual system without
for User in testcfg.genRandomString():
self.assertFalse(self.validator.checkUserExists(User))
def test_check_user_in_db(self):
for name in testcfg.Validator_db_user_exists:
self.assertTrue(self.validator.checkUserInDB(name, testcfg.test_db))
for name in testcfg.Validator_db_user_inexistent:
self.assertFalse(self.validator.checkUserInDB(name, testcfg.test_db))
def test_check_sshkey(self):
for key in testcfg.Validator_valid_ssh:
self.assertTrue(self.validator.checkSSHKey(key))
for key in testcfg.Validator_invalid_ssh:
self.assertFalse(self.validator.checkSSHKey(key))
def test_check_datetime_format(self):
for cur in testcfg.Validator_valid_datetime:
self.assertTrue(self.validator.checkDatetimeFormat(cur))
for cur in testcfg.Validator_invalid_checkname_names:
self.assertFalse(self.validator.checkDatetimeFormat(cur))
for cur in testcfg.genRandomString():
self.assertFalse(self.validator.checkDatetimeFormat(cur))
def test_check_name(self):
for name in testcfg.Validator_valid_checkname_names:
self.assertTrue(self.validator.checkName(name))
for name in testcfg.Validator_invalid_checkname_names:
self.assertFalse(self.validator.checkName(name))
for name in testcfg.genRandomString(alphabet="\n\b\r\t\f"):
self.assertFalse(self.validator.checkName(name))
def test_check_import_file(self):
self.assertTrue(self.validator.checkImportFile(testcfg.test_import_csv,
testcfg.test_db))
if not self.validator.checkImportFile(testcfg.test_import_invalid_csv, testcfg.test_db):
self.fail("Invalid import file should've failed the test")

@ -0,0 +1,50 @@
import os
import unittest
import sys
import csv
import backup
sys.path.append('..')
from lib.ListUsers import ListUsers
import test.testcfg as testcfg
class TestBackup(unittest.TestCase):
def setUp(self):
try:
self.fetch = ListUsers(testcfg.test_db, unapproved=False, approved=False).get_fetch()
self.Backup = backup.Backup(testcfg.test_backup_csv)
except Exception as general_setup:
self.fail(f"Failed setup already! {general_setup}")
def test_set_dialect(self):
self.Backup.set_dialect("excel")
self.assertEqual(self.Backup.dialect, "excel")
def test_set_quoting(self):
self.Backup.set_quoting(csv.QUOTE_NONNUMERIC)
self.assertEqual(self.Backup.quoting, csv.QUOTE_NONNUMERIC)
def test_set_filename(self):
self.Backup.set_filename(testcfg.test_backup_csv)
self.assertEqual(self.Backup.filename, testcfg.test_backup_csv)
self.Backup.set_field_names(self.fetch[0].keys())
def test_set_field_names(self):
# @TODO: Dynamic! Having a test scheme from which we setup our test is beneficial here, also values
self.Backup.set_field_names(self.fetch[0].keys())
keys_found = self.Backup.field_names
self.assertEqual(keys_found, ['id', 'username', 'email', 'name', 'pubkey', 'timestamp', 'status'])
def test_backup_to_file(self):
try:
self.Backup.set_field_names(self.fetch[0].keys())
self.Backup.backup_to_file(self.fetch)
self.assertTrue(os.path.exists(testcfg.test_backup_csv),
"Assert True that file exists and was written")
os.unlink(os.path.realpath(testcfg.test_backup_csv))
except IOError as io_error:
self.fail(io_error)
if __name__ == '__main__':
unittest.main()

@ -0,0 +1,60 @@
test_random_iterations: int = 50
def genRandomString(iterations: int = test_random_iterations, size: int = 20, alphabet: str = ""):
import random, string
if alphabet == "":
letters = string.digits + string.printable + string.ascii_letters
else:
letters = alphabet
for i in range(iterations):
yield ''.join(random.choice(letters) for i in range(size)).capitalize()
assured_user: str = "darksider3"
test_backup_csv: str = "test/testbackup.csv"
test_db: str = "./test/applications.sqlite"
test_import_csv: str = "test/testimport.csv"
test_import_invalid_csv: str = "test/testimport_fail.csv"
ListUsers_fetch_size_min: int = 3
ListUsers_output_newlines: int = 1
Validator_Valid_Users_Chars_List: list = ["darksider", "dirty", "hAndS", "world312Lol"]
Validator_Invalid_Users_Chars_List: list = ["12", "#uß", "Rawr"]
Validator_Valid_Users_Length: list = ["w💁💁💞💞elt", "hallo", "japp", "eksdee", "harrharr", "räraRüdigerSauß하",
"التَّطْبِيقَاتُ",
"𝓽𝓱𝓮", "𝓵𝓪𝔃𝔂", "𝓭𝓸𝓰"]
Validator_Invalid_Users_Length: list = ["f", "i", "fa", "fo", "ar"]
Validator_Valid_Mail: list = ["larp@example.org", "rawr@lulz.com", "woerld@hassa.fun"]
Validator_Invalid_Mail: list = ["läÄ@wi", "@rawr.", ".com", "@.de", ".@.de"]
Validator_db_user_exists: list = ["darksider3", "Darksider2", "Darksider1"]
Validator_db_user_inexistent: list = ["welt", "world", "hä#", "root"]
# @TODO: More valid and invalid test keys...
Validator_valid_ssh: list = ["""ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7qmegDxzv1omqG2cWM+
i+qaEGzCoSBwqCeXyGUU93sTqtNYYHJVGj6YZqXeXEGzJtKm2A/uo59Y+WmqhJgW7HcT2Hqvo80NfbIRhqE9TJETyBe
GiiC8qpiYgPC2zigCNvTsRXh0CH5FJ1qy4QEBjztQDWOqSrsoOSJEEWCJiKJizTiXDmlGdiKE409GBo8lvlbMRWbrMj
3iX825WTqy/T0Pio1kqANDotLnPA0sRXUPVyzc/ghzqRHzFetzP9j7C0nh
EvjiJphiuYvhbgix79FrCQG0lXBGcAWzsWUeAoT/d3kQu79+UTWxm+z4pnJ7gkKVMejqrWys560SdAqD264dc5UBRGI9j6X
xVKdraSaEitDneONrSAt2tE/RwRxh2ASxqQfdF88zyDI8/ma608tHc
FROaNsn5hF+/wzjRK9akdhp5WjA5HXhg2OlkwKvSMhGlSgotRj5pr4Ebxjegysy1mEWRFN/vh/oNq4uHQy8adpfogaVELkI/Z2nuAdQk
+uMy6D1hrKhUWubmBPxTbG00IWF25Tyuz8hnFRP9+gB/P
NRlF59/EHy27a72nirvuOyfxKnx/Mn+FD9Ah59OSLhWuo3sN9Im8yc2cliecwMz+DmTtE7TwzNw9v2zfxU9JDQwyLtppULiGpmKFOLHjz
+SVGxSbVsWS//IyNK1GrQ== gschoenb@gschoenb-X220""",
"""ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAklOUpkDHrfHY17SbrmTIpNLTGK9Tjom/BWDSU
GPl+nafzlHDTYW7hdI4yZ5ew18JH4JW9jbhUFrviQzM7xlELEVf4h9lFX5QVkbPppSwg0cda3
Pbv7kOdJ/MTyBlWXFCR+HAo3FXRitBqxiX1nKhXpHAZsMciLq8V6RjsNAQwdsdMFvSlVK/7XA
t3FaoJoAsncM1Q9x5+3V0Ww68/eIFmb1zuUFljQJKprrX88XypNDvjYNby6vw/Pb0rwert/En
mZ+AW4OZPnTPI89ZPmVMLuayrD2cE86Z/il8b+gw3r3+1nKatmIkjn2so1d01QraTlMqVSsbx
NrRFi9wrf+M7Q== schacon@mylaptop.local"""]
Validator_invalid_ssh: list = ["lol.", "ssh-rsa lol."]
#Y-%m-%d %H:%M:%S
Validator_valid_datetime: list = ["2020-10-07 14:15:30", "2020-01-07 12:23:11"]
Validator_ivvalid_datetime: list = ["20201007 14:15:30", "2020-01-07 122311"]
Validator_valid_checkname_names: list = ["HalloWelt"]
Validator_invalid_checkname_names: list = ["\\n", "\n\b"]

@ -0,0 +1,30 @@
"id","username","email","name","pubkey","timestamp","status"
1,"darksider1","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 16:12:52",1
1,"darksider2","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 12:11:52",1
1,"darksider3","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 13:14:52",0
1,"darksider4","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 14:15:52",1
1,"darksider5","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 15:19:52",1
1,"darksider6","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 16:12:52",1
1,"darksider7","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 17:11:52",0
1,"darksider8","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 18:14:52",1
1,"darksider9","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 19:15:52",1
1,"darksider10","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 20:19:52",1
1,"darksider11","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 21:12:52",0
1,"darksider12","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 22:11:52",1
1,"darksider13","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 23:14:52",0
1,"darksider14","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 00:15:52",1
1,"darksider15","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 01:19:52",0
1,"darksider16","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 03:15:52",1
1,"darksider17","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 02:19:52",1
1,"darksider18","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 05:12:52",0
1,"darksider19","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 04:11:52",1
1,"darksider20","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 06:14:52",0
1,"darksider21","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 07:15:52",1
1,"darksider22","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 09:19:52",0
1,"darksider23","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 08:15:52",1
1,"darksider24","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 10:19:52",1
1,"darksider25","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 11:12:52",0
1,"darksider26","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 12:11:52",1
1,"darksider27","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 13:14:52",0
1,"darksider28","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 14:15:52",1
1,"darksider29","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 15:19:52",0
1 id username email name pubkey timestamp status
2 1 darksider1 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 16:12:52 1
3 1 darksider2 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 12:11:52 1
4 1 darksider3 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 13:14:52 0
5 1 darksider4 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 14:15:52 1
6 1 darksider5 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 15:19:52 1
7 1 darksider6 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 16:12:52 1
8 1 darksider7 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 17:11:52 0
9 1 darksider8 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 18:14:52 1
10 1 darksider9 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 19:15:52 1
11 1 darksider10 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 20:19:52 1
12 1 darksider11 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 21:12:52 0
13 1 darksider12 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 22:11:52 1
14 1 darksider13 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 23:14:52 0
15 1 darksider14 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 00:15:52 1
16 1 darksider15 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 01:19:52 0
17 1 darksider16 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 03:15:52 1
18 1 darksider17 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 02:19:52 1
19 1 darksider18 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 05:12:52 0
20 1 darksider19 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 04:11:52 1
21 1 darksider20 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 06:14:52 0
22 1 darksider21 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 07:15:52 1
23 1 darksider22 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 09:19:52 0
24 1 darksider23 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 08:15:52 1
25 1 darksider24 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 10:19:52 1
26 1 darksider25 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 11:12:52 0
27 1 darksider26 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 12:11:52 1
28 1 darksider27 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 13:14:52 0
29 1 darksider28 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 14:15:52 1
30 1 darksider29 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 15:19:52 0

@ -0,0 +1,14 @@
"id","username","email","name","pubkey","timestamp","status"
1,"darksider3","tester@testentry.de","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro","2019-10-12 11:19:52",6
2,"Darksider2","notapproved@tester.com","Unapproved Tester","Unknown","2019-10-12 11:20:34",6
3,"darksider4","penis@cocksucker.email","Luca","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro",12345,1
4,"darksider5","pissfotze@example.com","empty","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOf5ZbbXbTyVD1WN0XES3lPG9jXGoYxcKhXPBq5D3ZwG test@example-com","1970-00-00",0
7,"3kliksphilip","test@test",,"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIjb0JardY8xeB5YwIPn9Z0HSEPfy3wEIB90f/rEgy33 test@example.com","11.10.20 20:20",1
8,"root","mailtest@tester.testets.test","Tester sagtHallo","ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBiR1xpeeBuKALRi1czgVbyPbGW7Y6It72PRULFDhiFM drip@kvn.home.steinke.pro","10.12.23 13:37",0
6213663,"4kliksphilip",,"Testtest","test@example.com nokey","12.10.19 01:15",1
11,"name mitleerzeichen","test","Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.","Unknown","1970-00-00",-7
8,"$HOME","wurst",,"invalid",1,3
12,"loremipsumdolorsitametconsetetursadipisceoraetorbi","eierkopf",,";;--",2,4
16,"darksider3","penis@darksider3.de",,,1970,200
-1,"darksider17","haha@lol.nigge.rs",,,"penis",6
1 id username email name pubkey timestamp status
2 1 darksider3 tester@testentry.de Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 2019-10-12 11:19:52 6
3 2 Darksider2 notapproved@tester.com Unapproved Tester Unknown 2019-10-12 11:20:34 6
4 3 darksider4 penis@cocksucker.email Luca ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDFsOkf8NTRY++69dCMO8hJslNCWIJMhgHxwN4vSPUG2 drip@kvn.home.steinke.pro 12345 1
5 4 darksider5 pissfotze@example.com empty ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOf5ZbbXbTyVD1WN0XES3lPG9jXGoYxcKhXPBq5D3ZwG test@example-com 1970-00-00 0
6 7 3kliksphilip test@test ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIjb0JardY8xeB5YwIPn9Z0HSEPfy3wEIB90f/rEgy33 test@example.com 11.10.20 20:20 1
7 8 root mailtest@tester.testets.test Tester sagtHallo ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBiR1xpeeBuKALRi1czgVbyPbGW7Y6It72PRULFDhiFM drip@kvn.home.steinke.pro 10.12.23 13:37 0
8 6213663 4kliksphilip Testtest test@example.com nokey 12.10.19 01:15 1
9 11 name mitleerzeichen test Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. Unknown 1970-00-00 -7
10 8 $HOME wurst invalid 1 3
11 12 loremipsumdolorsitametconsetetursadipisceoraetorbi eierkopf ;;-- 2 4
12 16 darksider3 penis@darksider3.de 1970 200
13 -1 darksider17 haha@lol.nigge.rs penis 6
Loading…
Cancel
Save