Browse Source

Continue working, and some clean up.

Cixo Develop 7 months ago
parent
commit
4739904ea0
8 changed files with 294 additions and 81 deletions
  1. 2 1
      assets/__init__.py
  2. 67 0
      assets/builder.py
  3. 48 39
      assets/secret.py
  4. 36 0
      assets/secret_loader.py
  5. 53 36
      assets/user.py
  6. 2 5
      assets/user_loader.py
  7. 40 0
      tests/004-password_database.py
  8. 46 0
      tests/005-builder.py

+ 2 - 1
assets/__init__.py

@@ -6,4 +6,5 @@ from .database import database
 from .apikey import apikey
 from .secret import secret
 from .secret import secret_builder
-from .secret_crypto import secret_crypto
+from .secret_crypto import secret_crypto
+from .builder import builder

+ 67 - 0
assets/builder.py

@@ -0,0 +1,67 @@
+class builder:
+    """
+    This is class responsible for building new items for database.
+    """
+
+    def __init__(self, target: object = None) -> None:
+        """ 
+        This create new builder, cleaning it by default.
+
+        Parameters:
+            target (object<target_type>): Object to work on
+        """
+
+        self.clear(target)
+
+    @property
+    def _target(self) -> object:
+        """ Target to work on for child class. """
+
+        return self.__target
+
+    @property
+    def result(self) -> object:
+        """
+        This return created object, or raise exception if it is not ready.
+
+        Returns:
+            (object<target_type>): Ready created object
+        """
+
+        if not self.__target.ready:
+            raise TypeError("Can not read not ready result.")
+
+        return self.__target
+
+    def clear(self, target: object = None) -> None:
+        """
+        This drop previous object from builded, and set new one. When target
+        is not set, it create empty object to work on.
+
+        Parameters:
+            target (object<target_type> | None): Target to work on or None
+        """
+
+        if target is not None:
+            self.__target = target
+            return
+
+        self.__target = self.__target_type()
+
+    def __init_subclass__(cls, target_type: type, **kwargs) -> None:
+        """
+        This create new class. It require target_type, which is class of new
+        objects. It must have ready property, to check when new object is 
+        ready to load.
+
+        Parameters:
+            target_type(type): Type of target objects
+            **kwargs: Rest of parameters
+        """
+
+        super().__init_subclass__(**kwargs)
+
+        if not type(target_type.ready) is property:
+            raise Exception("Builder can build only database items.")
+
+        cls.__target_type = target_type

+ 48 - 39
assets/secret.py

@@ -3,6 +3,7 @@ import hashlib
 import Crypto
 
 from .user import user
+from .builder import builder
 from .secret_crypto import secret_crypto
 
 class secret(sqlmodel.SQLModel, table = True):
@@ -17,83 +18,91 @@ class secret(sqlmodel.SQLModel, table = True):
     nonce: bytes | None = sqlmodel.Field(default = None)
     owner: int | None = sqlmodel.Field(default = None, foreign_key = "user.id") 
 
-class secret_builder:
-    """
-    This class is responsible for creating new secrets for the user.
-    """
-
-    def __init__(self) -> None:
-        """
-        This function create new clean builder.
-        """
-
-        self.clean()
-
-    def clean(self) -> None:
-        """
-        This function clean builder, and prepare it to build new secret.
-        """
+    @property
+    def in_database(self) -> bool:
+        """ True when secret exists in database. """
 
-        self.__target = secret()
+        return self.id is not None
 
     @property
     def ready(self) -> bool:
-        """ True when secret is ready, false when not. """
+        """ True when all fields are filled. """
 
-        if self.__target.name is None or self.__target.domain is None:
+        if self.name is None or self.domain is None:
             return False
 
-        if self.__target.crypted is None or self.__target.nonce is None:
+        if self.crypted is None or self.nonce is None:
             return False
 
-        if self.__target.owner is None:
+        if self.owner is None:
             return False
 
         return True
 
+    def __str__(self) -> str:
+        """
+        This cast user to string, very usefull for debug.
+
+        Returns:
+            (str): User dump as string
+        """
+
+        result = ""
+        result = result + "Secret "
+
+        if self.id is not None:
+            result = result + "(" + str(self.id) + ")"
+
+        result = result + "\n"
+        
+        result = result + "Name: " + self.name + "\n"
+        result = result + "Domain: " + self.domain + "\n"
+        result = result + "Owner ID: " + str(self.owner) + "\n"
+        result = result + "Crypted: " + self.crypted.hex() + "\n"
+        result = result + "Nonce: " + self.nonce.hex() + "\n"
+        
+        return result
+
+class secret_builder(builder, target_type = secret):
+    """
+    This class is responsible for creating new secrets for the user.
+    """
+
     @property
     def owner(self) -> int | None:
         """ This return ID of the secret owner, or None if not set. """
-        return self.__target.owner
+
+        return self._target.owner
 
     @owner.setter
     def owner(self, target: user):
         """ This set new owner of the secret. """
 
-        self.__target.owner = user.id
-
-    @property
-    def result(self) -> secret:
-        """ This return ready secret if it is ready, or raise Exception. """
-
-        if not self.ready:
-            raise TypeError("Secret is not ready to load.")
-
-        return self.__target
+        self._target.owner = user.id
 
     @property
     def name(self) -> str | None:
         """ This return name of the secret or None if not set. """
 
-        return self.__target.name
+        return self._target.name
 
     @property
     def domain(self) -> str | None:
         """ This return domain of the secret or None if not set. """
 
-        return self.__target.domain
+        return self._target.domain
 
     @name.setter
     def name(self, target: str) -> None:
         """ This set name of the secret. """
 
-        self.__target.name = target
+        self._target.name = target.upper()
 
     @domain.setter
     def domain(self, target: str) -> None:
         """ This set domain of the secret. """
 
-        self.__target.domain = target
+        self._target.domain = target
 
     def crypt(self, key: str, target: str) -> None:
         """
@@ -109,10 +118,10 @@ class secret_builder:
 
         crypter = secret_crypto(key)
 
-        if self.__target.nonce is not None:
+        if self._target.nonce is not None:
             crypter.set_iv(self.__target.nonce)
 
         crypted, nonce = crypter.crypted(target)
 
-        self.__target.crypted = crypted
-        self.__target.nonce = nonce
+        self._target.crypted = crypted
+        self._target.nonce = nonce

+ 36 - 0
assets/secret_loader.py

@@ -0,0 +1,36 @@
+import sqlmodel
+
+from .user import user
+from .secret import secret
+
+class secret_loader(sqlmodel.Session):
+    def __init__(self, owner: user) -> None:
+        if not owner.in_database:
+            raise Exception("User to build loaded for not exists.")
+
+        self.__owner = owner
+
+    @property
+    def owner(self) -> user:
+        return self.__owner
+
+    def append(self, target: secret) -> bool:
+        if target.in_database or not target.is_ready:
+            return False
+
+        if self.get_by_name(target.name) is not None:
+            return False
+
+        self.add(target)
+        self.commit()
+        self.refresh(target)
+
+    def get_by_name(self, target: str) -> secret | None:
+        target = target.upper()
+
+        query = sqlmodel.select(secret)
+        query = query.where(user.id == self.owner.id)
+        query = query.where(secret.name == target)
+        query = query.limit(1)
+
+        return self.exec(query).first()

+ 53 - 36
assets/user.py

@@ -2,6 +2,7 @@ import sqlmodel
 
 from .apikey import apikey
 from .password import password
+from .builder import builder
 
 class user(sqlmodel.SQLModel, table = True):
     """
@@ -15,44 +16,73 @@ class user(sqlmodel.SQLModel, table = True):
     password: str = sqlmodel.Field(index = False)
     apikey: str = sqlmodel.Field(index = True, unique = True)
 
-class user_builder:
-    def __init__(self) -> None:
-        """
-        Create new empty user builder.
-        """
-
-        self.clean()
-
-    def clean(self) -> None:
-        """
-        This clean builed. That mean, new empty user is creating.
-        """
+    @property
+    def in_database(self) -> bool:
+        """ True when user exists in database. """
 
-        self.__target = user(apikey = apikey())
+        return self.id is not None
 
     @property
     def ready(self) -> bool:
-        """
-        This check that user in builder is ready to get.
+        """ True when all fields are filled. """
 
-        return: bool - True when user is ready, false if not
-        """
+        if self.nick is None:
+            return False
 
-        if self.__target.nick is None:
+        if self.password is None:
             return False
 
-        if self.__target.password is None:
+        if self.apikey is None:
             return False
 
         return True
 
+    def __str__(self) -> str:
+        """
+        This function dump user to string, very usefull for debug.
+
+        Returns:
+            (str): User as string
+        """
+
+        result = ""
+        result = result + "User "
+
+        if self.id is not None:
+            result = result + "(" + str(self.id) + ")"
+
+        result = result + "\n"
+        
+        result = result + "Nick: " + self.nick + "\n"
+        result = result + "Password: " + self.password + "\n"
+        result = result + "API key: " + self.apikey + "\n"
+
+        return result
+
+class user_builder(builder, target_type = user):
+    """
+    This class is responsible for building new user.
+    """
+
+    def __init__(self, target: user | None = None) -> None:
+        """ 
+        This create new user builder. It can be initialized by already
+        created user. When None create empty user.
+
+        Parameters:
+            target(user | None): Target to initialize builder with
+        """
+
+        super().__init__(target)
+        self._target.apikey = apikey()
+
     @property
     def nick(self) -> str | None:
         """
         return: str | None - Current nick of the user in builder
         """
 
-        return self.__target.nick
+        return self._target.nick
 
     @property
     def password(self) -> bool:
@@ -60,7 +90,7 @@ class user_builder:
         return: bool - True when password is set, false if not
         """
 
-        return self.__target.password is not None
+        return self._target.password is not None
 
     @nick.setter
     def nick(self, target: str) -> None:
@@ -68,7 +98,7 @@ class user_builder:
         target: str - New nick for the user
         """
 
-        self.__target.nick = target
+        self._target.nick = target.upper()
 
     @password.setter
     def password(self, target: str) -> None:
@@ -76,17 +106,4 @@ class user_builder:
         target: str - New password to hash and set
         """
 
-        self.__target.password = password(target).result
-
-    @property
-    def result(self) -> user:
-        """
-        This return user setup in the builder.
-
-        return: user - User from builder
-        """
-
-        if not self.ready:
-            raise TypeError("User in builder is not ready yet.")
-
-        return self.__target
+        self._target.password = password(target).result

+ 2 - 5
assets/user_loader.py

@@ -36,13 +36,10 @@ class user_loader(sqlmodel.Session):
         return: bool - True when registered new user, False when failed
         """
 
-        if target.id is not None:
+        if target.in_database:
             return False
 
-        if target.apikey is None or target.nick is None:
-            return False
-
-        if target.password is None:
+        if not target.ready:
             return False
 
         if self.nick_in_use(target.nick):

+ 40 - 0
tests/004-password_database.py

@@ -0,0 +1,40 @@
+import pathlib
+
+current = pathlib.Path(__file__).parent
+root = current.parent
+
+import sys
+sys.path.append(str(root))
+
+import assets
+import sqlmodel
+
+connection = sqlmodel.create_engine("sqlite:///04-test.db")
+
+user_builder = assets.user_builder()
+user_builder.nick = "test"
+user_builder.password = "qwerty"
+
+sample_user = user_builder.result
+
+secret_builder = assets.secret_builder()
+
+secret_builder.clear()
+secret_builder.name = "secret1"
+secret_builder.owner = sample_user
+secret_builder.domain = "http://secret1"
+secret_builder.crypt("qwerty", "SAMPLE_1_PSK")
+
+secret_1 = secret_builder.result
+
+secret_builder.clear()
+secret_builder.name = "secret2"
+secret_builder.owner = sample_user
+secret_builder.domain = "https://secret2"
+secret_builder.crypt("qwerty", "SAMPLE_2_PSK")
+
+secret_2 = secret_builder.result
+
+print(sample_user)
+print(secret_1)
+print(secret_2)

+ 46 - 0
tests/005-builder.py

@@ -0,0 +1,46 @@
+import pathlib
+
+current = pathlib.Path(__file__).parent
+root = current.parent
+
+import sys
+sys.path.append(str(root))
+
+import assets
+
+class sample:
+    @property
+    def ready(self) -> bool:
+        return True
+
+class sample_builder(assets.builder, target_type = sample):
+    pass
+
+build = sample_builder()
+
+print("Builder type: " + str(type(build)))
+print("Sample: " + str(build.result))
+
+print("Cleaning...")
+build.clear()
+
+print("Sample: " + str(build.result))
+
+print("Set non cleaning")
+sample_1 = sample()
+build.clear(sample_1)
+
+print("Sampe 1: " + str(sample_1))
+print("Sample: " + str(build.result))
+
+print("Set non cleaning")
+sample_2 = sample()
+build.clear(sample_2)
+
+print("Sampe 2: " + str(sample_2))
+print("Sample: " + str(build.result))
+
+print("Cleaning...")
+build.clear()
+
+print("Sample: " + str(build.result))