alpha-python 0.3.2__tar.gz → 0.3.4__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.
- {alpha_python-0.3.2 → alpha_python-0.3.4}/PKG-INFO +3 -2
- {alpha_python-0.3.2 → alpha_python-0.3.4}/pyproject.toml +3 -2
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/cli.py +13 -3
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/domain/models/user.py +2 -2
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/exceptions.py +21 -0
- alpha_python-0.3.4/src/alpha/factories/password_factory.py +130 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/api_generate_handler.py +2 -2
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/Dockerfile.mustache +2 -0
- alpha_python-0.3.4/src/alpha/providers/database_provider.py +242 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/models/identity.py +94 -46
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/services/authentication_service.py +4 -4
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/is_pydantic.py +9 -1
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha_python.egg-info/PKG-INFO +3 -2
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha_python.egg-info/SOURCES.txt +1 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha_python.egg-info/requires.txt +2 -1
- alpha_python-0.3.2/src/alpha/providers/database_provider.py +0 -12
- {alpha_python-0.3.2 → alpha_python-0.3.4}/LICENSE +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/README.md +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/setup.cfg +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/adapters/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/adapters/sqla_unit_of_work.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/containers/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/containers/container.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/domain/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/domain/models/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/domain/models/base_model.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/domain/models/life_cycle_base.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/encoder.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/_type_conversion_matrix.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/_type_mapping.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/class_factories.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/default_field_factory.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/field_iterator.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/jwt_factory.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/logging_handler_factory.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/model_class_factory.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/models/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/models/factory_classes.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/request_factory.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/response_factory.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/factories/type_factories.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/api_run_handler.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/base_handler.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/gen-code.sh +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/models/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/models/argument.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/models/command.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/models/section.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/models/subparser.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/run-api.sh +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/README.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/__init__model.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/__init__test.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/__main__.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/base_model.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/controller.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/controller_test.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/dockerignore.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/encoder.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/git_push.sh.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/gitignore.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/model.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/openapi.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/param_type.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/requirements.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/security_controller_.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/setup.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/test-requirements.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/tox.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/travis.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/typing_utils.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/handlers/templates/python-flask/util.mustache +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/connectors/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/connectors/ldap_connector.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/connectors/oidc_connector.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/databases/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/databases/sql_alchemy.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/models/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/models/filter_operators.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/models/json_patch.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/models/order_by.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/models/query_clause.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/infra/models/search_filter.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/attrs_instance.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/dataclass_instance.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/factories.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/handler.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/openapi_model.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/patchable.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/providers.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/pydantic_instance.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/sql_database.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/sql_mapper.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/sql_repository.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/token_factory.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/unit_of_work.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/interfaces/updateable.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/mixins/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/mixins/jwt_provider.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/api_key_provider.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/ldap_provider.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/local_provider.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/models/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/models/credentials.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/models/token.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/providers/oidc_provider.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/py.typed +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/repositories/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/repositories/models/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/repositories/models/repository_model.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/repositories/sql_alchemy_repository.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/services/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/__init__.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/_http_codes.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/is_attrs.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/logging_configurator.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/logging_level_checker.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/response_object.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/verify_identity.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha/utils/version_checker.py +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha_python.egg-info/dependency_links.txt +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha_python.egg-info/entry_points.txt +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/src/alpha_python.egg-info/top_level.txt +0 -0
- {alpha_python-0.3.2 → alpha_python-0.3.4}/tests/test_encoder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpha-python
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.4
|
|
4
4
|
Summary: Alpha is intended to be the first dependency you need to add to your Python application. It is a Python library which contains standard building blocks that can be used in applications that are used as APIs and/or make use of database interaction.
|
|
5
5
|
Author-email: Bart Reijling <bart@reijling.eu>
|
|
6
6
|
Requires-Python: >=3.11
|
|
@@ -16,7 +16,8 @@ Requires-Dist: pyjwt>=2.10.1
|
|
|
16
16
|
Requires-Dist: six>=1.17.0
|
|
17
17
|
Requires-Dist: sqlalchemy>=2.0.44
|
|
18
18
|
Requires-Dist: requests>=2.28.1
|
|
19
|
-
Requires-Dist: dependency-injector[yaml]
|
|
19
|
+
Requires-Dist: dependency-injector[yaml]!=4.48.3,<5.0.0,>=4.42.0
|
|
20
|
+
Requires-Dist: argon2-cffi>=25.1.0
|
|
20
21
|
Provides-Extra: api-generator
|
|
21
22
|
Requires-Dist: openapi-generator-cli==7.14.0; (python_version >= "3.11" and python_version < "4.0") and extra == "api-generator"
|
|
22
23
|
Requires-Dist: jdk4py; extra == "api-generator"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "alpha-python"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.4"
|
|
4
4
|
description = "Alpha is intended to be the first dependency you need to add to your Python application. It is a Python library which contains standard building blocks that can be used in applications that are used as APIs and/or make use of database interaction."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -18,7 +18,8 @@ dependencies = [
|
|
|
18
18
|
"six>=1.17.0",
|
|
19
19
|
"sqlalchemy>=2.0.44",
|
|
20
20
|
"requests>=2.28.1",
|
|
21
|
-
"dependency-injector[yaml]>=4.
|
|
21
|
+
"dependency-injector[yaml]>=4.42.0,<5.0.0,!=4.48.3",
|
|
22
|
+
"argon2-cffi>=25.1.0",
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
[project.scripts]
|
|
@@ -134,15 +134,25 @@ def _guess_current_package_name() -> str:
|
|
|
134
134
|
try:
|
|
135
135
|
with open(pyproject_path, 'rb') as f:
|
|
136
136
|
pyproject_data = tomllib.load(f)
|
|
137
|
-
|
|
137
|
+
name = None
|
|
138
|
+
try:
|
|
139
|
+
if 'project' in pyproject_data:
|
|
140
|
+
name = pyproject_data['project']['name']
|
|
141
|
+
elif 'tool' in pyproject_data and 'poetry' in pyproject_data['tool']:
|
|
142
|
+
name = pyproject_data['tool']['poetry']['name']
|
|
143
|
+
if name is not None:
|
|
144
|
+
return name.replace('-', '_')
|
|
145
|
+
except KeyError:
|
|
146
|
+
print('Could not find project name in pyproject.toml')
|
|
138
147
|
except Exception:
|
|
139
148
|
pass
|
|
149
|
+
else:
|
|
150
|
+
print('Could not find pyproject.toml')
|
|
140
151
|
|
|
141
152
|
# Fallback to use the current folder name
|
|
142
|
-
print('
|
|
153
|
+
print('Guessing package name from folder')
|
|
143
154
|
return os.path.basename(cwd)
|
|
144
155
|
|
|
145
|
-
|
|
146
156
|
def init() -> None:
|
|
147
157
|
"""Init the container and wire it to the main function."""
|
|
148
158
|
container = Container()
|
|
@@ -48,7 +48,7 @@ class User(LifeCycleBase, BaseDomainModel):
|
|
|
48
48
|
|
|
49
49
|
Parameters
|
|
50
50
|
----------
|
|
51
|
-
|
|
51
|
+
obj
|
|
52
52
|
User object to update from.
|
|
53
53
|
"""
|
|
54
54
|
if not isinstance(obj, User):
|
|
@@ -60,7 +60,7 @@ class User(LifeCycleBase, BaseDomainModel):
|
|
|
60
60
|
self.display_name = obj.display_name
|
|
61
61
|
self.permissions = obj.permissions
|
|
62
62
|
self.groups = obj.groups
|
|
63
|
-
self.
|
|
63
|
+
self.modified_at = datetime.now(tz=timezone.utc)
|
|
64
64
|
self.is_active = obj.is_active
|
|
65
65
|
self.admin = obj.admin
|
|
66
66
|
return cast(DomainModel, self)
|
|
@@ -177,6 +177,27 @@ class TokenCreationException(Exception):
|
|
|
177
177
|
"""Raised when there is an error during token creation."""
|
|
178
178
|
|
|
179
179
|
|
|
180
|
+
# User exceptions
|
|
181
|
+
class UnknownUserException(BadRequestException):
|
|
182
|
+
"""Raised when a referenced user cannot be found."""
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class EmptyValueException(BadRequestException):
|
|
186
|
+
"""Raised when a required value is empty."""
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
class UnknownTokenException(UnauthorizedException):
|
|
190
|
+
"""Raised when a token is not recognized."""
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class WrongPasswordException(BadRequestException):
|
|
194
|
+
"""Raised when an incorrect password is provided."""
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class MissingPasswordException(InternalServerErrorException):
|
|
198
|
+
"""Raised when a required password is missing from the request or configuration."""
|
|
199
|
+
|
|
200
|
+
|
|
180
201
|
# Cli Exceptions
|
|
181
202
|
class InvalidArgumentsException(Exception):
|
|
182
203
|
"""Raised when invalid arguments are provided to a CLI command."""
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Contains PasswordFactory class with password hashing methods
|
|
2
|
+
hash_password & verify_password
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from argon2 import PasswordHasher
|
|
6
|
+
from argon2.exceptions import VerifyMismatchError
|
|
7
|
+
|
|
8
|
+
from alpha import exceptions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class PasswordFactory:
|
|
12
|
+
"""This class provides methods for hashing and verifying passwords using
|
|
13
|
+
the argon2 library. It includes the following methods:
|
|
14
|
+
- hash_password: Hashes a given password and returns the hashed value as a
|
|
15
|
+
hexadecimal string.
|
|
16
|
+
- verify_password: Verifies a given password against a provided hash and
|
|
17
|
+
returns True if the password matches the hash, False otherwise.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
password_hasher
|
|
22
|
+
An optional password hasher instance. If not provided, a default
|
|
23
|
+
PasswordHasher with a salt length of 16 will be used.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def __init__(self, password_hasher: PasswordHasher | None = None) -> None:
|
|
27
|
+
self._password_hasher = password_hasher or PasswordHasher(salt_len=16)
|
|
28
|
+
|
|
29
|
+
def hash_password(
|
|
30
|
+
self, password: str | None, convert_to_hex: bool = True
|
|
31
|
+
) -> str:
|
|
32
|
+
"""Hashes the provided password using the password hasher.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
password
|
|
37
|
+
The password to be hashed.
|
|
38
|
+
convert_to_hex
|
|
39
|
+
A boolean flag indicating whether to convert the hashed password to
|
|
40
|
+
hexadecimal format. Defaults to True.
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
The hashed password as a hexadecimal string.
|
|
45
|
+
|
|
46
|
+
Raises
|
|
47
|
+
------
|
|
48
|
+
exceptions.WrongPasswordException
|
|
49
|
+
Raised when the provided password is None or empty.
|
|
50
|
+
"""
|
|
51
|
+
if not password:
|
|
52
|
+
raise exceptions.WrongPasswordException("Password value is empty")
|
|
53
|
+
hashed_password = self._password_hasher.hash(password=password)
|
|
54
|
+
return (
|
|
55
|
+
self._to_hex(hashed_password)
|
|
56
|
+
if convert_to_hex
|
|
57
|
+
else hashed_password
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def verify_password(
|
|
61
|
+
self,
|
|
62
|
+
password: str | None,
|
|
63
|
+
hash: str | None,
|
|
64
|
+
convert_from_hex: bool = True,
|
|
65
|
+
) -> bool:
|
|
66
|
+
"""Verifies the provided password against the given hash using the
|
|
67
|
+
password hasher.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
password
|
|
72
|
+
The password to be verified.
|
|
73
|
+
hash
|
|
74
|
+
The hash to verify the password against.
|
|
75
|
+
convert_from_hex
|
|
76
|
+
A boolean flag indicating whether to convert the hash from
|
|
77
|
+
hexadecimal format before verification. Defaults to True.
|
|
78
|
+
|
|
79
|
+
Returns
|
|
80
|
+
-------
|
|
81
|
+
True if the password matches the hash, False otherwise.
|
|
82
|
+
Raises
|
|
83
|
+
------
|
|
84
|
+
exceptions.WrongPasswordException
|
|
85
|
+
Raised when the provided password is None or empty.
|
|
86
|
+
exceptions.MissingPasswordException
|
|
87
|
+
Raised when the provided hash is None or empty.
|
|
88
|
+
"""
|
|
89
|
+
if not password:
|
|
90
|
+
raise exceptions.WrongPasswordException("Password value is empty")
|
|
91
|
+
if not hash:
|
|
92
|
+
raise exceptions.MissingPasswordException(
|
|
93
|
+
"No password value to compare with"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
return self._password_hasher.verify(
|
|
98
|
+
hash=self._from_hex(hash) if convert_from_hex else hash,
|
|
99
|
+
password=password,
|
|
100
|
+
)
|
|
101
|
+
except VerifyMismatchError:
|
|
102
|
+
return False
|
|
103
|
+
|
|
104
|
+
def _to_hex(self, value: str) -> str:
|
|
105
|
+
"""Converts a string value to its hexadecimal representation.
|
|
106
|
+
|
|
107
|
+
Parameters
|
|
108
|
+
----------
|
|
109
|
+
value
|
|
110
|
+
The string value to be converted to hexadecimal.
|
|
111
|
+
|
|
112
|
+
Returns
|
|
113
|
+
-------
|
|
114
|
+
The hexadecimal representation of the string value.
|
|
115
|
+
"""
|
|
116
|
+
return value.encode("utf-8").hex()
|
|
117
|
+
|
|
118
|
+
def _from_hex(self, hex: str) -> str:
|
|
119
|
+
"""Converts a hexadecimal string back to its original string
|
|
120
|
+
representation.
|
|
121
|
+
|
|
122
|
+
Parameters
|
|
123
|
+
----------
|
|
124
|
+
hex
|
|
125
|
+
The hexadecimal string to be converted back to the original string.
|
|
126
|
+
Returns
|
|
127
|
+
-------
|
|
128
|
+
The original string representation of the hexadecimal input.
|
|
129
|
+
"""
|
|
130
|
+
return bytes.fromhex(hex).decode("utf-8")
|
|
@@ -23,8 +23,8 @@ class ApiGenerateHandler(BaseHandler):
|
|
|
23
23
|
"the required packages first. \nThis can be done by installing "
|
|
24
24
|
"the \'api-generator\' extra: \n"
|
|
25
25
|
"- pip install alpha-python[api-generator]\n"
|
|
26
|
-
"- poetry add alpha-python --extras api-generator\n"
|
|
27
|
-
"- uv add alpha-python --extra api-generator"
|
|
26
|
+
"- poetry add --dev alpha-python --extras api-generator\n"
|
|
27
|
+
"- uv add --dev alpha-python --extra api-generator"
|
|
28
28
|
)
|
|
29
29
|
exit(1)
|
|
30
30
|
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"""Database Identity Provider implementation"""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
from alpha.domain.models.user import User
|
|
6
|
+
from alpha.factories.password_factory import PasswordFactory
|
|
7
|
+
from alpha.interfaces.sql_repository import SqlRepository
|
|
8
|
+
from alpha.interfaces.token_factory import TokenFactory
|
|
9
|
+
from alpha.interfaces.unit_of_work import UnitOfWork
|
|
10
|
+
from alpha.mixins.jwt_provider import JWTProviderMixin
|
|
11
|
+
from alpha.providers.models.credentials import PasswordCredentials
|
|
12
|
+
from alpha.providers.models.identity import Identity
|
|
13
|
+
from alpha import exceptions
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DatabaseProvider(JWTProviderMixin):
|
|
17
|
+
"""Database Identity Provider implementation."""
|
|
18
|
+
|
|
19
|
+
protocol = "database"
|
|
20
|
+
_token_factory: TokenFactory | None = None
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
uow: UnitOfWork,
|
|
25
|
+
token_factory: TokenFactory | None = None,
|
|
26
|
+
password_factory: PasswordFactory | None = None,
|
|
27
|
+
user_name_attribute: str = "username",
|
|
28
|
+
users_repository_name: str = "users",
|
|
29
|
+
) -> None:
|
|
30
|
+
"""Database Identity Provider implementation for user authentication
|
|
31
|
+
and management. This provider uses a database to store user information
|
|
32
|
+
and credentials, and provides methods for authenticating users,
|
|
33
|
+
retrieving user information, and changing passwords.
|
|
34
|
+
|
|
35
|
+
Parameters
|
|
36
|
+
----------
|
|
37
|
+
uow
|
|
38
|
+
Unit of work instance to manage database transactions
|
|
39
|
+
token_factory
|
|
40
|
+
Token factory instance to generate and validate tokens
|
|
41
|
+
password_factory, optional
|
|
42
|
+
Password factory instance to handle password hashing and
|
|
43
|
+
verification, by default None
|
|
44
|
+
user_name_attribute, optional
|
|
45
|
+
Attribute name to identify the user, by default "username"
|
|
46
|
+
users_repository_name, optional
|
|
47
|
+
Repository name for user entities, by default "users"
|
|
48
|
+
"""
|
|
49
|
+
self.uow = uow
|
|
50
|
+
self._token_factory = token_factory
|
|
51
|
+
self._password_factory = password_factory or PasswordFactory()
|
|
52
|
+
self._user_name_attribute = user_name_attribute
|
|
53
|
+
self._users_repository_name = users_repository_name
|
|
54
|
+
|
|
55
|
+
def authenticate(self, credentials: PasswordCredentials) -> Identity:
|
|
56
|
+
"""Authenticate a user using their credentials.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
credentials
|
|
61
|
+
Password credentials for the user
|
|
62
|
+
|
|
63
|
+
Returns
|
|
64
|
+
-------
|
|
65
|
+
Identity instance representing the authenticated user
|
|
66
|
+
"""
|
|
67
|
+
with self.uow:
|
|
68
|
+
users: SqlRepository[User] = getattr(
|
|
69
|
+
self.uow, self._users_repository_name
|
|
70
|
+
)
|
|
71
|
+
user = self._verify_password(
|
|
72
|
+
credentials=credentials, user_repository=users
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return Identity.from_user(user)
|
|
76
|
+
|
|
77
|
+
def get_user(self, subject: str) -> Identity:
|
|
78
|
+
"""Retrieve a user by their subject identifier.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
subject
|
|
83
|
+
The subject identifier of the user
|
|
84
|
+
|
|
85
|
+
Returns
|
|
86
|
+
-------
|
|
87
|
+
Identity instance representing the user
|
|
88
|
+
"""
|
|
89
|
+
with self.uow:
|
|
90
|
+
users: SqlRepository[User] = getattr(
|
|
91
|
+
self.uow, self._users_repository_name
|
|
92
|
+
)
|
|
93
|
+
user = self._get_user(
|
|
94
|
+
username=subject, user_repository=users, attribute_name="id"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
return Identity.from_user(user)
|
|
98
|
+
|
|
99
|
+
def change_password(
|
|
100
|
+
self, credentials: PasswordCredentials, new_password: str
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Change the password for a user.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
credentials
|
|
107
|
+
Password credentials for the user
|
|
108
|
+
new_password
|
|
109
|
+
The new password to set for the user
|
|
110
|
+
"""
|
|
111
|
+
with self.uow:
|
|
112
|
+
users: SqlRepository[User] = getattr(
|
|
113
|
+
self.uow, self._users_repository_name
|
|
114
|
+
)
|
|
115
|
+
user = self._verify_password(
|
|
116
|
+
credentials=credentials, user_repository=users
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self._update_user_password(user, new_password)
|
|
120
|
+
self.uow.commit()
|
|
121
|
+
|
|
122
|
+
def _get_user(
|
|
123
|
+
self,
|
|
124
|
+
username: str,
|
|
125
|
+
user_repository: SqlRepository[User],
|
|
126
|
+
attribute_name: str | None = None,
|
|
127
|
+
) -> User:
|
|
128
|
+
"""Retrieve a user by their username.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
username
|
|
133
|
+
The username of the user
|
|
134
|
+
user_repository
|
|
135
|
+
The repository to query for the user
|
|
136
|
+
attribute_name
|
|
137
|
+
The attribute name to use for querying the user, by default None.
|
|
138
|
+
If None, the provider's configured user_name_attribute will be
|
|
139
|
+
used.
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
User instance representing the retrieved user
|
|
144
|
+
|
|
145
|
+
Raises
|
|
146
|
+
------
|
|
147
|
+
exceptions.UserNotFoundException
|
|
148
|
+
If the user does not exist
|
|
149
|
+
"""
|
|
150
|
+
user = user_repository.get_one_or_none(
|
|
151
|
+
attr=attribute_name or self._user_name_attribute,
|
|
152
|
+
value=username,
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
if not user:
|
|
156
|
+
msg = (
|
|
157
|
+
f"User with '{attribute_name or self._user_name_attribute}'="
|
|
158
|
+
f"'{username}' does not exist"
|
|
159
|
+
)
|
|
160
|
+
logging.debug(msg)
|
|
161
|
+
# Disabled lines below for future implementation of logging and
|
|
162
|
+
# unit of work commit
|
|
163
|
+
# self.logger(msg=msg, level=LogLevel.DEBUG)
|
|
164
|
+
# self.uow.commit()
|
|
165
|
+
raise exceptions.UserNotFoundException(msg)
|
|
166
|
+
|
|
167
|
+
return user
|
|
168
|
+
|
|
169
|
+
def _verify_password(
|
|
170
|
+
self,
|
|
171
|
+
credentials: PasswordCredentials,
|
|
172
|
+
user_repository: SqlRepository[User],
|
|
173
|
+
) -> User:
|
|
174
|
+
"""Verify the password for a user.
|
|
175
|
+
|
|
176
|
+
Parameters
|
|
177
|
+
----------
|
|
178
|
+
credentials
|
|
179
|
+
Password credentials for the user
|
|
180
|
+
user_repository
|
|
181
|
+
The repository to query for the user
|
|
182
|
+
|
|
183
|
+
Returns
|
|
184
|
+
-------
|
|
185
|
+
User instance representing the authenticated user
|
|
186
|
+
|
|
187
|
+
Raises
|
|
188
|
+
------
|
|
189
|
+
exceptions.InvalidCredentialsException
|
|
190
|
+
If the provided credentials are invalid
|
|
191
|
+
exceptions.MissingPasswordException
|
|
192
|
+
If the user does not have a password set
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
user = self._get_user(credentials.username, user_repository)
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
if not self._password_factory.verify_password(
|
|
199
|
+
credentials.password, user.password
|
|
200
|
+
):
|
|
201
|
+
msg = (
|
|
202
|
+
f"The provided password for user "
|
|
203
|
+
f"'{getattr(user, self._user_name_attribute)}' is "
|
|
204
|
+
"incorrect"
|
|
205
|
+
)
|
|
206
|
+
logging.debug(msg)
|
|
207
|
+
# Disabled lines below for future implementation of logging and
|
|
208
|
+
# unit of work commit
|
|
209
|
+
# self.logger(msg=msg, level=LogLevel.DEBUG)
|
|
210
|
+
# self.uow.commit()
|
|
211
|
+
raise exceptions.InvalidCredentialsException(msg)
|
|
212
|
+
except exceptions.MissingPasswordException as exc:
|
|
213
|
+
msg = (
|
|
214
|
+
f"No password value to compare for "
|
|
215
|
+
f"'{getattr(user, self._user_name_attribute)}'"
|
|
216
|
+
)
|
|
217
|
+
logging.error(msg)
|
|
218
|
+
# Disabled lines below for future implementation of logging and
|
|
219
|
+
# unit of work commit
|
|
220
|
+
# self.logger(msg=msg, level=LogLevel.ERROR)
|
|
221
|
+
# self.uow.commit()
|
|
222
|
+
raise exceptions.MissingPasswordException(msg) from exc
|
|
223
|
+
|
|
224
|
+
return user
|
|
225
|
+
|
|
226
|
+
def _update_user_password(
|
|
227
|
+
self,
|
|
228
|
+
user: User,
|
|
229
|
+
new_password: str,
|
|
230
|
+
) -> None:
|
|
231
|
+
"""Change the password for a user.
|
|
232
|
+
|
|
233
|
+
Parameters
|
|
234
|
+
----------
|
|
235
|
+
user
|
|
236
|
+
User instance representing the user to update the password for
|
|
237
|
+
new_password
|
|
238
|
+
The new password to set for the user
|
|
239
|
+
user_repository
|
|
240
|
+
The repository to query for the user
|
|
241
|
+
"""
|
|
242
|
+
user.password = self._password_factory.hash_password(new_password)
|
|
@@ -27,43 +27,43 @@ DEFAULT_AD_MAPPINGS = {
|
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
AD_SEARCH_ATTRIBUTES = [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
30
|
+
"cn",
|
|
31
|
+
"generationQualifier",
|
|
32
|
+
"name",
|
|
33
|
+
"postalAddress",
|
|
34
|
+
"lastLogonTimestamp",
|
|
35
|
+
"mobile",
|
|
36
|
+
"postalCode",
|
|
37
|
+
"countryCode",
|
|
38
|
+
"company",
|
|
39
|
+
"displayName",
|
|
40
|
+
"o",
|
|
41
|
+
"st",
|
|
42
|
+
"ou",
|
|
43
|
+
"givenName",
|
|
44
|
+
"msExchUserCulture",
|
|
45
|
+
"l",
|
|
46
|
+
"initials",
|
|
47
|
+
"msTSLicenseVersion",
|
|
48
|
+
"memberOf",
|
|
49
|
+
"whenChanged",
|
|
50
|
+
"mailNickname",
|
|
51
|
+
"sn",
|
|
52
|
+
"street",
|
|
53
|
+
"accountExpires",
|
|
54
|
+
"uSNChanged",
|
|
55
|
+
"distinguishedName",
|
|
56
|
+
"whenCreated",
|
|
57
|
+
"sAMAccountName",
|
|
58
|
+
"c",
|
|
59
|
+
"employeeID",
|
|
60
|
+
"streetAddress",
|
|
61
|
+
"description",
|
|
62
|
+
"mail",
|
|
63
|
+
"title",
|
|
64
|
+
"department",
|
|
65
|
+
"co",
|
|
66
|
+
"personalTitle",
|
|
67
67
|
]
|
|
68
68
|
|
|
69
69
|
|
|
@@ -230,6 +230,32 @@ class Identity:
|
|
|
230
230
|
),
|
|
231
231
|
)
|
|
232
232
|
|
|
233
|
+
@classmethod
|
|
234
|
+
def from_user(cls, user: User) -> "Identity":
|
|
235
|
+
"""Instantiate an Identity from a User instance.
|
|
236
|
+
|
|
237
|
+
Parameters
|
|
238
|
+
----------
|
|
239
|
+
user
|
|
240
|
+
User object to create the Identity from.
|
|
241
|
+
|
|
242
|
+
Returns
|
|
243
|
+
-------
|
|
244
|
+
An Identity instance populated with data from the User object.
|
|
245
|
+
"""
|
|
246
|
+
return cls(
|
|
247
|
+
subject=str(user.id),
|
|
248
|
+
username=user.username,
|
|
249
|
+
email=user.email,
|
|
250
|
+
display_name=user.display_name,
|
|
251
|
+
groups=user.groups or [],
|
|
252
|
+
permissions=user.permissions or [],
|
|
253
|
+
claims={},
|
|
254
|
+
issued_at=datetime.now(tz=timezone.utc),
|
|
255
|
+
role=user.role,
|
|
256
|
+
admin=user.admin,
|
|
257
|
+
)
|
|
258
|
+
|
|
233
259
|
def update_from_user(self, user: User) -> None:
|
|
234
260
|
"""Update the Identity instance with data from a User instance.
|
|
235
261
|
|
|
@@ -244,11 +270,11 @@ class Identity:
|
|
|
244
270
|
if not self.display_name:
|
|
245
271
|
self.display_name = user.display_name
|
|
246
272
|
for permission in user.permissions or []:
|
|
247
|
-
|
|
248
|
-
self.permissions
|
|
273
|
+
self.permissions = self._append_on_sequence(
|
|
274
|
+
self.permissions, permission
|
|
275
|
+
)
|
|
249
276
|
for group in user.groups or []:
|
|
250
|
-
|
|
251
|
-
self.groups.append(group) # type: ignore
|
|
277
|
+
self.groups = self._append_on_sequence(self.groups, group)
|
|
252
278
|
self.role = user.role
|
|
253
279
|
self.admin = user.admin
|
|
254
280
|
|
|
@@ -379,11 +405,33 @@ class Identity:
|
|
|
379
405
|
groups: list[str] = []
|
|
380
406
|
for item in entry.get("memberOf", []):
|
|
381
407
|
group = (
|
|
382
|
-
item.replace(
|
|
383
|
-
.split(
|
|
384
|
-
.replace(
|
|
385
|
-
.replace(
|
|
386
|
-
.replace(
|
|
408
|
+
item.replace("\\,", ";")
|
|
409
|
+
.split(",")[0]
|
|
410
|
+
.replace(";", ",")
|
|
411
|
+
.replace("CN=", "")
|
|
412
|
+
.replace("cn=", "")
|
|
387
413
|
)
|
|
388
414
|
groups.append(group)
|
|
389
415
|
return groups
|
|
416
|
+
|
|
417
|
+
def _append_on_sequence(
|
|
418
|
+
self, sequence: Sequence[str], item: str
|
|
419
|
+
) -> Sequence[str]:
|
|
420
|
+
"""Helper method to append an item to a sequence if it's not already
|
|
421
|
+
present.
|
|
422
|
+
|
|
423
|
+
Parameters
|
|
424
|
+
----------
|
|
425
|
+
sequence
|
|
426
|
+
The original sequence to append to.
|
|
427
|
+
item
|
|
428
|
+
The item to append if not already in the sequence.
|
|
429
|
+
|
|
430
|
+
Returns
|
|
431
|
+
-------
|
|
432
|
+
A new sequence with the item appended if it was not already
|
|
433
|
+
present.
|
|
434
|
+
"""
|
|
435
|
+
if item not in sequence:
|
|
436
|
+
return list(sequence) + [item]
|
|
437
|
+
return sequence
|