alpha-python 0.7.4__tar.gz → 0.7.6__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.7.4 → alpha_python-0.7.6}/PKG-INFO +2 -2
- {alpha_python-0.7.4 → alpha_python-0.7.6}/pyproject.toml +2 -2
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/__init__.py +2 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/adapters/sqla_unit_of_work.py +11 -3
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/domain/__init__.py +2 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/domain/models/__init__.py +2 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/domain/models/group.py +41 -1
- alpha_python-0.7.6/src/alpha/domain/models/permission.py +110 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/domain/models/user.py +66 -2
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/mixins/jwt_provider.py +4 -1
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/database_provider.py +8 -2
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/ldap_provider.py +18 -1
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/models/identity.py +52 -12
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/oidc_provider.py +20 -2
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/services/authentication_service.py +7 -2
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha_python.egg-info/PKG-INFO +2 -2
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha_python.egg-info/SOURCES.txt +1 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/LICENSE +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/README.md +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/setup.cfg +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/adapters/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/adapters/rest_api_unit_of_work.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/cli.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/containers/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/containers/container.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/domain/models/base_model.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/domain/models/life_cycle_base.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/domain/models/role.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/encoder.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/exceptions.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/_type_conversion_matrix.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/_type_mapping.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/class_factories.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/default_field_factory.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/field_iterator.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/jwt_factory.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/logging_handler_factory.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/model_class_factory.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/models/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/models/factory_classes.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/password_factory.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/request_factory.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/response_factory.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/factories/type_factories.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/api_generate_handler.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/api_run_handler.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/base_handler.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/gen-code.sh +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/models/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/models/argument.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/models/command.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/models/section.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/models/subparser.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/run-api.sh +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/Dockerfile.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/README.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/__init__model.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/__init__test.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/__main__.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/base_model.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/controller.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/controller_test.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/dockerignore.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/encoder.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/git_push.sh.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/gitignore.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/model.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/openapi.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/param_type.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/requirements.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/security_controller_.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/setup.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/test-requirements.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/tox.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/travis.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/typing_utils.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/handlers/templates/python-flask/util.mustache +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/connectors/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/connectors/ldap_connector.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/connectors/oidc_connector.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/connectors/sql_alchemy.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/databases/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/databases/sql_alchemy.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/models/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/models/filter_operators.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/models/json_patch.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/models/order_by.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/models/query_clause.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/infra/models/search_filter.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/api_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/attrs_instance.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/dataclass_instance.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/factories.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/handler.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/http_client.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/openapi_model.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/patchable.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/providers.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/pydantic_instance.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/refresh_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/sql_database.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/sql_mapper.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/sql_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/token_factory.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/unit_of_work.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/interfaces/updatable.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/mixins/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/mixins/group_lifecycle.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/mixins/user_lifecycle.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/api_key_provider.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/models/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/models/credentials.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/providers/models/token.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/py.typed +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/models/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/models/repository_model.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/refresh/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/refresh/cache_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/refresh/database_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/refresh/file_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/refresh/memory_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/rest_api_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/repositories/sql_alchemy_repository.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/services/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/services/models/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/services/models/cookie.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/services/user_lifecycle_management.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/_http_codes.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/cookie.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/is_attrs.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/is_pydantic.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/logging_configurator.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/logging_level_checker.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/__init__.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/container.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/exceptions.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/models.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/orm.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/response.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/openapi_test/service.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/request_headers.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/response_object.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/secret_generator.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/verify_identity.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha/utils/version_checker.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha_python.egg-info/dependency_links.txt +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha_python.egg-info/entry_points.txt +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha_python.egg-info/requires.txt +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/src/alpha_python.egg-info/top_level.txt +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/tests/test_cli.py +0 -0
- {alpha_python-0.7.4 → alpha_python-0.7.6}/tests/test_encoder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpha-python
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.6
|
|
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
|
License-Expression: MIT
|
|
@@ -16,7 +16,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
-
Requires-Python:
|
|
19
|
+
Requires-Python: >=3.11
|
|
20
20
|
Description-Content-Type: text/markdown
|
|
21
21
|
License-File: LICENSE
|
|
22
22
|
Requires-Dist: attrs>=25.4.0
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "alpha-python"
|
|
3
|
-
version = "0.7.
|
|
3
|
+
version = "0.7.6"
|
|
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,7 @@ classifiers = [
|
|
|
18
18
|
]
|
|
19
19
|
license = "MIT"
|
|
20
20
|
license-files = ['LICENSE']
|
|
21
|
-
requires-python = ">=3.11
|
|
21
|
+
requires-python = ">=3.11"
|
|
22
22
|
dependencies = [
|
|
23
23
|
"attrs>=25.4.0",
|
|
24
24
|
"gunicorn>=23.0.0",
|
|
@@ -7,6 +7,7 @@ from alpha.factories.logging_handler_factory import LoggingHandlerFactory
|
|
|
7
7
|
from alpha.factories.model_class_factory import ModelClassFactory
|
|
8
8
|
from alpha.domain.models.user import User
|
|
9
9
|
from alpha.domain.models.group import Group
|
|
10
|
+
from alpha.domain.models.permission import Permission
|
|
10
11
|
from alpha.domain.models.role import Role
|
|
11
12
|
from alpha.domain.models.base_model import (
|
|
12
13
|
BaseDomainModel,
|
|
@@ -123,6 +124,7 @@ __all__ = [
|
|
|
123
124
|
"LifeCycleBase",
|
|
124
125
|
"User",
|
|
125
126
|
"Group",
|
|
127
|
+
"Permission",
|
|
126
128
|
"Role",
|
|
127
129
|
"OIDCConnector",
|
|
128
130
|
"KeyCloakOIDCConnector",
|
|
@@ -78,14 +78,22 @@ class SqlAlchemyUnitOfWork:
|
|
|
78
78
|
|
|
79
79
|
return self
|
|
80
80
|
|
|
81
|
-
def __exit__(self,
|
|
82
|
-
"""Finalize the Unit of Work context.
|
|
81
|
+
def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
|
|
82
|
+
"""Finalize the Unit of Work context.
|
|
83
|
+
|
|
84
|
+
Roll back only when leaving the context because of an exception.
|
|
85
|
+
Rolling back after a successful commit can expire ORM state and may
|
|
86
|
+
trigger DetachedInstanceError once the session is closed/detached.
|
|
87
|
+
"""
|
|
83
88
|
if not self._session:
|
|
84
89
|
raise exceptions.DatabaseSessionError(
|
|
85
90
|
"No active database session is defined"
|
|
86
91
|
)
|
|
92
|
+
|
|
93
|
+
if exc_type is not None:
|
|
94
|
+
self._session.rollback()
|
|
95
|
+
|
|
87
96
|
self._session.close()
|
|
88
|
-
self.rollback()
|
|
89
97
|
self._session = None # type: ignore
|
|
90
98
|
|
|
91
99
|
def commit(self) -> None:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from alpha.domain.models.user import User
|
|
2
2
|
from alpha.domain.models.group import Group
|
|
3
|
+
from alpha.domain.models.permission import Permission
|
|
3
4
|
from alpha.domain.models.role import Role
|
|
4
5
|
from alpha.domain.models.base_model import (
|
|
5
6
|
BaseDomainModel,
|
|
@@ -17,5 +18,6 @@ __all__ = [
|
|
|
17
18
|
"LifeCycleBase",
|
|
18
19
|
"User",
|
|
19
20
|
"Group",
|
|
21
|
+
"Permission",
|
|
20
22
|
"Role",
|
|
21
23
|
]
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from alpha.domain.models.user import User
|
|
2
2
|
from alpha.domain.models.group import Group
|
|
3
|
+
from alpha.domain.models.permission import Permission
|
|
3
4
|
from alpha.domain.models.role import Role
|
|
4
5
|
from alpha.domain.models.base_model import (
|
|
5
6
|
BaseDomainModel,
|
|
@@ -17,5 +18,6 @@ __all__ = [
|
|
|
17
18
|
"LifeCycleBase",
|
|
18
19
|
"User",
|
|
19
20
|
"Group",
|
|
21
|
+
"Permission",
|
|
20
22
|
"Role",
|
|
21
23
|
]
|
|
@@ -39,6 +39,20 @@ class Group(LifeCycleBase, BaseDomainModel):
|
|
|
39
39
|
permissions: list[str] = field(default_factory=list) # type: ignore
|
|
40
40
|
is_active: bool = True
|
|
41
41
|
|
|
42
|
+
def __str__(self) -> str:
|
|
43
|
+
"""Return the name-based representation of the Group instance."""
|
|
44
|
+
return self.name or ""
|
|
45
|
+
|
|
46
|
+
def __repr__(self) -> str:
|
|
47
|
+
"""Return the official string representation of the Group instance."""
|
|
48
|
+
return (
|
|
49
|
+
"Group("
|
|
50
|
+
f"id={self.id!r}, "
|
|
51
|
+
f"name={self.name!r}, "
|
|
52
|
+
f"description={self.description!r}"
|
|
53
|
+
")"
|
|
54
|
+
)
|
|
55
|
+
|
|
42
56
|
def to_dict(self) -> dict[str, Any]:
|
|
43
57
|
"""Convert the Group instance to a dictionary.
|
|
44
58
|
|
|
@@ -47,12 +61,38 @@ class Group(LifeCycleBase, BaseDomainModel):
|
|
|
47
61
|
dict[str, Any]
|
|
48
62
|
A dictionary representation of the Group instance.
|
|
49
63
|
"""
|
|
64
|
+
permissions = cast(
|
|
65
|
+
list[str | dict[str, Any]],
|
|
66
|
+
[
|
|
67
|
+
p.to_dict() if hasattr(p, "to_dict") else p # type: ignore
|
|
68
|
+
for p in self.permissions
|
|
69
|
+
],
|
|
70
|
+
)
|
|
71
|
+
created_at = (
|
|
72
|
+
self.created_at.isoformat()
|
|
73
|
+
if hasattr(self, "created_at") and self.created_at is not None
|
|
74
|
+
else None
|
|
75
|
+
)
|
|
76
|
+
modified_at = (
|
|
77
|
+
self.modified_at.isoformat()
|
|
78
|
+
if hasattr(self, "modified_at") and self.modified_at is not None
|
|
79
|
+
else None
|
|
80
|
+
)
|
|
81
|
+
|
|
50
82
|
return {
|
|
51
83
|
"id": self.id,
|
|
52
84
|
"name": self.name,
|
|
53
85
|
"description": self.description,
|
|
54
|
-
"permissions":
|
|
86
|
+
"permissions": permissions,
|
|
55
87
|
"is_active": self.is_active,
|
|
88
|
+
"created_by": self.created_by
|
|
89
|
+
if hasattr(self, "created_by") and self.created_by is not None
|
|
90
|
+
else None,
|
|
91
|
+
"created_at": created_at,
|
|
92
|
+
"modified_by": self.modified_by
|
|
93
|
+
if hasattr(self, "modified_by") and self.modified_by is not None
|
|
94
|
+
else None,
|
|
95
|
+
"modified_at": modified_at,
|
|
56
96
|
}
|
|
57
97
|
|
|
58
98
|
def update(self, obj: DomainModel) -> DomainModel:
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from typing import Any, cast
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from alpha.domain.models.base_model import BaseDomainModel, DomainModel
|
|
7
|
+
from alpha.domain.models.life_cycle_base import LifeCycleBase
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(kw_only=True)
|
|
11
|
+
class Permission(LifeCycleBase, BaseDomainModel):
|
|
12
|
+
"""Permission class representing a system permission. This class is used to
|
|
13
|
+
define and manage permissions. User and group permissions are handled
|
|
14
|
+
through this class.
|
|
15
|
+
|
|
16
|
+
Attributes
|
|
17
|
+
----------
|
|
18
|
+
id
|
|
19
|
+
Unique identifier for the permission. Can be a UUID, integer, or
|
|
20
|
+
string.
|
|
21
|
+
name
|
|
22
|
+
Name of the permission.
|
|
23
|
+
description
|
|
24
|
+
Description for the purpose of the permission.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
id: UUID | int | str | None = None
|
|
28
|
+
name: str
|
|
29
|
+
description: str = ""
|
|
30
|
+
|
|
31
|
+
def __str__(self) -> str:
|
|
32
|
+
"""Return the name of the permission.
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
self.name value
|
|
37
|
+
"""
|
|
38
|
+
return self.name
|
|
39
|
+
|
|
40
|
+
def __repr__(self) -> str:
|
|
41
|
+
"""Return the official string representation of the object.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
A string representing the Permission instance.
|
|
46
|
+
"""
|
|
47
|
+
return (
|
|
48
|
+
f"Permission(name={self.name!r}, description={self.description!r})"
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def to_dict(self) -> dict[str, Any]:
|
|
52
|
+
"""Convert the Permission instance to a dictionary.
|
|
53
|
+
|
|
54
|
+
Returns
|
|
55
|
+
-------
|
|
56
|
+
dict[str, Any]
|
|
57
|
+
A dictionary representation of the Permission instance.
|
|
58
|
+
"""
|
|
59
|
+
created_at = (
|
|
60
|
+
self.created_at.isoformat()
|
|
61
|
+
if hasattr(self, "created_at") and self.created_at is not None
|
|
62
|
+
else None
|
|
63
|
+
)
|
|
64
|
+
modified_at = (
|
|
65
|
+
self.modified_at.isoformat()
|
|
66
|
+
if hasattr(self, "modified_at") and self.modified_at is not None
|
|
67
|
+
else None
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
"id": self.id,
|
|
72
|
+
"name": self.name,
|
|
73
|
+
"description": self.description,
|
|
74
|
+
"created_by": self.created_by
|
|
75
|
+
if hasattr(self, "created_by") and self.created_by is not None
|
|
76
|
+
else None,
|
|
77
|
+
"created_at": created_at,
|
|
78
|
+
"modified_by": self.modified_by
|
|
79
|
+
if hasattr(self, "modified_by") and self.modified_by is not None
|
|
80
|
+
else None,
|
|
81
|
+
"modified_at": modified_at,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
def update(self, obj: DomainModel) -> DomainModel:
|
|
85
|
+
"""Update the Permission instance with data from another Permission
|
|
86
|
+
instance.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
obj
|
|
91
|
+
Permission object to update from.
|
|
92
|
+
|
|
93
|
+
Returns
|
|
94
|
+
-------
|
|
95
|
+
DomainModel
|
|
96
|
+
The updated instance of the Permission.
|
|
97
|
+
|
|
98
|
+
Raises
|
|
99
|
+
------
|
|
100
|
+
TypeError
|
|
101
|
+
If the provided object is not a Permission instance.
|
|
102
|
+
"""
|
|
103
|
+
if not isinstance(obj, Permission):
|
|
104
|
+
raise TypeError("Permission.update expects a Permission instance.")
|
|
105
|
+
|
|
106
|
+
self.name = obj.name
|
|
107
|
+
self.description = obj.description
|
|
108
|
+
self.modified_at = datetime.now(tz=timezone.utc)
|
|
109
|
+
|
|
110
|
+
return cast(DomainModel, self)
|
|
@@ -72,6 +72,32 @@ class User(LifeCycleBase, BaseDomainModel):
|
|
|
72
72
|
is_active: bool = True
|
|
73
73
|
admin: bool = False
|
|
74
74
|
|
|
75
|
+
def __str__(self) -> str:
|
|
76
|
+
"""Return the username of the user.
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
self.username value
|
|
81
|
+
"""
|
|
82
|
+
return self.username or ""
|
|
83
|
+
|
|
84
|
+
def __repr__(self) -> str:
|
|
85
|
+
"""Return the official string representation of the object.
|
|
86
|
+
|
|
87
|
+
Returns
|
|
88
|
+
-------
|
|
89
|
+
A string representing the User instance.
|
|
90
|
+
"""
|
|
91
|
+
return (
|
|
92
|
+
"User("
|
|
93
|
+
f"id={self.id}, "
|
|
94
|
+
f"username={self.username!r}, "
|
|
95
|
+
f"display_name={self.display_name!r}, "
|
|
96
|
+
f"permissions={self.permissions!r}, "
|
|
97
|
+
f"groups={self.groups!r}"
|
|
98
|
+
")"
|
|
99
|
+
)
|
|
100
|
+
|
|
75
101
|
@classmethod
|
|
76
102
|
def from_identity(cls, identity: Identity) -> Self:
|
|
77
103
|
"""Create a User instance from an Identity instance.
|
|
@@ -101,6 +127,35 @@ class User(LifeCycleBase, BaseDomainModel):
|
|
|
101
127
|
dict[str, Any]
|
|
102
128
|
A dictionary representation of the User instance.
|
|
103
129
|
"""
|
|
130
|
+
permissions = cast(
|
|
131
|
+
list[str | dict[str, Any]],
|
|
132
|
+
[
|
|
133
|
+
permission.to_dict() # type: ignore
|
|
134
|
+
if hasattr(permission, "to_dict")
|
|
135
|
+
else permission
|
|
136
|
+
for permission in self.permissions
|
|
137
|
+
],
|
|
138
|
+
)
|
|
139
|
+
groups = cast(
|
|
140
|
+
list[str | dict[str, Any]],
|
|
141
|
+
[
|
|
142
|
+
group.to_dict() # type: ignore
|
|
143
|
+
if hasattr(group, "to_dict")
|
|
144
|
+
else group
|
|
145
|
+
for group in self.groups
|
|
146
|
+
],
|
|
147
|
+
)
|
|
148
|
+
created_at = (
|
|
149
|
+
self.created_at.isoformat()
|
|
150
|
+
if hasattr(self, "created_at") and self.created_at is not None
|
|
151
|
+
else None
|
|
152
|
+
)
|
|
153
|
+
modified_at = (
|
|
154
|
+
self.modified_at.isoformat()
|
|
155
|
+
if hasattr(self, "modified_at") and self.modified_at is not None
|
|
156
|
+
else None
|
|
157
|
+
)
|
|
158
|
+
|
|
104
159
|
return {
|
|
105
160
|
"id": self.id,
|
|
106
161
|
"username": self.username,
|
|
@@ -109,10 +164,18 @@ class User(LifeCycleBase, BaseDomainModel):
|
|
|
109
164
|
"email": self.email,
|
|
110
165
|
"phone": self.phone,
|
|
111
166
|
"display_name": self.display_name,
|
|
112
|
-
"permissions":
|
|
113
|
-
"groups":
|
|
167
|
+
"permissions": permissions,
|
|
168
|
+
"groups": groups,
|
|
114
169
|
"is_active": self.is_active,
|
|
115
170
|
"admin": self.admin,
|
|
171
|
+
"created_by": self.created_by
|
|
172
|
+
if hasattr(self, "created_by") and self.created_by is not None
|
|
173
|
+
else None,
|
|
174
|
+
"created_at": created_at,
|
|
175
|
+
"modified_by": self.modified_by
|
|
176
|
+
if hasattr(self, "modified_by") and self.modified_by is not None
|
|
177
|
+
else None,
|
|
178
|
+
"modified_at": modified_at,
|
|
116
179
|
}
|
|
117
180
|
|
|
118
181
|
def update(self, obj: DomainModel) -> DomainModel:
|
|
@@ -140,4 +203,5 @@ class User(LifeCycleBase, BaseDomainModel):
|
|
|
140
203
|
self.modified_at = datetime.now(tz=timezone.utc)
|
|
141
204
|
self.is_active = obj.is_active
|
|
142
205
|
self.admin = obj.admin
|
|
206
|
+
|
|
143
207
|
return cast(DomainModel, self)
|
|
@@ -12,6 +12,7 @@ class JWTProviderMixin:
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
token_factory: TokenFactory | None = None
|
|
15
|
+
_identity_model: type[Identity] = Identity
|
|
15
16
|
|
|
16
17
|
def validate(self, token: Token) -> Identity:
|
|
17
18
|
"""Validate a token and return the associated identity
|
|
@@ -43,7 +44,7 @@ class JWTProviderMixin:
|
|
|
43
44
|
"Token payload does not contain mandatory 'subject' field"
|
|
44
45
|
)
|
|
45
46
|
|
|
46
|
-
return
|
|
47
|
+
return self._identity_model.from_dict(payload)
|
|
47
48
|
|
|
48
49
|
def issue_token(self, identity: Identity) -> Token:
|
|
49
50
|
"""Issue a token for the given identity
|
|
@@ -57,6 +58,8 @@ class JWTProviderMixin:
|
|
|
57
58
|
-------
|
|
58
59
|
Token object
|
|
59
60
|
"""
|
|
61
|
+
identity = self._identity_model.from_dict(identity.to_dict())
|
|
62
|
+
|
|
60
63
|
if not self.token_factory:
|
|
61
64
|
raise exceptions.MissingDependencyException(
|
|
62
65
|
"Token factory is not configured"
|
|
@@ -18,12 +18,14 @@ class DatabaseProvider(JWTProviderMixin):
|
|
|
18
18
|
|
|
19
19
|
protocol = "database"
|
|
20
20
|
token_factory: TokenFactory | None = None
|
|
21
|
+
_identity_model: type[Identity] = Identity
|
|
21
22
|
|
|
22
23
|
def __init__(
|
|
23
24
|
self,
|
|
24
25
|
uow: UnitOfWork,
|
|
25
26
|
token_factory: TokenFactory | None = None,
|
|
26
27
|
password_factory: PasswordFactory | None = None,
|
|
28
|
+
identity_model: type[Identity] = Identity,
|
|
27
29
|
user_name_attribute: str = "username",
|
|
28
30
|
users_repository_name: str = "users",
|
|
29
31
|
) -> None:
|
|
@@ -42,6 +44,9 @@ class DatabaseProvider(JWTProviderMixin):
|
|
|
42
44
|
Password factory instance to handle password hashing and
|
|
43
45
|
verification, by default None. If None, a default PasswordFactory
|
|
44
46
|
will be used.
|
|
47
|
+
identity_model
|
|
48
|
+
Identity model class to use for representing users, by default
|
|
49
|
+
Identity
|
|
45
50
|
user_name_attribute
|
|
46
51
|
Attribute name to identify the user, by default "username"
|
|
47
52
|
users_repository_name
|
|
@@ -52,6 +57,7 @@ class DatabaseProvider(JWTProviderMixin):
|
|
|
52
57
|
self._password_factory = password_factory or PasswordFactory()
|
|
53
58
|
self._user_name_attribute = user_name_attribute
|
|
54
59
|
self._users_repository_name = users_repository_name
|
|
60
|
+
self._identity_model = identity_model
|
|
55
61
|
|
|
56
62
|
def authenticate(self, credentials: PasswordCredentials) -> Identity:
|
|
57
63
|
"""Authenticate a user using their credentials.
|
|
@@ -73,7 +79,7 @@ class DatabaseProvider(JWTProviderMixin):
|
|
|
73
79
|
credentials=credentials, user_repository=users
|
|
74
80
|
)
|
|
75
81
|
|
|
76
|
-
return
|
|
82
|
+
return self._identity_model.from_user(user)
|
|
77
83
|
|
|
78
84
|
def get_user(self, subject: str) -> Identity:
|
|
79
85
|
"""Retrieve a user by their subject identifier.
|
|
@@ -95,7 +101,7 @@ class DatabaseProvider(JWTProviderMixin):
|
|
|
95
101
|
username=subject, user_repository=users, attribute_name="id"
|
|
96
102
|
)
|
|
97
103
|
|
|
98
|
-
return
|
|
104
|
+
return self._identity_model.from_user(user)
|
|
99
105
|
|
|
100
106
|
def change_password(
|
|
101
107
|
self, credentials: PasswordCredentials, new_password: str
|
|
@@ -33,6 +33,7 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
33
33
|
|
|
34
34
|
protocol = "ldap"
|
|
35
35
|
token_factory: TokenFactory | None = None
|
|
36
|
+
_identity_model: type[Identity] = Identity
|
|
36
37
|
|
|
37
38
|
def __init__(
|
|
38
39
|
self,
|
|
@@ -45,8 +46,10 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
45
46
|
populate_groups: bool = True,
|
|
46
47
|
populate_permissions: bool = False,
|
|
47
48
|
populate_claims: bool = True,
|
|
49
|
+
flatten_claims: bool = True,
|
|
48
50
|
auto_connect: bool = True,
|
|
49
51
|
change_password_supported: bool = False,
|
|
52
|
+
identity_model: type[Identity] = Identity,
|
|
50
53
|
additional_connector_params: dict[str, Any] | None = None,
|
|
51
54
|
) -> None:
|
|
52
55
|
"""Initialize LDAPProvider.
|
|
@@ -71,11 +74,17 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
71
74
|
Whether to populate permissions in the Identity, by default False
|
|
72
75
|
populate_claims
|
|
73
76
|
Whether to populate claims in the Identity, by default True
|
|
77
|
+
flatten_claims
|
|
78
|
+
Whether to flatten single-value claims in the Identity, by default
|
|
79
|
+
True
|
|
74
80
|
auto_connect
|
|
75
81
|
Whether to automatically connect using the connector, by default
|
|
76
82
|
True
|
|
77
83
|
change_password_supported
|
|
78
84
|
Whether the provider supports changing passwords, by default False
|
|
85
|
+
identity_model
|
|
86
|
+
Identity model class to use for representing users, by default
|
|
87
|
+
Identity
|
|
79
88
|
additional_connector_params
|
|
80
89
|
Additional parameters to pass to the LDAP connection, by default
|
|
81
90
|
{"receive_timeout": 5}
|
|
@@ -89,8 +98,10 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
89
98
|
self._populate_groups = populate_groups
|
|
90
99
|
self._populate_permissions = populate_permissions
|
|
91
100
|
self._populate_claims = populate_claims
|
|
101
|
+
self._flatten_claims = flatten_claims
|
|
92
102
|
self._auto_connect = auto_connect
|
|
93
103
|
self._change_password_supported = change_password_supported
|
|
104
|
+
self._identity_model = identity_model
|
|
94
105
|
self._additional_connector_params = additional_connector_params or {
|
|
95
106
|
"receive_timeout": 5
|
|
96
107
|
}
|
|
@@ -291,12 +302,13 @@ class LDAPProvider(JWTProviderMixin):
|
|
|
291
302
|
Identity object
|
|
292
303
|
"""
|
|
293
304
|
entry_dict = cast(dict[str, Any], entry.entry_attributes_as_dict) # type: ignore
|
|
294
|
-
identity =
|
|
305
|
+
identity = self._identity_model.from_ldap_dict(
|
|
295
306
|
entry=entry_dict,
|
|
296
307
|
mappings=self._identity_mappings,
|
|
297
308
|
populate_claims=self._populate_claims,
|
|
298
309
|
populate_groups=self._populate_groups,
|
|
299
310
|
populate_permissions=self._populate_permissions,
|
|
311
|
+
flatten_claims=self._flatten_claims,
|
|
300
312
|
)
|
|
301
313
|
return identity
|
|
302
314
|
|
|
@@ -324,6 +336,7 @@ class ADProvider(LDAPProvider):
|
|
|
324
336
|
populate_groups: bool = True,
|
|
325
337
|
populate_permissions: bool = False,
|
|
326
338
|
populate_claims: bool = True,
|
|
339
|
+
flatten_claims: bool = True,
|
|
327
340
|
auto_connect: bool = True,
|
|
328
341
|
change_password_supported: bool = False,
|
|
329
342
|
) -> None:
|
|
@@ -356,6 +369,9 @@ class ADProvider(LDAPProvider):
|
|
|
356
369
|
populate_claims
|
|
357
370
|
Whether to populate claims on the :class:`Identity`, by default
|
|
358
371
|
True.
|
|
372
|
+
flatten_claims
|
|
373
|
+
Whether to flatten single-value claims in the Identity, by default
|
|
374
|
+
True
|
|
359
375
|
auto_connect
|
|
360
376
|
Whether to automatically open the LDAP connection on first use, by
|
|
361
377
|
default True.
|
|
@@ -373,6 +389,7 @@ class ADProvider(LDAPProvider):
|
|
|
373
389
|
populate_groups=populate_groups,
|
|
374
390
|
populate_permissions=populate_permissions,
|
|
375
391
|
populate_claims=populate_claims,
|
|
392
|
+
flatten_claims=flatten_claims,
|
|
376
393
|
auto_connect=auto_connect,
|
|
377
394
|
change_password_supported=change_password_supported,
|
|
378
395
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Mapping, Any, Self, Sequence, TYPE_CHECKING
|
|
4
|
+
from typing import Mapping, Any, Self, Sequence, TYPE_CHECKING, cast
|
|
5
5
|
from datetime import datetime, timezone
|
|
6
6
|
|
|
7
7
|
from alpha.domain.models.group import Group
|
|
@@ -125,7 +125,7 @@ class Identity:
|
|
|
125
125
|
username: str | None
|
|
126
126
|
email: str | None
|
|
127
127
|
display_name: str | None
|
|
128
|
-
groups: Sequence[str
|
|
128
|
+
groups: Sequence[str]
|
|
129
129
|
permissions: Sequence[str]
|
|
130
130
|
claims: Mapping[str, Any]
|
|
131
131
|
issued_at: datetime
|
|
@@ -160,6 +160,7 @@ class Identity:
|
|
|
160
160
|
populate_groups: bool = True,
|
|
161
161
|
populate_permissions: bool = False,
|
|
162
162
|
populate_claims: bool = True,
|
|
163
|
+
flatten_claims: bool = False,
|
|
163
164
|
) -> Identity:
|
|
164
165
|
"""Instantiate an Identity from an LDAP entry dictionary.
|
|
165
166
|
|
|
@@ -178,6 +179,9 @@ class Identity:
|
|
|
178
179
|
populate_claims
|
|
179
180
|
Whether to populate the claims dictionary from the LDAP entry, by
|
|
180
181
|
default True
|
|
182
|
+
flatten_claims
|
|
183
|
+
Whether to replace claim lists containing a single value with that
|
|
184
|
+
value, by default False
|
|
181
185
|
|
|
182
186
|
Returns
|
|
183
187
|
-------
|
|
@@ -188,6 +192,12 @@ class Identity:
|
|
|
188
192
|
if not username:
|
|
189
193
|
username = cls._get_key(entry, mappings["subject"])
|
|
190
194
|
|
|
195
|
+
claims = (
|
|
196
|
+
cls._remove_password_from_claims(entry) if populate_claims else {}
|
|
197
|
+
)
|
|
198
|
+
if populate_claims and flatten_claims:
|
|
199
|
+
claims = cls.flatten_single_value_claims(claims)
|
|
200
|
+
|
|
191
201
|
return cls(
|
|
192
202
|
subject=cls._get_key(entry, mappings["subject"], ""),
|
|
193
203
|
username=username,
|
|
@@ -204,11 +214,7 @@ class Identity:
|
|
|
204
214
|
if populate_permissions
|
|
205
215
|
else []
|
|
206
216
|
),
|
|
207
|
-
claims=
|
|
208
|
-
cls._remove_password_from_claims(entry)
|
|
209
|
-
if populate_claims
|
|
210
|
-
else {}
|
|
211
|
-
),
|
|
217
|
+
claims=claims,
|
|
212
218
|
issued_at=datetime.now(tz=timezone.utc),
|
|
213
219
|
)
|
|
214
220
|
|
|
@@ -268,13 +274,16 @@ class Identity:
|
|
|
268
274
|
An Identity instance populated with data from the User object.
|
|
269
275
|
"""
|
|
270
276
|
subject = str(user.id) if user.id else user.username
|
|
277
|
+
|
|
271
278
|
return cls(
|
|
272
279
|
subject=subject, # type: ignore
|
|
273
280
|
username=user.username,
|
|
274
281
|
email=user.email,
|
|
275
282
|
display_name=user.display_name,
|
|
276
|
-
groups=user.groups or [],
|
|
277
|
-
permissions=
|
|
283
|
+
groups=[str(group) for group in user.groups or []],
|
|
284
|
+
permissions=[
|
|
285
|
+
str(permission) for permission in user.permissions or []
|
|
286
|
+
],
|
|
278
287
|
claims={},
|
|
279
288
|
issued_at=datetime.now(tz=timezone.utc),
|
|
280
289
|
role=user.role, # type: ignore
|
|
@@ -318,10 +327,10 @@ class Identity:
|
|
|
318
327
|
self.display_name = user.display_name
|
|
319
328
|
for permission in user.permissions or []:
|
|
320
329
|
self.permissions = self._append_on_sequence(
|
|
321
|
-
self.permissions, permission
|
|
330
|
+
self.permissions, str(permission)
|
|
322
331
|
)
|
|
323
332
|
for group in user.groups or []:
|
|
324
|
-
self.groups = self._append_on_sequence(self.groups, group)
|
|
333
|
+
self.groups = self._append_on_sequence(self.groups, str(group))
|
|
325
334
|
self.role = user.role # type: ignore
|
|
326
335
|
self.admin = user.admin
|
|
327
336
|
|
|
@@ -337,7 +346,7 @@ class Identity:
|
|
|
337
346
|
for group in groups:
|
|
338
347
|
for permission in group.permissions or []:
|
|
339
348
|
self.permissions = self._append_on_sequence(
|
|
340
|
-
self.permissions, permission
|
|
349
|
+
self.permissions, str(permission)
|
|
341
350
|
)
|
|
342
351
|
|
|
343
352
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -430,6 +439,37 @@ class Identity:
|
|
|
430
439
|
}
|
|
431
440
|
return filtered_claims
|
|
432
441
|
|
|
442
|
+
@staticmethod
|
|
443
|
+
def flatten_single_value_claims(
|
|
444
|
+
claims: Mapping[str, Any],
|
|
445
|
+
) -> Mapping[str, Any]:
|
|
446
|
+
"""Replace single-value claim lists with their contained value.
|
|
447
|
+
|
|
448
|
+
Password-related claims (keys containing "password", case-insensitive)
|
|
449
|
+
are removed before flattening.
|
|
450
|
+
|
|
451
|
+
Parameters
|
|
452
|
+
----------
|
|
453
|
+
claims
|
|
454
|
+
Original claims dictionary.
|
|
455
|
+
|
|
456
|
+
Returns
|
|
457
|
+
-------
|
|
458
|
+
Mapping[str, Any]
|
|
459
|
+
A new claims dictionary with password-related keys removed and
|
|
460
|
+
single-value lists replaced by their contained value.
|
|
461
|
+
"""
|
|
462
|
+
claims = Identity._remove_password_from_claims(claims)
|
|
463
|
+
|
|
464
|
+
return {
|
|
465
|
+
key: (
|
|
466
|
+
value[0]
|
|
467
|
+
if isinstance(value, list) and len(cast(list[Any], value)) == 1
|
|
468
|
+
else value
|
|
469
|
+
)
|
|
470
|
+
for key, value in claims.items()
|
|
471
|
+
}
|
|
472
|
+
|
|
433
473
|
@staticmethod
|
|
434
474
|
def _extract_groups(
|
|
435
475
|
entry: Mapping[str, Any],
|