alpha-python 0.5.0__py3-none-any.whl → 0.5.1__py3-none-any.whl
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/__init__.py +4 -0
- alpha/domain/models/user.py +66 -2
- alpha/factories/jwt_factory.py +71 -26
- alpha/factories/password_factory.py +1 -0
- alpha/handlers/templates/python-flask/controller.mustache +36 -28
- alpha/interfaces/token_factory.py +6 -1
- alpha/providers/models/identity.py +1 -1
- alpha/repositories/models/repository_model.py +1 -1
- alpha/services/__init__.py +2 -0
- alpha/services/authentication_service.py +24 -13
- alpha/services/user_lifecycle_management.py +331 -0
- alpha/utils/__init__.py +2 -0
- alpha/utils/request_headers.py +108 -0
- {alpha_python-0.5.0.dist-info → alpha_python-0.5.1.dist-info}/METADATA +1 -1
- {alpha_python-0.5.0.dist-info → alpha_python-0.5.1.dist-info}/RECORD +19 -17
- {alpha_python-0.5.0.dist-info → alpha_python-0.5.1.dist-info}/WHEEL +0 -0
- {alpha_python-0.5.0.dist-info → alpha_python-0.5.1.dist-info}/entry_points.txt +0 -0
- {alpha_python-0.5.0.dist-info → alpha_python-0.5.1.dist-info}/licenses/LICENSE +0 -0
- {alpha_python-0.5.0.dist-info → alpha_python-0.5.1.dist-info}/top_level.txt +0 -0
alpha/__init__.py
CHANGED
|
@@ -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",
|
alpha/domain/models/user.py
CHANGED
|
@@ -1,25 +1,89 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
|
+
from enum import Enum, auto
|
|
3
4
|
from typing import Self, Sequence, cast
|
|
4
5
|
from uuid import UUID
|
|
5
6
|
|
|
6
7
|
from alpha.domain.models.base_model import BaseDomainModel, DomainModel
|
|
8
|
+
from alpha.domain.models.group import Group
|
|
7
9
|
from alpha.domain.models.life_cycle_base import LifeCycleBase
|
|
8
10
|
|
|
9
11
|
from alpha.providers.models.identity import Identity
|
|
10
12
|
|
|
11
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
|
+
|
|
12
76
|
@dataclass(kw_only=True)
|
|
13
77
|
class User(LifeCycleBase, BaseDomainModel):
|
|
14
78
|
id: UUID | int | str | None = None
|
|
15
79
|
username: str | None = None
|
|
16
80
|
password: str | None = None
|
|
17
|
-
role: str | None = None
|
|
81
|
+
role: str | Role | None = None
|
|
18
82
|
email: str | None = None
|
|
19
83
|
phone: str | None = None
|
|
20
84
|
display_name: str | None = None
|
|
21
85
|
permissions: Sequence[str] | None = None
|
|
22
|
-
groups: Sequence[str] | None = None
|
|
86
|
+
groups: Sequence[str | Group] | None = None
|
|
23
87
|
is_active: bool = True
|
|
24
88
|
admin: bool = False
|
|
25
89
|
|
alpha/factories/jwt_factory.py
CHANGED
|
@@ -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
|
-------
|
alpha/services/__init__.py
CHANGED
|
@@ -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
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Contains the UserLifecycleManagement class"""
|
|
2
|
+
|
|
3
|
+
from uuid import UUID
|
|
4
|
+
|
|
5
|
+
from alpha.domain.models.group import Group
|
|
6
|
+
from alpha.domain.models.user import User
|
|
7
|
+
from alpha.exceptions import BadRequestException, NotFoundException
|
|
8
|
+
from alpha.factories.password_factory import PasswordFactory
|
|
9
|
+
from alpha.interfaces.sql_repository import SqlRepository
|
|
10
|
+
from alpha.interfaces.unit_of_work import UnitOfWork
|
|
11
|
+
from alpha.providers.models.identity import Identity
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class UserLifecycleManagement:
|
|
15
|
+
"""Management service for User CRUD transactions"""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
uow: UnitOfWork,
|
|
20
|
+
users_repository_name: str = "users",
|
|
21
|
+
groups_repository_name: str = "groups",
|
|
22
|
+
password_support: bool = False,
|
|
23
|
+
password_factory: PasswordFactory | None = None,
|
|
24
|
+
user_username_attribute: str = "username",
|
|
25
|
+
user_password_attribute: str = "password",
|
|
26
|
+
):
|
|
27
|
+
"""Initializes the UserLifecycleManagement service. This service
|
|
28
|
+
provides methods for managing the lifecycle of User objects, including
|
|
29
|
+
creating, retrieving, updating, and deleting users. It interacts with a
|
|
30
|
+
Unit of Work to manage transactions and repositories for users and
|
|
31
|
+
groups. It also supports optional password hashing functionality when
|
|
32
|
+
enabled.
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
uow
|
|
37
|
+
Unit of Work instance for managing transactions
|
|
38
|
+
users_repository_name, optional
|
|
39
|
+
Name of the users repository, by default "users"
|
|
40
|
+
groups_repository_name, optional
|
|
41
|
+
Name of the groups repository, by default "groups"
|
|
42
|
+
password_support, optional
|
|
43
|
+
Whether password support is enabled, by default False
|
|
44
|
+
password_factory, optional
|
|
45
|
+
Factory for creating password hashes, by default None. If None, a
|
|
46
|
+
default PasswordFactory will be used.
|
|
47
|
+
user_username_attribute, optional
|
|
48
|
+
Attribute name for the username, by default "username"
|
|
49
|
+
user_password_attribute, optional
|
|
50
|
+
Attribute name for the password, by default "password"
|
|
51
|
+
"""
|
|
52
|
+
self.uow = uow
|
|
53
|
+
self._users_repository_name = users_repository_name
|
|
54
|
+
self._groups_repository_name = groups_repository_name
|
|
55
|
+
self._password_support = password_support
|
|
56
|
+
self._password_factory = password_factory or PasswordFactory()
|
|
57
|
+
self._user_username_attribute = user_username_attribute
|
|
58
|
+
self._user_password_attribute = user_password_attribute
|
|
59
|
+
|
|
60
|
+
def add_user(self, user: User, identity: Identity | None = None) -> User:
|
|
61
|
+
"""Adds a new user object to the repository
|
|
62
|
+
|
|
63
|
+
Parameters
|
|
64
|
+
----------
|
|
65
|
+
user
|
|
66
|
+
New user object
|
|
67
|
+
identity
|
|
68
|
+
The identity of the user making the changes, by default None. If
|
|
69
|
+
provided, the `created_by` attribute of the user will be updated
|
|
70
|
+
with the username from the identity.
|
|
71
|
+
|
|
72
|
+
Returns
|
|
73
|
+
-------
|
|
74
|
+
Created user object
|
|
75
|
+
"""
|
|
76
|
+
if not hasattr(user, self._user_username_attribute) or not getattr(
|
|
77
|
+
user, self._user_username_attribute
|
|
78
|
+
):
|
|
79
|
+
raise BadRequestException("Username of User attribute is empty")
|
|
80
|
+
|
|
81
|
+
if self._password_support:
|
|
82
|
+
if not hasattr(user, self._user_password_attribute) or not getattr(
|
|
83
|
+
user, self._user_password_attribute
|
|
84
|
+
):
|
|
85
|
+
raise BadRequestException(
|
|
86
|
+
"Password of User attribute is empty"
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
setattr(
|
|
90
|
+
user,
|
|
91
|
+
self._user_password_attribute,
|
|
92
|
+
self._password_factory.hash_password(
|
|
93
|
+
password=getattr(user, self._user_password_attribute)
|
|
94
|
+
),
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
if identity:
|
|
98
|
+
user.created_by = identity.username
|
|
99
|
+
|
|
100
|
+
with self.uow:
|
|
101
|
+
users: SqlRepository[User] = getattr(
|
|
102
|
+
self.uow, self._users_repository_name
|
|
103
|
+
)
|
|
104
|
+
user = users.add(user, raise_if_exists=True)
|
|
105
|
+
self.uow.commit()
|
|
106
|
+
return user
|
|
107
|
+
|
|
108
|
+
def add_group(
|
|
109
|
+
self, group: Group, identity: Identity | None = None
|
|
110
|
+
) -> Group:
|
|
111
|
+
"""Adds a new group object to the repository
|
|
112
|
+
|
|
113
|
+
Parameters
|
|
114
|
+
----------
|
|
115
|
+
group
|
|
116
|
+
New group object
|
|
117
|
+
identity
|
|
118
|
+
The identity of the user making the changes, by default None. If
|
|
119
|
+
provided, the `created_by` attribute of the group will be updated
|
|
120
|
+
with the username from the identity.
|
|
121
|
+
|
|
122
|
+
Returns
|
|
123
|
+
-------
|
|
124
|
+
Created group object
|
|
125
|
+
"""
|
|
126
|
+
if identity:
|
|
127
|
+
group.created_by = identity.username
|
|
128
|
+
|
|
129
|
+
with self.uow:
|
|
130
|
+
groups: SqlRepository[Group] = getattr(
|
|
131
|
+
self.uow, self._groups_repository_name
|
|
132
|
+
)
|
|
133
|
+
group = groups.add(group, raise_if_exists=True)
|
|
134
|
+
self.uow.commit()
|
|
135
|
+
return group
|
|
136
|
+
|
|
137
|
+
def get_user(self, user_id: str | int | UUID) -> User:
|
|
138
|
+
"""Get an user object by id from the repository
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
user_id
|
|
143
|
+
The id of the user object
|
|
144
|
+
|
|
145
|
+
Returns
|
|
146
|
+
-------
|
|
147
|
+
User object which corresponds to the id
|
|
148
|
+
|
|
149
|
+
Raises
|
|
150
|
+
------
|
|
151
|
+
NotFoundException
|
|
152
|
+
When the object is not found in the repository
|
|
153
|
+
"""
|
|
154
|
+
with self.uow:
|
|
155
|
+
users: SqlRepository[User] = getattr(
|
|
156
|
+
self.uow, self._users_repository_name
|
|
157
|
+
)
|
|
158
|
+
user = users.get_by_id(user_id)
|
|
159
|
+
|
|
160
|
+
if not user:
|
|
161
|
+
raise NotFoundException(
|
|
162
|
+
f"User with id '{user_id}' is not found"
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
return user
|
|
166
|
+
|
|
167
|
+
def get_group(self, group_id: str | int | UUID) -> Group:
|
|
168
|
+
"""Get a group object by id from the repository
|
|
169
|
+
|
|
170
|
+
Parameters
|
|
171
|
+
----------
|
|
172
|
+
group_id
|
|
173
|
+
The id of the group object
|
|
174
|
+
|
|
175
|
+
Returns
|
|
176
|
+
-------
|
|
177
|
+
Group object which corresponds to the id
|
|
178
|
+
|
|
179
|
+
Raises
|
|
180
|
+
------
|
|
181
|
+
NotFoundException
|
|
182
|
+
When the object is not found in the repository
|
|
183
|
+
"""
|
|
184
|
+
with self.uow:
|
|
185
|
+
groups: SqlRepository[Group] = getattr(
|
|
186
|
+
self.uow, self._groups_repository_name
|
|
187
|
+
)
|
|
188
|
+
group = groups.get_by_id(group_id)
|
|
189
|
+
|
|
190
|
+
if not group:
|
|
191
|
+
raise NotFoundException(
|
|
192
|
+
f"Group with id '{group_id}' is not found"
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return group
|
|
196
|
+
|
|
197
|
+
def get_users(self) -> list[User]:
|
|
198
|
+
"""Gets all user objects from the repository
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
A collection of all the user objects
|
|
203
|
+
"""
|
|
204
|
+
with self.uow:
|
|
205
|
+
users: SqlRepository[User] = getattr(
|
|
206
|
+
self.uow, self._users_repository_name
|
|
207
|
+
)
|
|
208
|
+
result = users.select()
|
|
209
|
+
|
|
210
|
+
return result
|
|
211
|
+
|
|
212
|
+
def get_groups(self) -> list[Group]:
|
|
213
|
+
"""Gets all group objects from the repository
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
A collection of all the group objects
|
|
218
|
+
"""
|
|
219
|
+
with self.uow:
|
|
220
|
+
groups: SqlRepository[Group] = getattr(
|
|
221
|
+
self.uow, self._groups_repository_name
|
|
222
|
+
)
|
|
223
|
+
result = groups.select()
|
|
224
|
+
|
|
225
|
+
return result
|
|
226
|
+
|
|
227
|
+
def remove_user(self, user_id: str | int | UUID) -> None:
|
|
228
|
+
"""Removes an user object from the repository
|
|
229
|
+
|
|
230
|
+
Parameters
|
|
231
|
+
----------
|
|
232
|
+
user_id
|
|
233
|
+
The id of the user object
|
|
234
|
+
"""
|
|
235
|
+
user = self.get_user(user_id=user_id)
|
|
236
|
+
with self.uow:
|
|
237
|
+
users: SqlRepository[User] = getattr(
|
|
238
|
+
self.uow, self._users_repository_name
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
users.remove(user)
|
|
242
|
+
self.uow.commit()
|
|
243
|
+
|
|
244
|
+
def remove_group(self, group_id: str | int | UUID) -> None:
|
|
245
|
+
"""Removes a group object from the repository
|
|
246
|
+
|
|
247
|
+
Parameters
|
|
248
|
+
----------
|
|
249
|
+
group_id
|
|
250
|
+
The id of the group object
|
|
251
|
+
"""
|
|
252
|
+
group = self.get_group(group_id=group_id)
|
|
253
|
+
with self.uow:
|
|
254
|
+
groups: SqlRepository[Group] = getattr(
|
|
255
|
+
self.uow, self._groups_repository_name
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
groups.remove(group)
|
|
259
|
+
self.uow.commit()
|
|
260
|
+
|
|
261
|
+
def update_user(
|
|
262
|
+
self,
|
|
263
|
+
user_id: str | int | UUID,
|
|
264
|
+
user: User,
|
|
265
|
+
identity: Identity | None = None,
|
|
266
|
+
) -> User:
|
|
267
|
+
"""Updates an existing user object in the repository
|
|
268
|
+
|
|
269
|
+
Parameters
|
|
270
|
+
----------
|
|
271
|
+
user
|
|
272
|
+
User object with changes
|
|
273
|
+
user_id
|
|
274
|
+
The id of the user object
|
|
275
|
+
identity
|
|
276
|
+
The identity of the user making the changes, by default None. If
|
|
277
|
+
provided, the `modified_by` attribute of the user will be updated
|
|
278
|
+
with the username from the identity.
|
|
279
|
+
|
|
280
|
+
Returns
|
|
281
|
+
-------
|
|
282
|
+
Updated user object
|
|
283
|
+
"""
|
|
284
|
+
original_user = self.get_user(user_id=user_id)
|
|
285
|
+
|
|
286
|
+
with self.uow:
|
|
287
|
+
users: SqlRepository[User] = getattr(
|
|
288
|
+
self.uow, self._users_repository_name
|
|
289
|
+
)
|
|
290
|
+
updated_user = users.update(original_user, user)
|
|
291
|
+
if identity:
|
|
292
|
+
updated_user.modified_by = identity.username
|
|
293
|
+
self.uow.commit()
|
|
294
|
+
|
|
295
|
+
return updated_user
|
|
296
|
+
|
|
297
|
+
def update_group(
|
|
298
|
+
self,
|
|
299
|
+
group_id: str | int | UUID,
|
|
300
|
+
group: Group,
|
|
301
|
+
identity: Identity | None = None,
|
|
302
|
+
) -> Group:
|
|
303
|
+
"""Updates an existing group object in the repository
|
|
304
|
+
|
|
305
|
+
Parameters
|
|
306
|
+
----------
|
|
307
|
+
group
|
|
308
|
+
Group object with changes
|
|
309
|
+
group_id
|
|
310
|
+
The id of the group object
|
|
311
|
+
identity
|
|
312
|
+
The identity of the user making the changes, by default None. If
|
|
313
|
+
provided, the `modified_by` attribute of the group will be updated
|
|
314
|
+
with the username from the identity.
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
Updated group object
|
|
319
|
+
"""
|
|
320
|
+
original_group = self.get_group(group_id=group_id)
|
|
321
|
+
|
|
322
|
+
with self.uow:
|
|
323
|
+
groups: SqlRepository[Group] = getattr(
|
|
324
|
+
self.uow, self._groups_repository_name
|
|
325
|
+
)
|
|
326
|
+
updated_group = groups.update(original_group, group)
|
|
327
|
+
if identity:
|
|
328
|
+
updated_group.modified_by = identity.username
|
|
329
|
+
self.uow.commit()
|
|
330
|
+
|
|
331
|
+
return updated_group
|
alpha/utils/__init__.py
CHANGED
|
@@ -5,6 +5,7 @@ from alpha.utils.logging_configurator import (
|
|
|
5
5
|
GunicornLogger,
|
|
6
6
|
)
|
|
7
7
|
from alpha.utils.logging_level_checker import logging_level_checker
|
|
8
|
+
from alpha.utils.request_headers import Headers
|
|
8
9
|
from alpha.utils.response_object import create_response_object
|
|
9
10
|
from alpha.utils.verify_identity import verify_identity
|
|
10
11
|
from alpha.utils.version_checker import minor_version_gte
|
|
@@ -18,4 +19,5 @@ __all__ = [
|
|
|
18
19
|
"create_response_object",
|
|
19
20
|
"verify_identity",
|
|
20
21
|
"minor_version_gte",
|
|
22
|
+
"Headers",
|
|
21
23
|
]
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from http import cookies
|
|
3
|
+
from typing import Mapping, Self
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class Headers:
|
|
8
|
+
"""A dataclass representing the headers of an HTTP request. This is
|
|
9
|
+
primarily used for extracting authentication tokens and API keys from the
|
|
10
|
+
request headers and cookies.
|
|
11
|
+
|
|
12
|
+
This class provides a convenient way to access authentication tokens and
|
|
13
|
+
API keys, regardless of whether they are provided in the standard headers
|
|
14
|
+
or in cookies. It provides properties to check the presence of these tokens
|
|
15
|
+
and keys.
|
|
16
|
+
|
|
17
|
+
This class is designed to be immutable and uses slots for memory
|
|
18
|
+
efficiency.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
auth_token: str | None = None
|
|
22
|
+
auth_token_type: str | None = None
|
|
23
|
+
refresh_token: str | None = None
|
|
24
|
+
api_key: str | None = None
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def from_headers(
|
|
28
|
+
cls,
|
|
29
|
+
headers: Mapping[str, str],
|
|
30
|
+
auth_token_cookie_name: str = "auth_token",
|
|
31
|
+
refresh_token_cookie_name: str = "refresh_token",
|
|
32
|
+
api_key_cookie_name: str = "api_key",
|
|
33
|
+
) -> Self:
|
|
34
|
+
"""Create a Headers instance from a mapping of headers.
|
|
35
|
+
|
|
36
|
+
For the auth token, the method first checks the "Authorization" header.
|
|
37
|
+
If present, it extracts the token and its type (e.g., "Bearer"). If it
|
|
38
|
+
is not present, it looks for a cookie with the name specified by
|
|
39
|
+
`auth_token_cookie_name`. The same logic applies to the refresh token
|
|
40
|
+
and API key, checking the "X-Refresh-Token" and "X-API-Key" headers. If
|
|
41
|
+
they are not present, it looks for cookies with the names specified by
|
|
42
|
+
`refresh_token_cookie_name` and `api_key_cookie_name`.
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
----------
|
|
46
|
+
headers
|
|
47
|
+
A mapping of header names to their values.
|
|
48
|
+
auth_token_cookie_name
|
|
49
|
+
The name of the cookie containing the auth token, default is
|
|
50
|
+
"auth_token".
|
|
51
|
+
refresh_token_cookie_name
|
|
52
|
+
The name of the cookie containing the refresh token, default is
|
|
53
|
+
"refresh_token".
|
|
54
|
+
api_key_cookie_name
|
|
55
|
+
The name of the cookie containing the API key, default is
|
|
56
|
+
"api_key".
|
|
57
|
+
|
|
58
|
+
Returns
|
|
59
|
+
-------
|
|
60
|
+
An instance of the Headers class.
|
|
61
|
+
"""
|
|
62
|
+
auth_token = None
|
|
63
|
+
auth_token_type = None
|
|
64
|
+
authorization_header = headers.get("Authorization")
|
|
65
|
+
if authorization_header is not None:
|
|
66
|
+
parts = authorization_header.split()
|
|
67
|
+
if len(parts) == 2:
|
|
68
|
+
auth_token_type, auth_token = parts
|
|
69
|
+
if auth_token_type.lower() == "bearer":
|
|
70
|
+
auth_token_type = "Bearer"
|
|
71
|
+
|
|
72
|
+
refresh_token = headers.get("X-Refresh-Token")
|
|
73
|
+
api_key = headers.get("X-API-Key")
|
|
74
|
+
cookies_header = headers.get("Cookie")
|
|
75
|
+
|
|
76
|
+
if cookies_header is not None:
|
|
77
|
+
cookie_jar = cookies.SimpleCookie(cookies_header)
|
|
78
|
+
|
|
79
|
+
if not auth_token and auth_token_cookie_name in cookie_jar:
|
|
80
|
+
auth_token = cookie_jar[auth_token_cookie_name].value
|
|
81
|
+
auth_token_type = "Bearer"
|
|
82
|
+
if not refresh_token and refresh_token_cookie_name in cookie_jar:
|
|
83
|
+
refresh_token = cookie_jar[refresh_token_cookie_name].value
|
|
84
|
+
if not api_key and api_key_cookie_name in cookie_jar:
|
|
85
|
+
api_key = cookie_jar[api_key_cookie_name].value
|
|
86
|
+
|
|
87
|
+
return cls(
|
|
88
|
+
auth_token=auth_token,
|
|
89
|
+
auth_token_type=auth_token_type,
|
|
90
|
+
refresh_token=refresh_token,
|
|
91
|
+
api_key=api_key,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def has_auth_token(self) -> bool:
|
|
96
|
+
if not self.auth_token:
|
|
97
|
+
return False
|
|
98
|
+
if not self.auth_token_type:
|
|
99
|
+
return False
|
|
100
|
+
return self.auth_token_type.lower() == "bearer"
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def has_refresh_token(self) -> bool:
|
|
104
|
+
return True if self.refresh_token else False
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def has_api_key(self) -> bool:
|
|
108
|
+
return True if self.api_key else False
|
|
@@ -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,4 +1,4 @@
|
|
|
1
|
-
alpha/__init__.py,sha256=
|
|
1
|
+
alpha/__init__.py,sha256=2Dk-KZu8cM84sFeePENcFnxb4-CHl9OR7qBbYSetvnw,4926
|
|
2
2
|
alpha/cli.py,sha256=YdBvz_nqvC1c4kA-Xb1KtS-1Or6rbDu2VcF3ClatP6o,5657
|
|
3
3
|
alpha/encoder.py,sha256=ZKPHnG4DT6azne3Sh9tNOuPO9s8xnjlr64CQ0e2abjI,1924
|
|
4
4
|
alpha/exceptions.py,sha256=AHoFMPyHvjj6j_1X2TS40dSaFBzCDtgzAmucimSZjfc,5793
|
|
@@ -13,17 +13,17 @@ alpha/domain/models/__init__.py,sha256=ZBL9sp-DL8qVvD5QQHOqoAGGZD7TneuiW7A_JPYtQ
|
|
|
13
13
|
alpha/domain/models/base_model.py,sha256=VzPDUdizaDAydvv3PfSqWwkUtzaBSKv1et7yPcKEL44,837
|
|
14
14
|
alpha/domain/models/group.py,sha256=8UeZrXDScAlfYf7qz8PzjFqUNdZkI49_oPPsAD-rYWI,1108
|
|
15
15
|
alpha/domain/models/life_cycle_base.py,sha256=XMLAFoCxUeItmx9owyAxzUHJHN19s40TQKk_ZEDhexQ,246
|
|
16
|
-
alpha/domain/models/user.py,sha256=
|
|
16
|
+
alpha/domain/models/user.py,sha256=9YXDn2l0S7TjobaUKNJsqLqpz3v43ObUo0jMqZR6DEk,4783
|
|
17
17
|
alpha/factories/__init__.py,sha256=baxoq0UY7UYust7NiR9wT954nJw5bfgSmOWvLjBt2C4,278
|
|
18
18
|
alpha/factories/_type_conversion_matrix.py,sha256=mhMYpus6OFE0sZB0gQ-b1ndIOhiA1ScprvDa9tLANAM,5055
|
|
19
19
|
alpha/factories/_type_mapping.py,sha256=f8cRfu8KUfw1ggY0Txs6fEX2e6GaXrsNcc2SCeYFRHM,789
|
|
20
20
|
alpha/factories/class_factories.py,sha256=MDUnWQC1a3OUE0HyKEZx6watdMes8BjjoZQaOzBWfVo,15582
|
|
21
21
|
alpha/factories/default_field_factory.py,sha256=ZvPU6BdrdELmZNXIeTmRp_YOCIscWFC9W6s05qXyXrk,1758
|
|
22
22
|
alpha/factories/field_iterator.py,sha256=_7Z5UIdGEHhVV2JYA-e7ayJb7wnvo6KVRgXwykOCbgA,6212
|
|
23
|
-
alpha/factories/jwt_factory.py,sha256=
|
|
23
|
+
alpha/factories/jwt_factory.py,sha256=zNRmGu874AYOHvRPrNQd1p-TAQPRx1lPgxusiut1JhM,6407
|
|
24
24
|
alpha/factories/logging_handler_factory.py,sha256=H9gVS1rNt5Jln3C_2EbOvJG-gqS9ksTsYUIx2UK7gVk,3059
|
|
25
25
|
alpha/factories/model_class_factory.py,sha256=40eiLr8gYXpJfbbn1wPzcgsys9gU5x9nB2zlVTbgU7E,5704
|
|
26
|
-
alpha/factories/password_factory.py,sha256=
|
|
26
|
+
alpha/factories/password_factory.py,sha256=dmpcKUG-R4CI68GpW5sxlnbuP5T09uzzRYeXbMn-QWk,4187
|
|
27
27
|
alpha/factories/request_factory.py,sha256=271gFfiDf5ZgtDcB0CQYG4ppbj-XuKic2ENWVNMrg2s,6704
|
|
28
28
|
alpha/factories/response_factory.py,sha256=QFtOq2n7cYkBeLoz-mZdBtHkVsJiQ2rHNF6SdP_nz_E,6399
|
|
29
29
|
alpha/factories/type_factories.py,sha256=_cBJQQtccnbq-fThP402J1YuI5vM0BlmSJWGyZi0MTU,6017
|
|
@@ -47,7 +47,7 @@ alpha/handlers/templates/python-flask/__init__model.mustache,sha256=JRwyJmOCAD8n
|
|
|
47
47
|
alpha/handlers/templates/python-flask/__init__test.mustache,sha256=wUxDXcyE4rPh3sDp8MvR5m8vqzoU25Fw1bo_xh6InnY,438
|
|
48
48
|
alpha/handlers/templates/python-flask/__main__.mustache,sha256=GetXHmF7BXxrwVajfkI6OjLQV7ekFxD9SPYHjZLNNWY,2393
|
|
49
49
|
alpha/handlers/templates/python-flask/base_model.mustache,sha256=B--jl9LmfiHACq9n1kDxm6YTi6tb1oSUFnZya_IKOhU,2184
|
|
50
|
-
alpha/handlers/templates/python-flask/controller.mustache,sha256=
|
|
50
|
+
alpha/handlers/templates/python-flask/controller.mustache,sha256=zGVY4a5dGKH4gEYT6D5bXiTy3DEqRPKLKDSI3wucr64,13395
|
|
51
51
|
alpha/handlers/templates/python-flask/controller_test.mustache,sha256=RX_IG_eaC4rmI696jDUkDcOhPE9CgS2YmgqOBIEn7GM,2849
|
|
52
52
|
alpha/handlers/templates/python-flask/dockerignore.mustache,sha256=hqlHuqIMLuuwvVqK6xhIcfaezKoChriR3D7TvxtkjGo,906
|
|
53
53
|
alpha/handlers/templates/python-flask/encoder.mustache,sha256=03rsvhN_QdqaWsQC2v3772UvrLEXna9WcvxQsl6mwiU,866
|
|
@@ -89,7 +89,7 @@ alpha/interfaces/pydantic_instance.py,sha256=S96jLEWRmDyvYV3VqofSqjiN-lcjfkjiNp7
|
|
|
89
89
|
alpha/interfaces/sql_database.py,sha256=H52NR1ti2j9G9zJsibPtKmXL7WdHOzeSyPXO4Zq9wlA,996
|
|
90
90
|
alpha/interfaces/sql_mapper.py,sha256=NyPYc4DuG9HBd9LO8ooYidD-rv1poSty5GmzW6jmE8g,465
|
|
91
91
|
alpha/interfaces/sql_repository.py,sha256=PS_nIK9Yej15OlSVdLm_JNDPzhMEuCTT0C98lznIqJ8,8767
|
|
92
|
-
alpha/interfaces/token_factory.py,sha256=
|
|
92
|
+
alpha/interfaces/token_factory.py,sha256=8leGt3jIGiYQfQBFQWWgscU9uI3N_P4bkOSAwh15oMg,1845
|
|
93
93
|
alpha/interfaces/unit_of_work.py,sha256=yQ0Zexj3gVOzH7pjwF_9tvAKUPvzCielvgdzzCgLuZA,870
|
|
94
94
|
alpha/interfaces/updateable.py,sha256=7Vq6kzNa8hyTVk_vzjUdM8hUyRWclHt7L3erRlo7L2U,173
|
|
95
95
|
alpha/mixins/__init__.py,sha256=4In0NQdfIFB8U-E4dzxHOJFJGUk6-1lQ_eJ7C1fKnvk,87
|
|
@@ -101,30 +101,32 @@ alpha/providers/ldap_provider.py,sha256=r6T8cMVrN1oMAw_a494n8P7ZmHQkNLVmcOwk4lCh
|
|
|
101
101
|
alpha/providers/oidc_provider.py,sha256=sAu4kMepJ2YRao3d4LZo3-nX86k3Sn9cukQ7nEeCFhg,12717
|
|
102
102
|
alpha/providers/models/__init__.py,sha256=SI-qTA3JMOxXx4o1h7frk8x3PRj0g_GSCEbLzyvw7CI,409
|
|
103
103
|
alpha/providers/models/credentials.py,sha256=_nq60FcPVcWfRQ6UmK1Ug4ey6f-MWXGwKWHdIct2q9M,476
|
|
104
|
-
alpha/providers/models/identity.py,sha256=
|
|
104
|
+
alpha/providers/models/identity.py,sha256=KostRfSm3vc0kTokdqPVdUZyBgEcNYK9hfpU_pLg2FQ,14825
|
|
105
105
|
alpha/providers/models/token.py,sha256=jWFQSuXsPuy2xNpJRwpdzpOTtiMJ1xfUelQhQTxmykU,1698
|
|
106
106
|
alpha/repositories/__init__.py,sha256=qN7iefBr93_fXo8cTn_RUFaz8PPhsUlzojKbYFM2m-c,306
|
|
107
107
|
alpha/repositories/rest_api_repository.py,sha256=RWGa5KxvOAiS_g5WIx_qLTPAUd5HPtKgAY2CX3-W-t0,46019
|
|
108
108
|
alpha/repositories/sql_alchemy_repository.py,sha256=k4WeK15w4a0XZ360UoUMtflLamGX8ycbySrxFpPwcio,19912
|
|
109
109
|
alpha/repositories/models/__init__.py,sha256=PY3kFG0z5r5ALcwBFXVNO9GAVNn1tVTkvUp8XoZEMuY,109
|
|
110
|
-
alpha/repositories/models/repository_model.py,sha256=
|
|
111
|
-
alpha/services/__init__.py,sha256=
|
|
112
|
-
alpha/services/authentication_service.py,sha256=
|
|
110
|
+
alpha/repositories/models/repository_model.py,sha256=WpfW90HpDvwDK5jcqNcprIKugqxcMaQWoqEFEgr0FoA,1038
|
|
111
|
+
alpha/services/__init__.py,sha256=yeUbS-N0127DSejbCAv9jbdKuHRa_H5Ue0CtsWf4ghI,224
|
|
112
|
+
alpha/services/authentication_service.py,sha256=61x17xNI_D0MFhxf6D1ME0ceSfqfkn28bF6U8tDlQ5Y,29694
|
|
113
|
+
alpha/services/user_lifecycle_management.py,sha256=OHrzmRJ79yrYWB55Vp_9DDAM9V__nCUpgKCF5PwVT4E,10411
|
|
113
114
|
alpha/services/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
114
115
|
alpha/services/models/cookie.py,sha256=V0BTBG_AsvJEGoBv5_6n4LYWt36YJfGT2MVkTvWfxTU,1654
|
|
115
|
-
alpha/utils/__init__.py,sha256=
|
|
116
|
+
alpha/utils/__init__.py,sha256=QHentkiIMO_schZQ1k7XoTwZLfdwxliKlHExm51sMuc,698
|
|
116
117
|
alpha/utils/_http_codes.py,sha256=QLU43JnjvYGsv6u_quRP7jzgg996boa4-a-yKcSw9Bo,6558
|
|
117
118
|
alpha/utils/is_attrs.py,sha256=LlLozdSnKEjFtVwJm9AWf7NAIkBPotnpbhFmwz3f4AI,432
|
|
118
119
|
alpha/utils/is_pydantic.py,sha256=qFxiR8UsHW612D_0e33ghtSZ4xDG-chhY6JU56lSzrg,635
|
|
119
120
|
alpha/utils/logging_configurator.py,sha256=gnN1mlV9sPnSrWvR8jrGuBrc8pYWeQ2cyTpzCkWDOFM,3741
|
|
120
121
|
alpha/utils/logging_level_checker.py,sha256=x3MrwV0aqmtd-YOb5VxZzNm6MKsX3YtaVxUQ6dT0E4E,669
|
|
122
|
+
alpha/utils/request_headers.py,sha256=ETaYLFuPdAYiFHBeb9HFSrpqM3lZsxueFwHqYOq_-wU,3993
|
|
121
123
|
alpha/utils/response_object.py,sha256=AomSsUGHNjH-evtcFDlUbR56-B676Fs8hb94dhKELyY,4948
|
|
122
124
|
alpha/utils/secret_generator.py,sha256=SOzhyC3e3epSLCpc-7TEKntmdsrNoU6SRD2mIxwlGe0,401
|
|
123
125
|
alpha/utils/verify_identity.py,sha256=tnh9JAez8FlrqN4Bqh1bfTdnEOM2VA1ZAVLfZCqI-ZU,2229
|
|
124
126
|
alpha/utils/version_checker.py,sha256=W8v69hDbrr0VvJ40gpFmXMtVawrDmUz_YGWssKUTmVE,380
|
|
125
|
-
alpha_python-0.5.
|
|
126
|
-
alpha_python-0.5.
|
|
127
|
-
alpha_python-0.5.
|
|
128
|
-
alpha_python-0.5.
|
|
129
|
-
alpha_python-0.5.
|
|
130
|
-
alpha_python-0.5.
|
|
127
|
+
alpha_python-0.5.1.dist-info/licenses/LICENSE,sha256=5KwEqC3KUoH4lVXgZ9tGriKOl-LGxHkXBWo16mFmAYM,1070
|
|
128
|
+
alpha_python-0.5.1.dist-info/METADATA,sha256=Gw4-1tWzAB-lx_hBAxLryd6Lt2kGoDQXvZdd6DTfWo4,3783
|
|
129
|
+
alpha_python-0.5.1.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
130
|
+
alpha_python-0.5.1.dist-info/entry_points.txt,sha256=LBEXdcofOugYYdZ46nz5Dxj_aj1QbRBkumfPGhy-GXI,41
|
|
131
|
+
alpha_python-0.5.1.dist-info/top_level.txt,sha256=tqmNnOmi2RSSiPo99C03fD5Cc3r9za9xTjPAoQC1EGA,6
|
|
132
|
+
alpha_python-0.5.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|