alpha-python 0.5.0__tar.gz → 0.5.1__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.5.0/src/alpha_python.egg-info → alpha_python-0.5.1}/PKG-INFO +1 -1
- {alpha_python-0.5.0 → alpha_python-0.5.1}/pyproject.toml +1 -1
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/__init__.py +4 -0
- alpha_python-0.5.1/src/alpha/domain/models/user.py +130 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/jwt_factory.py +71 -26
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/password_factory.py +1 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/controller.mustache +36 -28
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/token_factory.py +6 -1
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/models/identity.py +1 -1
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/repositories/models/repository_model.py +1 -1
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/services/__init__.py +2 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/services/authentication_service.py +24 -13
- alpha_python-0.5.1/src/alpha/services/user_lifecycle_management.py +331 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/__init__.py +2 -0
- alpha_python-0.5.1/src/alpha/utils/request_headers.py +108 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1/src/alpha_python.egg-info}/PKG-INFO +1 -1
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha_python.egg-info/SOURCES.txt +2 -0
- alpha_python-0.5.0/src/alpha/domain/models/user.py +0 -66
- {alpha_python-0.5.0 → alpha_python-0.5.1}/LICENSE +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/README.md +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/setup.cfg +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/adapters/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/adapters/rest_api_unit_of_work.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/adapters/sqla_unit_of_work.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/cli.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/containers/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/containers/container.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/domain/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/domain/models/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/domain/models/base_model.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/domain/models/group.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/domain/models/life_cycle_base.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/encoder.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/exceptions.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/_type_conversion_matrix.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/_type_mapping.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/class_factories.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/default_field_factory.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/field_iterator.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/logging_handler_factory.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/model_class_factory.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/models/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/models/factory_classes.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/request_factory.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/response_factory.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/factories/type_factories.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/api_generate_handler.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/api_run_handler.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/base_handler.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/gen-code.sh +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/models/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/models/argument.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/models/command.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/models/section.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/models/subparser.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/run-api.sh +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/Dockerfile.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/README.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/__init__model.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/__init__test.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/__main__.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/base_model.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/controller_test.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/dockerignore.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/encoder.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/git_push.sh.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/gitignore.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/model.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/openapi.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/param_type.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/requirements.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/security_controller_.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/setup.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/test-requirements.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/tox.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/travis.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/typing_utils.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/handlers/templates/python-flask/util.mustache +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/connectors/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/connectors/ldap_connector.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/connectors/oidc_connector.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/databases/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/databases/sql_alchemy.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/models/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/models/filter_operators.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/models/json_patch.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/models/order_by.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/models/query_clause.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/infra/models/search_filter.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/api_repository.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/attrs_instance.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/dataclass_instance.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/factories.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/handler.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/openapi_model.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/patchable.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/providers.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/pydantic_instance.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/sql_database.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/sql_mapper.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/sql_repository.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/unit_of_work.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/interfaces/updateable.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/mixins/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/mixins/jwt_provider.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/api_key_provider.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/database_provider.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/ldap_provider.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/models/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/models/credentials.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/models/token.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/providers/oidc_provider.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/py.typed +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/repositories/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/repositories/models/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/repositories/rest_api_repository.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/repositories/sql_alchemy_repository.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/services/models/__init__.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/services/models/cookie.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/_http_codes.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/is_attrs.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/is_pydantic.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/logging_configurator.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/logging_level_checker.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/response_object.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/secret_generator.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/verify_identity.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha/utils/version_checker.py +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha_python.egg-info/dependency_links.txt +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha_python.egg-info/entry_points.txt +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha_python.egg-info/requires.txt +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/src/alpha_python.egg-info/top_level.txt +0 -0
- {alpha_python-0.5.0 → alpha_python-0.5.1}/tests/test_encoder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: alpha-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.1
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "alpha-python"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.1"
|
|
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 = [
|
|
@@ -49,6 +49,7 @@ from alpha.repositories.models.repository_model import RepositoryModel
|
|
|
49
49
|
from alpha.repositories.rest_api_repository import RestApiRepository
|
|
50
50
|
from alpha.repositories.sql_alchemy_repository import SqlAlchemyRepository
|
|
51
51
|
from alpha.services.authentication_service import AuthenticationService
|
|
52
|
+
from alpha.services.user_lifecycle_management import UserLifecycleManagement
|
|
52
53
|
from alpha.utils.is_attrs import is_attrs
|
|
53
54
|
from alpha.utils.is_pydantic import is_pydantic
|
|
54
55
|
from alpha.utils.logging_configurator import (
|
|
@@ -56,6 +57,7 @@ from alpha.utils.logging_configurator import (
|
|
|
56
57
|
GunicornLogger,
|
|
57
58
|
)
|
|
58
59
|
from alpha.utils.logging_level_checker import logging_level_checker
|
|
60
|
+
from alpha.utils.request_headers import Headers
|
|
59
61
|
from alpha.utils.response_object import create_response_object
|
|
60
62
|
from alpha.utils.verify_identity import verify_identity
|
|
61
63
|
from alpha.utils.version_checker import minor_version_gte
|
|
@@ -126,11 +128,13 @@ __all__ = [
|
|
|
126
128
|
"RestApiRepository",
|
|
127
129
|
"SqlAlchemyRepository",
|
|
128
130
|
"AuthenticationService",
|
|
131
|
+
"UserLifecycleManagement",
|
|
129
132
|
"is_attrs",
|
|
130
133
|
"is_pydantic",
|
|
131
134
|
"LoggingConfigurator",
|
|
132
135
|
"GunicornLogger",
|
|
133
136
|
"logging_level_checker",
|
|
137
|
+
"Headers",
|
|
134
138
|
"create_response_object",
|
|
135
139
|
"verify_identity",
|
|
136
140
|
"minor_version_gte",
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from datetime import datetime, timezone
|
|
3
|
+
from enum import Enum, auto
|
|
4
|
+
from typing import Self, Sequence, cast
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from alpha.domain.models.base_model import BaseDomainModel, DomainModel
|
|
8
|
+
from alpha.domain.models.group import Group
|
|
9
|
+
from alpha.domain.models.life_cycle_base import LifeCycleBase
|
|
10
|
+
|
|
11
|
+
from alpha.providers.models.identity import Identity
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Role(Enum):
|
|
15
|
+
"""Defines user roles with varying levels of permissions. The roles are
|
|
16
|
+
ordered from highest to lowest permissions. The comparison methods allow
|
|
17
|
+
for easy comparison of roles based on their hierarchy. The roles are
|
|
18
|
+
ordered on a scale from highest to lowest permissions.
|
|
19
|
+
|
|
20
|
+
Typical permissions are as follows:
|
|
21
|
+
- CREATE: Permission to create new content or data, but not modify existing
|
|
22
|
+
content.
|
|
23
|
+
- READ: Permission to read content or data.
|
|
24
|
+
- UPDATE: Permission to modify existing content or data, but not create new
|
|
25
|
+
content.
|
|
26
|
+
- DELETE: Permission to delete content or data.
|
|
27
|
+
- MANAGE_USERS: Permission to manage user accounts and permissions.
|
|
28
|
+
- MANAGE_SETTINGS: Permission to manage system settings and configurations.
|
|
29
|
+
- ALL: Permission to perform all actions, including user management and
|
|
30
|
+
system settings.
|
|
31
|
+
|
|
32
|
+
Roles:
|
|
33
|
+
- ADMIN: Role with permissions to manage users, content, and system
|
|
34
|
+
settings. Typically has the ALL permissions.
|
|
35
|
+
- SUPERUSER: Role with all permissions, including system settings and user
|
|
36
|
+
management. Typically has the ALL permissions, but may be used to denote a
|
|
37
|
+
special type of admin user with additional privileges or responsibilities.
|
|
38
|
+
- OWNER: Role with permissions to manage their own resources and users, but
|
|
39
|
+
not system settings. Typically has permissions similar to ADMIN, but
|
|
40
|
+
limited to their own scope of resources.
|
|
41
|
+
- MODERATOR: Role with permissions to manage content and users, but not
|
|
42
|
+
system settings. Typically has permissions to UPDATE and DELETE content,
|
|
43
|
+
and MANAGE_USERS, but not MANAGE_SETTINGS.
|
|
44
|
+
- EDITOR: Role with permissions to create and edit content, but not manage
|
|
45
|
+
users or settings. Typically has permissions to CREATE, READ, UPDATE, and
|
|
46
|
+
DELETE content, but not MANAGE_USERS or MANAGE_SETTINGS.
|
|
47
|
+
- USER: Default role with standard permissions. Typically has permissions
|
|
48
|
+
to CREATE, READ, and UPDATE their own content, but not DELETE content or
|
|
49
|
+
manage users or settings.
|
|
50
|
+
- VIEWER: Typical read-only role with limited permissions. Typically has
|
|
51
|
+
permission to READ content, but not CREATE, UPDATE, DELETE, or manage users
|
|
52
|
+
or settings.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
ADMIN = auto()
|
|
56
|
+
SUPERUSER = auto()
|
|
57
|
+
OWNER = auto()
|
|
58
|
+
MODERATOR = auto()
|
|
59
|
+
EDITOR = auto()
|
|
60
|
+
USER = auto()
|
|
61
|
+
VIEWER = auto()
|
|
62
|
+
|
|
63
|
+
def __lt__(self, obj: Self) -> bool:
|
|
64
|
+
return self.value < obj.value
|
|
65
|
+
|
|
66
|
+
def __le__(self, obj: Self) -> bool:
|
|
67
|
+
return self.value <= obj.value
|
|
68
|
+
|
|
69
|
+
def __gt__(self, obj: Self) -> bool:
|
|
70
|
+
return self.value > obj.value
|
|
71
|
+
|
|
72
|
+
def __ge__(self, obj: Self) -> bool:
|
|
73
|
+
return self.value >= obj.value
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass(kw_only=True)
|
|
77
|
+
class User(LifeCycleBase, BaseDomainModel):
|
|
78
|
+
id: UUID | int | str | None = None
|
|
79
|
+
username: str | None = None
|
|
80
|
+
password: str | None = None
|
|
81
|
+
role: str | Role | None = None
|
|
82
|
+
email: str | None = None
|
|
83
|
+
phone: str | None = None
|
|
84
|
+
display_name: str | None = None
|
|
85
|
+
permissions: Sequence[str] | None = None
|
|
86
|
+
groups: Sequence[str | Group] | None = None
|
|
87
|
+
is_active: bool = True
|
|
88
|
+
admin: bool = False
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_identity(cls, identity: Identity) -> Self:
|
|
92
|
+
"""Create a User instance from an Identity instance.
|
|
93
|
+
|
|
94
|
+
Parameters
|
|
95
|
+
----------
|
|
96
|
+
identity
|
|
97
|
+
Identity object to convert.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
User instance created from the Identity.
|
|
102
|
+
"""
|
|
103
|
+
return cls(
|
|
104
|
+
id=identity.subject,
|
|
105
|
+
username=identity.username,
|
|
106
|
+
email=identity.email,
|
|
107
|
+
display_name=identity.display_name,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def update(self, obj: DomainModel) -> DomainModel:
|
|
111
|
+
"""Update the User instance with data from another User instance.
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
obj
|
|
116
|
+
User object to update from.
|
|
117
|
+
"""
|
|
118
|
+
if not isinstance(obj, User):
|
|
119
|
+
raise TypeError("User.update expects a User instance.")
|
|
120
|
+
|
|
121
|
+
self.username = obj.username
|
|
122
|
+
self.email = obj.email
|
|
123
|
+
self.phone = obj.phone
|
|
124
|
+
self.display_name = obj.display_name
|
|
125
|
+
self.permissions = obj.permissions
|
|
126
|
+
self.groups = obj.groups
|
|
127
|
+
self.modified_at = datetime.now(tz=timezone.utc)
|
|
128
|
+
self.is_active = obj.is_active
|
|
129
|
+
self.admin = obj.admin
|
|
130
|
+
return cast(DomainModel, self)
|
|
@@ -18,8 +18,12 @@ class JWTFactory:
|
|
|
18
18
|
lifetime_hours: str | None = "12",
|
|
19
19
|
issuer: str = "http://localhost",
|
|
20
20
|
jwt_algorithm: str = "HS256",
|
|
21
|
+
options: dict[str, Any] | None = None,
|
|
21
22
|
) -> None:
|
|
22
|
-
"""Initialize the JWTFactory.
|
|
23
|
+
"""Initialize the JWTFactory. This method sets up the necessary
|
|
24
|
+
configuration for creating and validating JWT tokens. It requires a
|
|
25
|
+
secret key for signing the tokens and allows optional configuration
|
|
26
|
+
for token lifetime, issuer, algorithm, and decoding options.
|
|
23
27
|
|
|
24
28
|
Parameters
|
|
25
29
|
----------
|
|
@@ -31,6 +35,10 @@ class JWTFactory:
|
|
|
31
35
|
The issuer of the JWT, by default "http://localhost"
|
|
32
36
|
jwt_algorithm, optional
|
|
33
37
|
The algorithm used to sign the JWT, by default "HS256"
|
|
38
|
+
options, optional
|
|
39
|
+
A dictionary of options to customize the decoding behavior, by
|
|
40
|
+
default None. If not provided, it defaults to requiring standard
|
|
41
|
+
claims and verifying the signature.
|
|
34
42
|
|
|
35
43
|
Raises
|
|
36
44
|
------
|
|
@@ -46,6 +54,10 @@ class JWTFactory:
|
|
|
46
54
|
self.JWT_ISSUER = issuer
|
|
47
55
|
self.JWT_ALGORITHM = jwt_algorithm
|
|
48
56
|
self.JWT_LIFETIME_SECONDS = 3600 * int(lifetime_hours)
|
|
57
|
+
self.JWT_OPTIONS = options or {
|
|
58
|
+
"require": ["exp", "iat", "nbf", "iss", "sub"],
|
|
59
|
+
"verify_signature": True,
|
|
60
|
+
}
|
|
49
61
|
|
|
50
62
|
def create(
|
|
51
63
|
self,
|
|
@@ -93,13 +105,19 @@ class JWTFactory:
|
|
|
93
105
|
)
|
|
94
106
|
return token
|
|
95
107
|
|
|
96
|
-
def validate(
|
|
97
|
-
|
|
108
|
+
def validate(
|
|
109
|
+
self, token: str, options: dict[str, Any] | None = None
|
|
110
|
+
) -> bool:
|
|
111
|
+
"""Validate a JWT token. This method checks the token's signature,
|
|
112
|
+
expiration, and issuer. If the token is invalid, it raises an
|
|
113
|
+
appropriate exception. If the token is valid, it returns True.
|
|
98
114
|
|
|
99
115
|
Parameters
|
|
100
116
|
----------
|
|
101
117
|
token
|
|
102
118
|
The JWT token to be validated.
|
|
119
|
+
options
|
|
120
|
+
A dictionary of options to customize the decoding behavior.
|
|
103
121
|
|
|
104
122
|
Returns
|
|
105
123
|
-------
|
|
@@ -112,15 +130,62 @@ class JWTFactory:
|
|
|
112
130
|
InvalidSignatureException
|
|
113
131
|
If the token signature is invalid.
|
|
114
132
|
"""
|
|
133
|
+
if self._decode(token, options):
|
|
134
|
+
return True
|
|
135
|
+
return False
|
|
136
|
+
|
|
137
|
+
def get_payload(
|
|
138
|
+
self, token: str, options: dict[str, Any] | None = None
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
"""Retrieve the payload from a JWT token. This method does not perform
|
|
141
|
+
validation of the token by default. It simply decodes the token and
|
|
142
|
+
extracts the payload.
|
|
143
|
+
|
|
144
|
+
If the `options` parameter is provided, it will be passed to the
|
|
145
|
+
`jwt.decode` function. This allows customization of the decoding
|
|
146
|
+
behavior, such as enabling or disabling signature verification.
|
|
147
|
+
|
|
148
|
+
Parameters
|
|
149
|
+
----------
|
|
150
|
+
token
|
|
151
|
+
The JWT token from which to extract the payload.
|
|
152
|
+
options
|
|
153
|
+
A dictionary of options to customize the decoding behavior.
|
|
154
|
+
|
|
155
|
+
Returns
|
|
156
|
+
-------
|
|
157
|
+
A dictionary containing the payload data extracted from the token.
|
|
158
|
+
"""
|
|
159
|
+
decoded = self._decode(token, options)
|
|
160
|
+
return decoded.get("payload", {})
|
|
161
|
+
|
|
162
|
+
def _decode(
|
|
163
|
+
self, token: str, options: dict[str, Any] | None = None
|
|
164
|
+
) -> dict[str, Any]:
|
|
165
|
+
"""Decode a JWT token without performing validation. This method is
|
|
166
|
+
intended for internal use and should not be exposed as part of the
|
|
167
|
+
public API.
|
|
168
|
+
|
|
169
|
+
Parameters
|
|
170
|
+
----------
|
|
171
|
+
token
|
|
172
|
+
The JWT token to be decoded.
|
|
173
|
+
options
|
|
174
|
+
A dictionary of options to customize the decoding behavior.
|
|
175
|
+
|
|
176
|
+
Returns
|
|
177
|
+
-------
|
|
178
|
+
A dictionary containing the decoded token data.
|
|
179
|
+
"""
|
|
115
180
|
try:
|
|
116
|
-
jwt.decode(
|
|
181
|
+
decoded: dict[str, Any] = jwt.decode(
|
|
117
182
|
jwt=token,
|
|
118
183
|
key=self.JWT_SECRET,
|
|
119
184
|
algorithms=[self.JWT_ALGORITHM],
|
|
120
185
|
issuer=self.JWT_ISSUER,
|
|
121
|
-
|
|
186
|
+
options=options or self.JWT_OPTIONS,
|
|
122
187
|
)
|
|
123
|
-
return
|
|
188
|
+
return decoded
|
|
124
189
|
except jwt.ExpiredSignatureError as e:
|
|
125
190
|
raise exceptions.TokenExpiredException(str(e)) from e
|
|
126
191
|
except jwt.InvalidSignatureError as e:
|
|
@@ -129,23 +194,3 @@ class JWTFactory:
|
|
|
129
194
|
raise exceptions.InvalidTokenException(
|
|
130
195
|
f"Token is invalid: {str(e)}"
|
|
131
196
|
) from e
|
|
132
|
-
|
|
133
|
-
def get_payload(self, token: str) -> dict[str, Any]:
|
|
134
|
-
"""Retrieve the payload from a JWT token.
|
|
135
|
-
|
|
136
|
-
Parameters
|
|
137
|
-
----------
|
|
138
|
-
token
|
|
139
|
-
The JWT token from which to extract the payload.
|
|
140
|
-
|
|
141
|
-
Returns
|
|
142
|
-
-------
|
|
143
|
-
A dictionary containing the payload data extracted from the token.
|
|
144
|
-
"""
|
|
145
|
-
decoded: dict[str, Any] = jwt.decode(
|
|
146
|
-
jwt=token,
|
|
147
|
-
key=self.JWT_SECRET,
|
|
148
|
-
algorithms=[self.JWT_ALGORITHM],
|
|
149
|
-
issuer=self.JWT_ISSUER,
|
|
150
|
-
)
|
|
151
|
-
return decoded.get("payload", {})
|
|
@@ -11,6 +11,7 @@ from alpha import exceptions
|
|
|
11
11
|
class PasswordFactory:
|
|
12
12
|
"""This class provides methods for hashing and verifying passwords using
|
|
13
13
|
the argon2 library. It includes the following methods:
|
|
14
|
+
|
|
14
15
|
- hash_password: Hashes a given password and returns the hashed value as a
|
|
15
16
|
hexadecimal string.
|
|
16
17
|
- verify_password: Verifies a given password against a provided hash and
|
|
@@ -11,6 +11,7 @@ from typing import List
|
|
|
11
11
|
from alpha.factories.request_factory import RequestFactory
|
|
12
12
|
from alpha.factories.response_factory import ResponseFactory
|
|
13
13
|
from alpha.utils.response_object import create_response_object
|
|
14
|
+
from alpha.utils.request_headers import Headers
|
|
14
15
|
from alpha.utils.verify_identity import verify_identity
|
|
15
16
|
from alpha.utils._http_codes import http_codes_{{#languageCode}}{{languageCode}}{{/languageCode}}{{^languageCode}}en{{/languageCode}} as http_codes
|
|
16
17
|
from alpha.exceptions import (
|
|
@@ -144,28 +145,36 @@ def {{operationId}}(
|
|
|
144
145
|
{{response}}
|
|
145
146
|
{{/isContainer}}
|
|
146
147
|
{{/allParams}}
|
|
148
|
+
headers = Headers.from_headers(connexion.request.headers)
|
|
149
|
+
|
|
150
|
+
auth_token = headers.auth_token if headers.has_auth_token else None
|
|
151
|
+
refresh_token = headers.refresh_token
|
|
152
|
+
api_key = headers.api_key
|
|
147
153
|
|
|
148
|
-
{{#authMethods}}{{#isBasicBearer}}
|
|
149
|
-
if "Authorization" in connexion.request.headers:
|
|
150
|
-
token = connexion.request.headers["Authorization"].split(" ").pop()
|
|
151
|
-
else:
|
|
152
|
-
token = None
|
|
153
|
-
{{/isBasicBearer}}{{/authMethods}}
|
|
154
154
|
try:
|
|
155
155
|
# Objects used for authorization
|
|
156
156
|
roles=[{{#vendorExtensions.x-alpha-verify-roles}}"{{.}}",{{/vendorExtensions.x-alpha-verify-roles}}]
|
|
157
157
|
groups=[{{#vendorExtensions.x-alpha-verify-groups}}"{{.}}",{{/vendorExtensions.x-alpha-verify-groups}}]
|
|
158
158
|
permissions=[{{#vendorExtensions.x-alpha-verify-permissions}}"{{.}}",{{/vendorExtensions.x-alpha-verify-permissions}}]
|
|
159
|
-
{{#authMethods}}
|
|
160
|
-
#
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
159
|
+
{{#authMethods}}
|
|
160
|
+
{{#isBasicBearer}}
|
|
161
|
+
# Validate authentication token
|
|
162
|
+
if auth_token is None:
|
|
163
|
+
raise UnauthorizedException('Missing authentication token')
|
|
164
|
+
|
|
165
|
+
token_factory.validate(auth_token)
|
|
166
|
+
|
|
167
|
+
identity = token_factory.get_payload(auth_token)
|
|
168
|
+
verify_identity(identity,
|
|
169
|
+
roles=roles,
|
|
170
|
+
groups=groups,
|
|
171
|
+
permissions=permissions,
|
|
172
|
+
)
|
|
173
|
+
{{/isBasicBearer}}
|
|
174
|
+
{{#isApiKey}}
|
|
175
|
+
# This needs to be replaced by code to authenticate with an API key
|
|
176
|
+
{{/isApiKey}}
|
|
177
|
+
{{/authMethods}}
|
|
169
178
|
# Call method or use variable
|
|
170
179
|
{{#vendorExtensions.x-alpha-request-factory}}
|
|
171
180
|
{{#vendorExtensions.x-alpha-service-name}}
|
|
@@ -180,35 +189,34 @@ def {{operationId}}(
|
|
|
180
189
|
{{/vendorExtensions.x-alpha-service-method}}
|
|
181
190
|
{{/vendorExtensions.x-alpha-service-name}}
|
|
182
191
|
{{^vendorExtensions.x-alpha-service-name}}
|
|
183
|
-
{{#vendorExtensions.x-alpha-
|
|
184
|
-
result = {{vendorExtensions.x-alpha-
|
|
185
|
-
{{/vendorExtensions.x-alpha-
|
|
192
|
+
{{#vendorExtensions.x-alpha-custom-response}}
|
|
193
|
+
result = {{vendorExtensions.x-alpha-custom-response}}
|
|
194
|
+
{{/vendorExtensions.x-alpha-custom-response}}
|
|
186
195
|
{{/vendorExtensions.x-alpha-service-name}}
|
|
187
196
|
{{/vendorExtensions.x-alpha-request-factory}}
|
|
188
197
|
{{^vendorExtensions.x-alpha-request-factory}}
|
|
189
198
|
{{^vendorExtensions.x-alpha-service-name}}
|
|
190
|
-
{{^vendorExtensions.x-alpha-
|
|
199
|
+
{{^vendorExtensions.x-alpha-custom-response}}
|
|
191
200
|
raise ServerErrorException('Missing x-alpha-service-name in API specification')
|
|
192
|
-
{{/vendorExtensions.x-alpha-
|
|
201
|
+
{{/vendorExtensions.x-alpha-custom-response}}
|
|
193
202
|
{{^vendorExtensions.x-alpha-service-method}}
|
|
194
|
-
{{^vendorExtensions.x-alpha-
|
|
203
|
+
{{^vendorExtensions.x-alpha-custom-response}}
|
|
195
204
|
raise ServerErrorException('Missing x-alpha-service-method in API specification')
|
|
196
|
-
{{/vendorExtensions.x-alpha-
|
|
205
|
+
{{/vendorExtensions.x-alpha-custom-response}}
|
|
197
206
|
{{/vendorExtensions.x-alpha-service-method}}
|
|
198
207
|
{{/vendorExtensions.x-alpha-service-name}}
|
|
199
208
|
{{#vendorExtensions.x-alpha-service-name}}
|
|
200
209
|
{{#vendorExtensions.x-alpha-service-method}}
|
|
201
210
|
result = {{vendorExtensions.x-alpha-service-name}}.{{vendorExtensions.x-alpha-service-method}}(
|
|
202
211
|
{{#allParams}}{{paramName}}={{paramName}},{{/allParams}}
|
|
203
|
-
{{#vendorExtensions.x-alpha-service-additional-parameters}}{{.}}={{.}},
|
|
204
|
-
{{/vendorExtensions.x-alpha-service-additional-parameters}}
|
|
212
|
+
{{#vendorExtensions.x-alpha-service-additional-parameters}}{{.}}={{.}},{{/vendorExtensions.x-alpha-service-additional-parameters}}
|
|
205
213
|
)
|
|
206
214
|
{{/vendorExtensions.x-alpha-service-method}}
|
|
207
215
|
{{/vendorExtensions.x-alpha-service-name}}
|
|
208
216
|
{{^vendorExtensions.x-alpha-service-name}}
|
|
209
|
-
{{#vendorExtensions.x-alpha-
|
|
210
|
-
result = {{vendorExtensions.x-alpha-
|
|
211
|
-
{{/vendorExtensions.x-alpha-
|
|
217
|
+
{{#vendorExtensions.x-alpha-custom-response}}
|
|
218
|
+
result = {{vendorExtensions.x-alpha-custom-response}}
|
|
219
|
+
{{/vendorExtensions.x-alpha-custom-response}}
|
|
212
220
|
{{/vendorExtensions.x-alpha-service-name}}
|
|
213
221
|
{{/vendorExtensions.x-alpha-request-factory}}
|
|
214
222
|
|
|
@@ -48,13 +48,18 @@ class TokenFactory(Protocol):
|
|
|
48
48
|
"""
|
|
49
49
|
...
|
|
50
50
|
|
|
51
|
-
def get_payload(
|
|
51
|
+
def get_payload(
|
|
52
|
+
self, token: str, options: dict[str, bool] | None
|
|
53
|
+
) -> dict[str, str]:
|
|
52
54
|
"""Retrieve the payload from an authentication token.
|
|
53
55
|
|
|
54
56
|
Parameters
|
|
55
57
|
----------
|
|
56
58
|
token
|
|
57
59
|
The authentication token from which to extract the payload.
|
|
60
|
+
options
|
|
61
|
+
A dictionary of options to customize the decoding behavior, such as
|
|
62
|
+
enabling or disabling signature verification.
|
|
58
63
|
|
|
59
64
|
Returns
|
|
60
65
|
-------
|
|
@@ -43,8 +43,8 @@ class AuthenticationService:
|
|
|
43
43
|
refresh_token_max_age: int = 3600 * 24 * 7,
|
|
44
44
|
merge_with_database_users: bool = False,
|
|
45
45
|
merge_with_database_groups: bool = False,
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
user_username_attribute: str = "username",
|
|
47
|
+
group_name_attribute: str = "name",
|
|
48
48
|
uow: UnitOfWork | None = None,
|
|
49
49
|
users_repository_name: str = "users",
|
|
50
50
|
groups_repository_name: str = "groups",
|
|
@@ -106,10 +106,10 @@ class AuthenticationService:
|
|
|
106
106
|
merge_with_database_groups, optional
|
|
107
107
|
Whether to merge identity data with database group data,
|
|
108
108
|
by default False
|
|
109
|
-
|
|
109
|
+
user_username_attribute, optional
|
|
110
110
|
Attribute name in the user database to use as the unique
|
|
111
111
|
identifier, by default "username"
|
|
112
|
-
|
|
112
|
+
group_name_attribute, optional
|
|
113
113
|
Attribute name in the group database to use as the unique
|
|
114
114
|
identifier, by default "name"
|
|
115
115
|
uow, optional
|
|
@@ -176,8 +176,8 @@ class AuthenticationService:
|
|
|
176
176
|
self._refresh_token_max_age = refresh_token_max_age
|
|
177
177
|
self._merge_with_database_users = merge_with_database_users
|
|
178
178
|
self._merge_with_database_groups = merge_with_database_groups
|
|
179
|
-
self.
|
|
180
|
-
self.
|
|
179
|
+
self._user_username_attribute = user_username_attribute
|
|
180
|
+
self._group_name_attribute = group_name_attribute
|
|
181
181
|
self.uow = uow
|
|
182
182
|
self._users_repository_name = users_repository_name
|
|
183
183
|
self._groups_repository_name = groups_repository_name
|
|
@@ -368,8 +368,14 @@ class AuthenticationService:
|
|
|
368
368
|
"configured, cannot retrieve identity from auth token."
|
|
369
369
|
)
|
|
370
370
|
try:
|
|
371
|
+
# Attempt to retrieve the identity from the auth token without
|
|
372
|
+
# validating the token, since it may be expired. The payload
|
|
373
|
+
# should still be retrievable if the token is expired, as long
|
|
374
|
+
# as the signature is valid. If the signature is invalid, an
|
|
375
|
+
# exception will be raised and caught, resulting in the
|
|
376
|
+
# identity remaining None.
|
|
371
377
|
payload = self._identity_provider.token_factory.get_payload(
|
|
372
|
-
token=auth_token
|
|
378
|
+
token=auth_token, options={"verify_exp": False}
|
|
373
379
|
)
|
|
374
380
|
identity = Identity.from_dict(payload)
|
|
375
381
|
except Exception:
|
|
@@ -469,8 +475,8 @@ class AuthenticationService:
|
|
|
469
475
|
)
|
|
470
476
|
|
|
471
477
|
user = users.get_by_id(
|
|
472
|
-
value=getattr(identity, self.
|
|
473
|
-
attr=self.
|
|
478
|
+
value=getattr(identity, self._user_username_attribute),
|
|
479
|
+
attr=self._user_username_attribute,
|
|
474
480
|
)
|
|
475
481
|
if user:
|
|
476
482
|
identity.update_from_user(user)
|
|
@@ -502,18 +508,23 @@ class AuthenticationService:
|
|
|
502
508
|
self._raise_no_uow()
|
|
503
509
|
|
|
504
510
|
with self.uow:
|
|
505
|
-
|
|
511
|
+
groups_repo: SqlRepository[Group] = getattr(
|
|
506
512
|
self.uow, self._groups_repository_name
|
|
507
513
|
)
|
|
508
514
|
|
|
515
|
+
groups = list(identity.groups)
|
|
516
|
+
for i, group in enumerate(groups):
|
|
517
|
+
if isinstance(group, Group):
|
|
518
|
+
groups[i] = getattr(group, self._group_name_attribute)
|
|
519
|
+
|
|
509
520
|
filters = [
|
|
510
521
|
SearchFilter(
|
|
511
|
-
field=self.
|
|
522
|
+
field=self._group_name_attribute,
|
|
512
523
|
op=Operator.IN,
|
|
513
|
-
value=
|
|
524
|
+
value=groups,
|
|
514
525
|
)
|
|
515
526
|
]
|
|
516
|
-
user_groups =
|
|
527
|
+
user_groups = groups_repo.select(filters=filters)
|
|
517
528
|
identity.update_from_groups(user_groups)
|
|
518
529
|
|
|
519
530
|
return identity
|