passphera-core 0.8.2__tar.gz → 0.9.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: passphera-core
3
- Version: 0.8.2
3
+ Version: 0.9.0
4
4
  Summary: The core system of passphera project
5
5
  Home-page: https://github.com/passphera/core
6
6
  Author: Fathi Abdelmalek
7
7
  Author-email: abdelmalek.fathi.2001@gmail.com
8
- Classifier: Development Status :: 2 - Pre-Alpha
8
+ Classifier: Development Status :: 3 - Alpha
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python
@@ -1,2 +1,2 @@
1
- from passphera_core.generator import PasswordGenerator
1
+ from passphera_core.entities.generator import Generator
2
2
  from passphera_core.exceptions import InvalidAlgorithmException
@@ -0,0 +1,125 @@
1
+ from dataclasses import dataclass, field
2
+ from datetime import datetime, timezone
3
+ from uuid import UUID, uuid4
4
+
5
+ from cryptography.fernet import Fernet
6
+
7
+ from cipherspy.cipher import *
8
+ from cipherspy.cipher.base_cipher import BaseCipherAlgorithm
9
+ from cipherspy.utilities import generate_salt, derive_key
10
+
11
+ from passphera_core import exceptions
12
+
13
+
14
+ @dataclass
15
+ class Password:
16
+ id: UUID = field(default_factory=uuid4)
17
+ user_id: UUID = field(default_factory=uuid4)
18
+ created_at: datetime = field(default=datetime.now(timezone.utc))
19
+ updated_at: datetime = field(default=datetime.now(timezone.utc))
20
+ context: str = field(default_factory=str)
21
+ text: str = field(default_factory=str)
22
+ password: str = field(default_factory=str)
23
+ salt: bytes = field(default_factory=bytes)
24
+
25
+ def encrypt(self) -> None:
26
+ self.salt = generate_salt()
27
+ key = derive_key(self.password, self.salt)
28
+ self.password = Fernet(key).encrypt(self.password.encode()).decode()
29
+
30
+ def decrypt(self) -> str:
31
+ key = derive_key(self.password, self.salt)
32
+ return Fernet(key).decrypt(self.password.encode()).decode()
33
+
34
+
35
+ @dataclass
36
+ class GeneratorConfig:
37
+ id: UUID = field(default_factory=uuid4)
38
+ generator_id: UUID = field(default_factory=uuid4)
39
+ shift: int = field(default=3)
40
+ multiplier: int = field(default=3)
41
+ key: str = field(default="hill")
42
+ algorithm: str = field(default="hill")
43
+ prefix: str = field(default="secret")
44
+ postfix: str = field(default="secret")
45
+ characters_replacements: dict[str, str] = field(default_factory=dict[str, str])
46
+ _cipher_registry: dict[str, BaseCipherAlgorithm] = field(default_factory=lambda: {
47
+ 'caesar': CaesarCipherAlgorithm,
48
+ 'affine': AffineCipherAlgorithm,
49
+ 'playfair': PlayfairCipherAlgorithm,
50
+ 'hill': HillCipherAlgorithm,
51
+ }, init=False)
52
+
53
+ def get_algorithm(self) -> BaseCipherAlgorithm:
54
+ """
55
+ Get the primary algorithm used to cipher the password
56
+ :return: BaseCipherAlgorithm: The primary algorithm used for the cipher
57
+ """
58
+ if self.algorithm.lower() not in self._cipher_registry:
59
+ raise exceptions.InvalidAlgorithmException(self.algorithm)
60
+ return self._cipher_registry[self.algorithm.lower()]
61
+
62
+ def replace_character(self, char: str, replacement: str) -> None:
63
+ """
64
+ Replace a character with another character or set of characters
65
+ Eg: pg.replace_character('a', '@1')
66
+ :param char: The character to be replaced
67
+ :param replacement: The (character|set of characters) to replace the first one
68
+ :return:
69
+ """
70
+ self.characters_replacements[char[0]] = replacement
71
+
72
+ def reset_character(self, char: str) -> None:
73
+ """
74
+ Reset a character to its original value (remove its replacement from characters_replacements)
75
+ :param char: The character to be reset to its original value
76
+ :return:
77
+ """
78
+ self.characters_replacements.pop(char, None)
79
+
80
+
81
+ @dataclass
82
+ class Generator:
83
+ id: UUID = field(default_factory=uuid4)
84
+ user_id: UUID = field(default_factory=uuid4)
85
+ created_at: datetime = field(default_factory=datetime.now)
86
+ updated_at: datetime = field(default_factory=datetime.now)
87
+ config: GeneratorConfig = field(default_factory=GeneratorConfig)
88
+
89
+ def apply_replacements(self, password: str) -> str:
90
+ """
91
+ Replace character from the ciphered password with character replacements from the generator configurations
92
+ :return: str: The new ciphered password after character replacements
93
+ """
94
+ translation_table = str.maketrans(self.config.characters_replacements)
95
+ return password.translate(translation_table)
96
+
97
+ def generate_password(self, text: str) -> str:
98
+ """
99
+ Generate a strong password string using the raw password (add another layer of encryption to it)
100
+ :return: str: The generated ciphered password
101
+ """
102
+ affine = AffineCipherAlgorithm(self.config.shift, self.config.multiplier)
103
+ intermediate = affine.encrypt(f"{self.config.prefix}{text}{self.config.postfix}")
104
+ main_algorithm = self.config.get_algorithm()
105
+ password = main_algorithm.encrypt(intermediate)
106
+ password = self.apply_replacements(password)
107
+ return ''.join(c.upper() if c in text else c for c in password)
108
+
109
+
110
+ @dataclass
111
+ class User:
112
+ id: UUID = field(default_factory=uuid4)
113
+ created_at: datetime = field(default_factory=datetime.now)
114
+ updated_at: datetime = field(default_factory=datetime.now)
115
+ username: str = field(default_factory=str)
116
+ email: str = field(default_factory=str)
117
+ password: str = field(default_factory=str)
118
+ generator: UUID = field(default_factory=UUID)
119
+ passwords: list[UUID] = field(default_factory=list[UUID])
120
+
121
+ def add_password(self, password_id: UUID) -> None:
122
+ self.passwords.append(password_id)
123
+
124
+ def delete_password(self, password_id: UUID) -> None:
125
+ self.passwords.remove(password_id)
@@ -0,0 +1,49 @@
1
+ from typing import Union, Literal
2
+
3
+ from passphera_core.entities import Password, Generator, User
4
+
5
+
6
+ class InvalidAlgorithmException(Exception):
7
+ def __init__(self, algorithm: str) -> None:
8
+ self.algorithm = algorithm
9
+ super().__init__(f"Invalid algorithm: '{algorithm}'")
10
+
11
+
12
+ class EntityNotFoundException(Exception):
13
+ def __init__(self, entity: Union[Password, Generator, User]) -> None:
14
+ self.entity = entity
15
+ entity_type = entity.__class__.__name__
16
+ super().__init__(f"{entity_type} not found")
17
+
18
+
19
+ class DuplicateEntityException(Exception):
20
+ def __init__(
21
+ self,
22
+ entity: Union[Password, User],
23
+ duplicate_field: Literal['context', 'email', 'username'] = None
24
+ ) -> None:
25
+ self.entity = entity
26
+ self.duplicate_field = duplicate_field
27
+ message = self._build_message(entity, duplicate_field)
28
+ super().__init__(message)
29
+
30
+ def _build_message(self, entity: Union[Password, User], duplicate_field: str | None) -> str:
31
+ if isinstance(entity, Password):
32
+ return self._build_password_message(entity)
33
+ elif isinstance(entity, User):
34
+ return self._build_user_message(entity, duplicate_field)
35
+ return "Duplicate entity detected"
36
+
37
+ @staticmethod
38
+ def _build_password_message(password: Password) -> str:
39
+ if hasattr(password, 'context') and password.context:
40
+ return f"Password for context '{password.context}' already exists"
41
+ return "Duplicate password detected"
42
+
43
+ @staticmethod
44
+ def _build_user_message(user: User, duplicate_field: str) -> str:
45
+ if duplicate_field == 'email':
46
+ return f"User with email '{user.email}' already exists"
47
+ elif duplicate_field == 'username':
48
+ return f"User with username '{user.username}' already exists"
49
+ return "Duplicate user detected"
@@ -0,0 +1,92 @@
1
+ from abc import ABC, abstractmethod
2
+ from uuid import UUID
3
+
4
+ from passphera_core.entities import Password, Generator, GeneratorConfig, User
5
+
6
+
7
+ class PasswordRepository(ABC):
8
+ @abstractmethod
9
+ def save(self, password: Password) -> None:
10
+ pass
11
+
12
+ @abstractmethod
13
+ def update(self, password: Password) -> None:
14
+ pass
15
+
16
+ @abstractmethod
17
+ def delete(self, password_id: UUID) -> None:
18
+ pass
19
+
20
+ @abstractmethod
21
+ def find_by_id(self, password_id: UUID) -> Password:
22
+ pass
23
+
24
+
25
+ class GeneratorRepository(ABC):
26
+ @abstractmethod
27
+ def save(self, generator: Generator) -> None:
28
+ pass
29
+
30
+ @abstractmethod
31
+ def update(self, generator: Generator) -> None:
32
+ pass
33
+
34
+ @abstractmethod
35
+ def delete(self, generator_id: UUID) -> None:
36
+ pass
37
+
38
+ @abstractmethod
39
+ def find_by_id(self, generator_id: UUID) -> Generator:
40
+ pass
41
+
42
+ @abstractmethod
43
+ def find_by_user_id(self, user_id: UUID) -> Generator:
44
+ pass
45
+
46
+
47
+ class GeneratorConfigRepository(ABC):
48
+ @abstractmethod
49
+ def save(self, generator_config: GeneratorConfig) -> None:
50
+ pass
51
+
52
+ @abstractmethod
53
+ def update(self, generator_config: GeneratorConfig) -> None:
54
+ pass
55
+
56
+ @abstractmethod
57
+ def delete(self, generator_config_id: UUID) -> None:
58
+ pass
59
+
60
+ @abstractmethod
61
+ def find_by_id(self, generator_config_id: UUID) -> GeneratorConfig:
62
+ pass
63
+
64
+ @abstractmethod
65
+ def find_by_generator_id(self, generator_id: UUID) -> GeneratorConfig:
66
+ pass
67
+
68
+
69
+ class UserRepository(ABC):
70
+ @abstractmethod
71
+ def save(self, user: User) -> None:
72
+ pass
73
+
74
+ @abstractmethod
75
+ def update(self, user: User) -> None:
76
+ pass
77
+
78
+ @abstractmethod
79
+ def delete(self, user_id: UUID) -> None:
80
+ pass
81
+
82
+ @abstractmethod
83
+ def find_by_id(self, user_id: UUID) -> User:
84
+ pass
85
+
86
+ @abstractmethod
87
+ def find_by_username(self, username: str) -> User:
88
+ pass
89
+
90
+ @abstractmethod
91
+ def find_by_email(self, email: str) -> User:
92
+ pass
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: passphera-core
3
- Version: 0.8.2
3
+ Version: 0.9.0
4
4
  Summary: The core system of passphera project
5
5
  Home-page: https://github.com/passphera/core
6
6
  Author: Fathi Abdelmalek
7
7
  Author-email: abdelmalek.fathi.2001@gmail.com
8
- Classifier: Development Status :: 2 - Pre-Alpha
8
+ Classifier: Development Status :: 3 - Alpha
9
9
  Classifier: License :: OSI Approved :: MIT License
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python
@@ -1,8 +1,9 @@
1
1
  README.md
2
2
  setup.py
3
3
  passphera_core/__init__.py
4
+ passphera_core/entities.py
4
5
  passphera_core/exceptions.py
5
- passphera_core/generator.py
6
+ passphera_core/interfaces.py
6
7
  passphera_core.egg-info/PKG-INFO
7
8
  passphera_core.egg-info/SOURCES.txt
8
9
  passphera_core.egg-info/dependency_links.txt
@@ -5,7 +5,7 @@ with open("README.md", "r") as fh:
5
5
 
6
6
  setup(
7
7
  name='passphera-core',
8
- version='0.8.2',
8
+ version='0.9.0',
9
9
  author='Fathi Abdelmalek',
10
10
  author_email='abdelmalek.fathi.2001@gmail.com',
11
11
  url='https://github.com/passphera/core',
@@ -16,7 +16,7 @@ setup(
16
16
  python_requires='>=3',
17
17
  install_requires=['cipherspy'],
18
18
  classifiers=[
19
- "Development Status :: 2 - Pre-Alpha",
19
+ "Development Status :: 3 - Alpha",
20
20
  "License :: OSI Approved :: MIT License",
21
21
  "Operating System :: OS Independent",
22
22
  "Programming Language :: Python",
@@ -1,3 +0,0 @@
1
- class InvalidAlgorithmException(Exception):
2
- def __init__(self, algorithm: str) -> None:
3
- super().__init__(f"Invalid algorithm name [{algorithm}]")
@@ -1,228 +0,0 @@
1
- from cipherspy.cipher import *
2
-
3
- from .exceptions import InvalidAlgorithmException
4
-
5
-
6
- class PasswordGenerator:
7
- """
8
- A strong password generator use multiple cipher algorithms to cipher a given plain text
9
- """
10
- def __init__(
11
- self,
12
- shift: int = 3,
13
- multiplier: int = 3,
14
- key: str = "hill",
15
- algorithm: str = 'hill',
16
- prefix: str = 'secret',
17
- postfix: str = 'secret',
18
- characters_replacements: dict = None,
19
- ):
20
- """
21
- :param shift: number of characters to shift each character (default 3)
22
- :param multiplier: number of characters to shift each character (default 3)
23
- :param key: cipher key string (default "secret")
24
- :param algorithm: main cipher algorithm name (default 'playfair')
25
- :param characters_replacements: replace characters with the given values (default {})
26
- :param text: plain text to be ciphered
27
- """
28
- if characters_replacements is None:
29
- characters_replacements = {}
30
- self._shift: int = shift
31
- self._multiplier: int = multiplier
32
- self._key: str = key
33
- self._algorithm_name: str = algorithm.lower()
34
- self._algorithm = self._set_algorithm()
35
- self._prefix: str = prefix
36
- self._postfix: str = postfix
37
- self._characters_replacements: dict = characters_replacements
38
-
39
- @property
40
- def shift(self) -> int:
41
- """
42
- Returns the shift value for the cipher algorithm
43
- Eg: ```shift = pg.shift```
44
- :return: int: The shift value for the cipher algorithm
45
- """
46
- return self._shift
47
-
48
- @shift.setter
49
- def shift(self, shift: int) -> None:
50
- """
51
- Sets the shift value for the cipher algorithm
52
- Eg: ```pg.shift = 3```
53
- :param shift: The shift value for the cipher algorithm
54
- :return:
55
- """
56
- self._shift = shift
57
-
58
- @property
59
- def multiplier(self) -> int:
60
- """
61
- Returns the multiplier value for the cipher algorithm
62
- Eg: ```multiplier = pg.multiplier```
63
- :return: int: The multiplier value for the cipher algorithm
64
- """
65
- return self._multiplier
66
-
67
- @multiplier.setter
68
- def multiplier(self, multiplier: int) -> None:
69
- """
70
- Sets the multiplier value for the cipher algorithm
71
- Eg: ```pg.multiplier = 3```
72
- :param multiplier: The multiplier value for the cipher algorithm
73
- :return:
74
- """
75
- self._multiplier = multiplier
76
-
77
- @property
78
- def key(self) -> str:
79
- """
80
- Returns the key string for the cipher algorithm
81
- Eg: ```key = pg.key```
82
- :return: str: The key string for the cipher algorithm
83
- """
84
- return self._key
85
-
86
- @key.setter
87
- def key(self, key: str) -> None:
88
- """
89
- Sets the key string for the cipher algorithm
90
- Eg: ```pg.key = 'secret key'```
91
- :param key: The key string for the cipher algorithm
92
- :return:
93
- """
94
- self._key = key
95
-
96
- @property
97
- def prefix(self) -> str:
98
- """
99
- Returns the prefix string for the cipher algorithm
100
- Eg: ```prefix = pg.prefix```
101
- :return: str: The prefix string for the cipher algorithm
102
- """
103
- return self._prefix
104
-
105
- @prefix.setter
106
- def prefix(self, prefix: str):
107
- """
108
- Sets the prefix string for the cipher algorithm
109
- Eg: ```pg.prefix = 'something'```
110
- :param prefix: The string for the cipher algorithm
111
- :return:
112
- """
113
- self._prefix = prefix
114
-
115
- @property
116
- def postfix(self) -> str:
117
- """
118
- Returns the postfix string for the cipher algorithm
119
- Eg: ```postfix = pg.postfix```
120
- :return: str: The postfix string for the cipher algorithm
121
- """
122
- return self._postfix
123
-
124
- @postfix.setter
125
- def postfix(self, postfix: str):
126
- """
127
- Sets the postfix string for the cipher algorithm
128
- Eg: ```pg.postfix = 'something'```
129
- :param postfix: The string for the cipher algorithm
130
- :return:
131
- """
132
- self._postfix = postfix
133
-
134
- @property
135
- def algorithm(self) -> str:
136
- """
137
- Returns the main cipher algorithm name
138
- Eg: ```algorithm = pg.algorithm```
139
- :return: str: The main cipher algorithm name
140
- """
141
- return self._algorithm_name
142
-
143
- @algorithm.setter
144
- def algorithm(self, algorithm: str) -> None:
145
- """
146
- Sets the main cipher algorithm
147
- Eg: ```pg.algorithm = 'playfair'```
148
- :param algorithm: The name of the main cipher algorithm
149
- :return:
150
- """
151
- self._algorithm_name = algorithm.lower()
152
- self._algorithm = self._set_algorithm()
153
-
154
- @property
155
- def characters_replacements(self) -> dict:
156
- """
157
- Returns the dictionary of the characters replacements
158
- Eg: ```print(pg.characters_replacements) # {'a': '@1', 'b': '#2'}```
159
- :return: dict: The dictionary of the characters replacements
160
- """
161
- return self._characters_replacements
162
-
163
- def _set_algorithm(self):
164
- """
165
- Return new instance of the used algorithm to the given one by it's name
166
- :return: new algorithm class
167
- """
168
- match self._algorithm_name:
169
- case 'caesar':
170
- return CaesarCipher(self._shift)
171
- case 'affine':
172
- return AffineCipher(self._shift, self._multiplier)
173
- case 'playfair':
174
- return PlayfairCipher(self._key)
175
- case 'hill':
176
- return HillCipher(self._key)
177
- case _:
178
- raise InvalidAlgorithmException(self._algorithm_name)
179
-
180
- def _update_algorithm_properties(self) -> None:
181
- """
182
- Update the main cipher algorithm
183
- """
184
- self._algorithm = self._set_algorithm()
185
-
186
- def replace_character(self, char: str, replacement: str) -> None:
187
- """
188
- Replace a character with another character or set of characters
189
- Eg: pg.replace_character('a', '@1')
190
- :param char: The character to be replaced
191
- :param replacement: The (character|set of characters) to replace the first one
192
- :return:
193
- """
194
- self._characters_replacements[char[0]] = replacement
195
-
196
- def reset_character(self, char: str) -> None:
197
- """
198
- Reset a character to it's original value (remove it's replacement from characters_replacements)
199
- :param char: The character to be reset to its original value
200
- :return:
201
- """
202
- if char in self._characters_replacements:
203
- del self._characters_replacements[char]
204
-
205
- def generate_raw_password(self, text: str) -> str:
206
- """
207
- Generate a raw password string using the given parameters
208
- :return: str: The generated raw password
209
- """
210
- self._update_algorithm_properties()
211
- return self._algorithm.encrypt(f"{self._prefix}{text}{self._postfix}")
212
-
213
- def generate_password(self, text: str) -> str:
214
- """
215
- Generate a strong password string using the raw password (add another layer of encryption to it)
216
- :return: str: The generated strong password
217
- """
218
- old_algorithm = self._algorithm_name
219
- self._algorithm_name = 'affine'
220
- password = self.generate_raw_password(text)
221
- self._algorithm_name = old_algorithm
222
- password = self.generate_raw_password(password)
223
- for char, replacement in self._characters_replacements.items():
224
- password = password.replace(char, replacement)
225
- for char in password:
226
- if char in text:
227
- password = password.replace(char, char.upper())
228
- return password
File without changes
File without changes