Переглянути джерело

Continue working on project.

Cixo Develop 10 місяців тому
батько
коміт
3f25b9d3a0

+ 2 - 1
assets/__init__.py

@@ -2,4 +2,5 @@ from .user import user
 from .users_manager import users_manager 
 from .user_validator import user_validator
 from .secret_generator import secret_generator
-
+from .database_model import database_model 
+from .user_table import user_table

+ 23 - 0
assets/adapter.py

@@ -0,0 +1,23 @@
+class adapter:
+    def __init__(self, target):
+        if target is None:
+            raise Exception("Adapter target must not bo None.")
+
+        self.__result = None
+        self.__target = target
+
+    @property
+    def target(self):
+        return self.__target
+    
+    @property
+    def result(self):
+        if self.__result is None:
+            self.__result = self.__class__.convert(self.target)
+            
+            return self.__result
+
+        return self.__result
+
+    def convert(target):
+        raise Exception("This function must be overwriten.")

+ 23 - 0
assets/database_model.py

@@ -0,0 +1,23 @@
+class database_model:
+    def __init__(self, id: int | None = None):
+        self.__id = id
+
+    @property
+    def id(self) -> int | None:
+        return self.__id
+
+    def exists(self) -> bool:
+        return self.__id != None
+
+    @id.setter
+    def id(self, target: int) -> None:
+        if self.exists():
+            raise Exception("Database item already exists, can not change id.")
+
+        if type(target) != int:
+            raise Exception("Target ID must be Int.")
+
+        if target < 1:
+            raise Exception("Target ID must be greater than 1.")
+
+        self.__id = target

+ 60 - 0
assets/secret.py

@@ -0,0 +1,60 @@
+from .secret_properties import secret_properties
+
+class secret(metaclass = secret_properties):
+    def __init__(self, token: str):
+        if not self.__check(token):
+            raise Exception("Token is not valid.")
+
+        self.__token = token
+    
+    def clone(self):
+        return secret(self.token)
+
+    def build(hashed: bytes, salt: bytes):
+        separator = secret.salt_separator
+        result = secret(hashed.hex() + separator + salt.hex())
+
+        return result
+
+    @property
+    def token(self) -> str:
+        return self.__token
+
+    @property
+    def hashed(self) -> bytes:
+        properties = self.__class__
+        separator = properties.salt_separator
+        splited = self.token.split(separator)
+
+        return bytes.fromhex(splited[0])
+
+    @property
+    def salt(self) -> bytes:
+        properties = self.__class__
+        separator = properties.salt_separator
+        splited = self.token.split(separator)
+
+        return bytes.fromhex(splited[-1])
+
+    def __str__(self) -> str:
+        return self.token
+
+    def __check(self, token: str) -> bool:  
+        properties = self.__class__
+        separator = properties.salt_separator
+        splited = token.split(separator)
+
+        if len(splited) != 2:
+            return False
+
+        hashed = splited[0]
+        salt = splited[1]
+
+        if len(hashed) != properties.hash_hex_length:
+            return False
+
+        if len(salt) / 2 != properties.salt_length:
+            return False
+        
+        return True
+    

+ 15 - 50
assets/secret_generator.py

@@ -2,6 +2,7 @@ import hashlib
 import os
 
 from .secret_properties import secret_properties
+from .secret import secret
 
 class secret_generator(metaclass = secret_properties):
     def __init__(self, password: str):
@@ -12,50 +13,25 @@ class secret_generator(metaclass = secret_properties):
     def password(self) -> str: 
         return self.__password
 
-    def validate(self, secret: str) -> bool:
-        if not self.__check_secret(secret):
-            return False
-
-        hashed, salt = self.__split_secret(secret)
-        target_hashed, second_salt = self.__generate_hashed(salt)
+    def validate(self, target: secret) -> bool:
+        hashed = target.hashed
+        salt = target.salt
+        target_hashed = self.__generate_hashed(salt)
 
         return hashed == target_hashed
 
-    def __check_secret(self, secret: str) -> bool:
+    def __generate_salt(self) -> bytes:
         properties = self.__class__
-        
-        hash_length = properties.hash_hex_length
-        salt_length = properties.salt_hex_length
-        separator = properties.salt_separator
-
-        if secret.find(separator) != hash_length:
-            return False
-
-        if len(secret) != hash_length + salt_length + len(separator):
-            return False
-
-        return True
+        result = os.urandom(properties.salt_length)
 
-    def __split_secret(self, secret: str) -> [bytes, bytes]:
-        properties = self.__class__
-        separator = properties.salt_separator
-        splited = secret.split(separator)
-
-        hashed = bytes.fromhex(splited[0])
-        salt = bytes.fromhex(splited[-1])
-
-        return hashed, salt
+        return result
 
-    def __generate_hashed(self, salt: bytes | None = None) -> str:
+    def __generate_hashed(self, salt: bytes) -> bytes:
         properties = self.__class__
         
         rounds = properties.hash_rounds
         algorithm = properties.hash_algorithm
         password = self.password.encode("UTF-8")
-        
-        if salt is None:
-            random_size = properties.salt_length
-            salt = os.urandom(random_size)
 
         hashed = hashlib.pbkdf2_hmac(
             algorithm, 
@@ -64,25 +40,14 @@ class secret_generator(metaclass = secret_properties):
             rounds
         )
 
-        return hashed, salt
+        return hashed
 
     @property
-    def secret(self) -> str:
+    def secret(self) -> secret:
         if self.__secret is not None:
             return self.__secret
 
-        hashed, salt = self.__generate_hashed()
-        self.__secret = self.__create_secret(hashed, salt)
-
-        return self.secret
-
-    def __create_secret(self, hashed: bytes, salt: bytes) -> str:
-        properties = self.__class__
-        separator = properties.salt_separator
-
-        result = hashed.hex() + separator + salt.hex()
-
-        if not self.__check_secret(result):
-            raise Exception("Can not create secret. Check secret settings!")
-
-        return result
+        salt = self.__generate_salt()
+        hashed = self.__generate_hashed(salt)
+        
+        return secret.build(hashed, salt)

+ 42 - 10
assets/user.py

@@ -1,15 +1,47 @@
-import sqlmodel
+from .database_model import database_model
+from .secret import secret
 
-class user(sqlmodel.SQLModel, table = True):
-    id: int | None = sqlmodel.Field(default = None, primary_key = True)
-    nick: str
-    secret: str
+class user(database_model):
+    def __init__(self, id: int | None = None):
+        super(id)
+
+        self.__nick = None
+        self.__password = None
+
+    @property
+    def nick(self) -> str:
+        if self.__nick is None:
+            raise Exception("Nick must be set, before access.")
+
+        return self.__nick
+
+    @nick.setter
+    def nick(self, target: str) -> None:
+        if type(target) != str: 
+            raise Exception("Nick of the user must be string.")
+
+        self.__nick = target
+
+    @property
+    def password(self) -> secret:
+        if self.__password is None:
+            raise Exception("Password secret must be set, before access.")
+
+        return self.__password
+
+    @password.setter
+    def password(self, target: secret):
+        if type(target) != secret:
+            raise Exception("Password of the user must be secret instance.")
+
+        self.__password = target
 
     def clone(self) -> super:   
-        return user(
-            id = self.id,
-            nick = self.nick,
-            secret = self.secret
-        )
+        target = user(self.id)
+          
+        target.nick = self.nick
+        target.secret = self.secret.clone()
+
+        return target
 
 

+ 13 - 0
assets/user_adapter.py

@@ -0,0 +1,13 @@
+from .adapter import adapter
+from .user import user
+from .user_table import user_table
+
+class user_adapter(adapter):
+    def convert(target: user) -> user_table:
+        result = user_table()
+
+        result.id = target.id
+        result.nick = target.nick
+        result.password = target.password.secret
+
+        return result

+ 6 - 0
assets/user_table.py

@@ -0,0 +1,6 @@
+import sqlmodel
+
+class user_table(sqlmodel.SQLModel, table = True):
+    id: int | None = sqlmodel.Field(default = None, primary_key = True)
+    nick: str
+    password: str

+ 14 - 0
assets/user_table_adapter.py

@@ -0,0 +1,14 @@
+from .adapter import adapter
+from .user import user
+from .user_table import user_table
+from .secret import secret
+
+class user_table_adapter(adapter):
+    def convert(targer: user_table) -> user:
+        result = user()
+        
+        result.id = target.id
+        result.nick = target.nick
+        result.password = secret(target.password)
+
+        return result

+ 2 - 2
assets/user_validator.py

@@ -17,7 +17,7 @@ class user_validation_exception(BaseException):
 
 class user_validator:
     def __init__(self, target: user):
-        self.__target = user.clone()
+        self.__target = user.clone(target)
 
     @property
     def target(self) -> user:   
@@ -43,7 +43,7 @@ class user_validator:
 
     @property
     def has_valid_nick(self) -> bool:
-        return self.nick_validation_result == user_validation_resut.VALID
+        return self.nick_validation_result == user_validation_result.VALID
 
     @property
     def secret_validation_result(self) -> user_validation_result:

+ 3 - 3
tests/01-secret-generator.py

@@ -14,10 +14,10 @@ password_b = secret_generator("Password_b")
 verify_password_a = secret_generator("Password_a")
 
 print("Check secret generation: ")
-print("Secret for 'Password_a': " + password_a.secret)
+print("Secret for 'Password_a': " + str(password_a.secret))
 print("Check that secret is generated once in the single instance: ")
-print("Secret for 'Password_a': " + password_a.secret)
-print("Secret for 'Password_b': " + password_b.secret)
+print("Secret for 'Password_a': " + str(password_a.secret))
+print("Secret for 'Password_b': " + str(password_b.secret))
 
 validation_result = "False"
 

+ 23 - 0
tests/02-database.py

@@ -1,5 +1,6 @@
 import sys
 import pathlib
+import sqlmodel
 
 file = pathlib.Path(__file__)
 directory = file.parent
@@ -7,3 +8,25 @@ import_root = directory.parent
 
 sys.path.append(str(import_root))
 
+from assets import user
+from assets import user_validator
+from assets import users_manager
+
+database = sqlmodel.create_engine("sqlite:///database.db")
+connection = sqlmodel.SQLModel.metadata.create_all(database)
+manager = users_manager(connection)
+
+first_user = user()
+
+user.nick = "First"
+user.secret = "Secret"
+
+print("User created, registering...")
+
+manager.register(user)
+
+print("Registered, trying to log in...")
+
+check = manager.login("First", "Secret")
+
+

BIN
tests/database.db