diff --git a/crawler.py b/crawler.py index 79de786..ec6ea6a 100644 --- a/crawler.py +++ b/crawler.py @@ -10,7 +10,7 @@ class FileParser: pass -class CheckSumCalculator: +class FileCheckSumCalculator: def checksum(self, path: str) -> str: """ @@ -21,7 +21,7 @@ class CheckSumCalculator: raise NotImplementedError() -class Md5CheckSumCalculator(CheckSumCalculator): +class Md5CheckSumCalculator(FileCheckSumCalculator): def __init__(self): self.name = "md5" @@ -41,7 +41,7 @@ class Md5CheckSumCalculator(CheckSumCalculator): return result.hexdigest().upper() -class Sha1CheckSumCalculator(CheckSumCalculator): +class Sha1CheckSumCalculator(FileCheckSumCalculator): def __init__(self): self.name = "sha1" @@ -61,7 +61,7 @@ class Sha1CheckSumCalculator(CheckSumCalculator): return result.hexdigest().upper() -class Sha256CheckSumCalculator(CheckSumCalculator): +class Sha256CheckSumCalculator(FileCheckSumCalculator): def __init__(self): self.name = "sha256" diff --git a/database.sql b/database.sql index df72374..d186ed6 100644 --- a/database.sql +++ b/database.sql @@ -35,6 +35,8 @@ CREATE TABLE User ( CREATE TABLE User_canRead_Directory ( username TEXT, directory_id INTEGER, - PRIMARY KEY (username, directory_id) + PRIMARY KEY (username, directory_id), + FOREIGN KEY (username) REFERENCES User(username), + FOREIGN KEY (directory_id) REFERENCES Directory(id) ) diff --git a/requirements.txt b/requirements.txt index 5b84407..a7478f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ PIL simplejson +flask +flask_bcrypt \ No newline at end of file diff --git a/setupDb.sh b/setupDb.sh deleted file mode 100755 index 15b8e89..0000000 --- a/setupDb.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash - -rm test.db -sqlite3 local_storage.db -init "database.sql" \ No newline at end of file diff --git a/spec/LocalStorage_spec.py b/spec/LocalStorage_spec.py index 56063d6..81e8bef 100644 --- a/spec/LocalStorage_spec.py +++ b/spec/LocalStorage_spec.py @@ -1,18 +1,17 @@ from unittest import TestCase - -from storage import LocalStorage, Directory, DuplicateDirectoryException +from storage import LocalStorage, Directory, DuplicateDirectoryException, User class LocalStorageTest(TestCase): def setUp(self): - s = LocalStorage() + s = LocalStorage("test_database.db") s.init_db("../database.sql") def test_save_and_retrieve_dir(self): - storage = LocalStorage() + storage = LocalStorage("test_database.db") d = Directory("/some/directory", True, ["opt1", "opt2", "opt3"]) @@ -23,19 +22,19 @@ class LocalStorageTest(TestCase): def test_save_and_retrieve_dir_persistent(self): - s1 = LocalStorage() + s1 = LocalStorage("test_database.db") d = Directory("/some/directory", True, ["opt1", "opt2", "opt3"]) s1.save_directory(d) - s2 = LocalStorage() + s2 = LocalStorage("test_database.db") self.assertEqual(s2.dirs()["/some/directory"].enabled, True) self.assertEqual(s2.dirs()["/some/directory"].options[0], "opt1") def test_reject_duplicate_path(self): - s = LocalStorage() + s = LocalStorage("test_database.db") d1 = Directory("/some/directory", True, ["opt1", "opt2"]) d2 = Directory("/some/directory", True, ["opt1", "opt2"]) @@ -45,3 +44,34 @@ class LocalStorageTest(TestCase): with self.assertRaises(DuplicateDirectoryException) as e: s.save_directory(d2) + def test_save_and_retrieve_user(self): + + s = LocalStorage("test_database.db") + + u = User("bob", "anHashedPassword", True) + + s.save_user(u) + + self.assertEqual(s.users()["bob"].username, "bob") + self.assertEqual(s.users()["bob"].admin, True) + + def test_return_none_with_unknown_user(self): + + s = LocalStorage("test_database.db") + + with self.assertRaises(KeyError) as e: + _ = s.users()["unknown_user"] + + def test_auth_user(self): + + s = LocalStorage("test_database.db") + + u = User("bob", b'$2b$14$VZEMbwAdy/HvLL/zh0.Iv.8XYnoZMz/LU9V4VKXLiuS.pthcUly2O', True) + + s.save_user(u) + + self.assertTrue(s.auth_user("bob", "test")) + self.assertFalse(s.auth_user("bob", "wrong")) + self.assertFalse(s.auth_user("wrong", "test")) + + pass diff --git a/storage.py b/storage.py index be09824..7df7ded 100644 --- a/storage.py +++ b/storage.py @@ -1,11 +1,34 @@ import sqlite3 import os +import flask_bcrypt + + +class CheckSumCalculator: + + def checksum(self, string: str): + + return flask_bcrypt.generate_password_hash(string, 14) # todo load from config class DuplicateDirectoryException(Exception): pass +class DuplicateUsernameException(Exception): + pass + + +class User: + """ + Data structure to hold user information + """ + + def __init__(self, username: str, hashed_password: bytes, admin: bool): + self.username = username + self.hashed_password = hashed_password + self.admin = admin + + class Directory: """ Data structure to hold directory information @@ -25,22 +48,20 @@ class LocalStorage: Could be refactored into a abstract class to switch from SQLite3 to something else """ - cache_outdated = True - """Static variable that indicates that the database was changed since the last time it was cached in memory""" - - db_path = "../local_storage.db" - - def __init__(self): + def __init__(self, db_path): self.cached_dirs = {} + self.cached_users = {} + self.db_path = db_path + self.dir_cache_outdated = True # Indicates that the database was changed since it was cached in memory + self.user_cache_outdated = True pass - @staticmethod - def init_db(script_path): + def init_db(self, script_path): """Creates a blank database. Overwrites the old one""" - if os.path.isfile(LocalStorage.db_path): - os.remove(LocalStorage.db_path) + if os.path.isfile(self.db_path): + os.remove(self.db_path) - conn = sqlite3.connect(LocalStorage.db_path) + conn = sqlite3.connect(self.db_path) c = conn.cursor() with open(script_path, "r") as f: c.executescript(f.read()) @@ -56,9 +77,9 @@ class LocalStorage: :return: None """ - LocalStorage.cache_outdated = True + self.dir_cache_outdated = True - conn = sqlite3.connect(LocalStorage.db_path) + conn = sqlite3.connect(self.db_path) c = conn.cursor() c.execute("PRAGMA FOREIGN_KEYS = ON;") try: @@ -79,11 +100,11 @@ class LocalStorage: def dirs(self): - if LocalStorage.cache_outdated: + if self.dir_cache_outdated: self.cached_dirs = {} - conn = sqlite3.connect(LocalStorage.db_path) + conn = sqlite3.connect(self.db_path) c = conn.cursor() c.execute("SELECT id, path, enabled FROM Directory") db_directories = c.fetchall() @@ -100,8 +121,63 @@ class LocalStorage: options.append(db_opt[0]) self.cached_dirs[directory.path] = directory - LocalStorage.cache_outdated = False + self.dir_cache_outdated = False return self.cached_dirs else: return self.cached_dirs + + def save_user(self, user: User): + """Save user to storage""" + + self.user_cache_outdated = True + + try: + conn = sqlite3.connect(self.db_path) + c = conn.cursor() + c.execute("PRAGMA FOREIGN_KEYS = ON;") + c.execute("INSERT INTO User (username, password, is_admin) VALUES (?,?,?)", + (user.username, user.hashed_password, user.admin)) + conn.commit() + conn.close() + except sqlite3.IntegrityError: + raise DuplicateDirectoryException("Duplicate username: " + user.username) + + def users(self): + """Get user list""" + + if self.user_cache_outdated: + + self.cached_users = {} + + conn = sqlite3.connect(self.db_path) + c = conn.cursor() + c.execute("SELECT username, is_admin FROM User") + + db_users = c.fetchall() + + for db_user in db_users: + self.cached_users[db_user[0]] = User(db_user[0], "", db_user[1]) + + conn.close() + + return self.cached_users + + else: + return self.cached_users + + def auth_user(self, username: str, password: str) -> bool: + """Authenticates an user""" + + conn = sqlite3.connect(self.db_path) + c = conn.cursor() + c.execute("SELECT username, password FROM User WHERE username=?", (username,)) + + db_user = c.fetchone() + + if db_user is not None: + return flask_bcrypt.check_password_hash(db_user[1], password) + + return False + + diff --git a/templates/layout.html b/templates/layout.html index 0e6687c..9383979 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -6,5 +6,8 @@ +{% block body %} +{% endblock body %} + \ No newline at end of file