alpha-python 0.7.4__py3-none-any.whl → 0.7.6__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 +2 -0
- alpha/adapters/sqla_unit_of_work.py +11 -3
- alpha/domain/__init__.py +2 -0
- alpha/domain/models/__init__.py +2 -0
- alpha/domain/models/group.py +41 -1
- alpha/domain/models/permission.py +110 -0
- alpha/domain/models/user.py +66 -2
- alpha/mixins/jwt_provider.py +4 -1
- alpha/providers/database_provider.py +8 -2
- alpha/providers/ldap_provider.py +18 -1
- alpha/providers/models/identity.py +52 -12
- alpha/providers/oidc_provider.py +20 -2
- alpha/services/authentication_service.py +7 -2
- {alpha_python-0.7.4.dist-info → alpha_python-0.7.6.dist-info}/METADATA +2 -2
- {alpha_python-0.7.4.dist-info → alpha_python-0.7.6.dist-info}/RECORD +19 -18
- {alpha_python-0.7.4.dist-info → alpha_python-0.7.6.dist-info}/WHEEL +1 -1
- {alpha_python-0.7.4.dist-info → alpha_python-0.7.6.dist-info}/entry_points.txt +0 -0
- {alpha_python-0.7.4.dist-info → alpha_python-0.7.6.dist-info}/licenses/LICENSE +0 -0
- {alpha_python-0.7.4.dist-info → alpha_python-0.7.6.dist-info}/top_level.txt +0 -0
alpha/__init__.py
CHANGED
|
@@ -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:
|
alpha/domain/__init__.py
CHANGED
|
@@ -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
|
]
|
alpha/domain/models/__init__.py
CHANGED
|
@@ -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
|
]
|
alpha/domain/models/group.py
CHANGED
|
@@ -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)
|
alpha/domain/models/user.py
CHANGED
|
@@ -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)
|
alpha/mixins/jwt_provider.py
CHANGED
|
@@ -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
|
alpha/providers/ldap_provider.py
CHANGED
|
@@ -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],
|
alpha/providers/oidc_provider.py
CHANGED
|
@@ -48,6 +48,7 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
48
48
|
|
|
49
49
|
protocol = "oidc"
|
|
50
50
|
token_factory: TokenFactory | None = None
|
|
51
|
+
_identity_model: type[Identity] = Identity
|
|
51
52
|
|
|
52
53
|
def __init__(
|
|
53
54
|
self,
|
|
@@ -57,6 +58,8 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
57
58
|
populate_groups: bool = True,
|
|
58
59
|
populate_permissions: bool = False,
|
|
59
60
|
populate_claims: bool = False,
|
|
61
|
+
flatten_claims: bool = True,
|
|
62
|
+
identity_model: type[Identity] = Identity,
|
|
60
63
|
change_password_supported: bool = False,
|
|
61
64
|
) -> None:
|
|
62
65
|
"""Initialize OIDCProvider.
|
|
@@ -79,6 +82,11 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
79
82
|
Whether to populate permissions on the Identity.
|
|
80
83
|
populate_claims
|
|
81
84
|
Whether to include raw claims on the Identity.
|
|
85
|
+
flatten_claims
|
|
86
|
+
Whether to flatten single-value claims in the Identity.
|
|
87
|
+
identity_model
|
|
88
|
+
Identity model class to use for representing users, by default
|
|
89
|
+
Identity
|
|
82
90
|
change_password_supported
|
|
83
91
|
Whether this provider supports changing passwords.
|
|
84
92
|
"""
|
|
@@ -90,6 +98,8 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
90
98
|
self._populate_groups = populate_groups
|
|
91
99
|
self._populate_permissions = populate_permissions
|
|
92
100
|
self._populate_claims = populate_claims
|
|
101
|
+
self._flatten_claims = flatten_claims
|
|
102
|
+
self._identity_model = identity_model
|
|
93
103
|
self._change_password_supported = change_password_supported
|
|
94
104
|
|
|
95
105
|
def authenticate(self, credentials: PasswordCredentials) -> Identity:
|
|
@@ -237,14 +247,17 @@ class OIDCProvider(JWTProviderMixin):
|
|
|
237
247
|
|
|
238
248
|
audience = self._extract_audience(claims)
|
|
239
249
|
|
|
240
|
-
|
|
250
|
+
if self._populate_claims and self._flatten_claims:
|
|
251
|
+
claims = self._identity_model.flatten_single_value_claims(claims)
|
|
252
|
+
|
|
253
|
+
identity = self._identity_model(
|
|
241
254
|
subject=str(subject),
|
|
242
255
|
username=username,
|
|
243
256
|
email=self._get_claim(claims, "email"),
|
|
244
257
|
display_name=self._get_claim(claims, "display_name"),
|
|
245
258
|
groups=groups,
|
|
246
259
|
permissions=permissions,
|
|
247
|
-
claims=
|
|
260
|
+
claims=claims if self._populate_claims else {},
|
|
248
261
|
issued_at=issued_at,
|
|
249
262
|
audience=audience,
|
|
250
263
|
role=self._get_claim(claims, "role"),
|
|
@@ -403,6 +416,7 @@ class KeyCloakProvider(OIDCProvider):
|
|
|
403
416
|
populate_groups: bool = True,
|
|
404
417
|
populate_permissions: bool = False,
|
|
405
418
|
populate_claims: bool = False,
|
|
419
|
+
flatten_claims: bool = True,
|
|
406
420
|
change_password_supported: bool = False,
|
|
407
421
|
) -> None:
|
|
408
422
|
"""Initialize KeyCloakProvider.
|
|
@@ -427,6 +441,9 @@ class KeyCloakProvider(OIDCProvider):
|
|
|
427
441
|
Whether to populate permissions on the Identity.
|
|
428
442
|
populate_claims
|
|
429
443
|
Whether to include raw claims on the Identity.
|
|
444
|
+
flatten_claims
|
|
445
|
+
Whether to flatten single-value claims when populating raw claims
|
|
446
|
+
on the Identity.
|
|
430
447
|
change_password_supported
|
|
431
448
|
Whether this provider supports changing passwords.
|
|
432
449
|
"""
|
|
@@ -437,5 +454,6 @@ class KeyCloakProvider(OIDCProvider):
|
|
|
437
454
|
populate_groups=populate_groups,
|
|
438
455
|
populate_permissions=populate_permissions,
|
|
439
456
|
populate_claims=populate_claims,
|
|
457
|
+
flatten_claims=flatten_claims,
|
|
440
458
|
change_password_supported=change_password_supported,
|
|
441
459
|
)
|
|
@@ -74,6 +74,7 @@ class AuthenticationService:
|
|
|
74
74
|
user_model: type[User] = User,
|
|
75
75
|
group_model: type[Group] = Group,
|
|
76
76
|
token_model: type[Token] = Token,
|
|
77
|
+
identity_model: type[Identity] = Identity,
|
|
77
78
|
users_repository_name: str = "users",
|
|
78
79
|
groups_repository_name: str = "groups",
|
|
79
80
|
refresh_repository: RefreshRepository | None = None,
|
|
@@ -147,6 +148,9 @@ class AuthenticationService:
|
|
|
147
148
|
Group model class to use for database operations, by default Group
|
|
148
149
|
token_model
|
|
149
150
|
Token model class to use for database operations, by default Token
|
|
151
|
+
identity_model
|
|
152
|
+
Identity model class to use for object creation and manipulation,
|
|
153
|
+
by default Identity
|
|
150
154
|
users_repository_name
|
|
151
155
|
Name of the user repository in the UnitOfWork, by default "users"
|
|
152
156
|
groups_repository_name
|
|
@@ -206,6 +210,7 @@ class AuthenticationService:
|
|
|
206
210
|
self._user_model = user_model
|
|
207
211
|
self._group_model = group_model
|
|
208
212
|
self._token_model = token_model
|
|
213
|
+
self._identity_model = identity_model
|
|
209
214
|
self._users_repository_name = users_repository_name
|
|
210
215
|
self._groups_repository_name = groups_repository_name
|
|
211
216
|
self._refresh_repository = (
|
|
@@ -265,7 +270,7 @@ class AuthenticationService:
|
|
|
265
270
|
and credentials.username == self._static_user.username
|
|
266
271
|
and credentials.password == self._static_user.password
|
|
267
272
|
):
|
|
268
|
-
identity =
|
|
273
|
+
identity = self._identity_model.from_user(self._static_user)
|
|
269
274
|
|
|
270
275
|
# Use the identity provider to authenticate the user and retrieve their
|
|
271
276
|
# identity
|
|
@@ -464,7 +469,7 @@ class AuthenticationService:
|
|
|
464
469
|
payload = self._identity_provider.token_factory.get_payload(
|
|
465
470
|
token=auth_token, options={"verify_exp": False}
|
|
466
471
|
)
|
|
467
|
-
identity =
|
|
472
|
+
identity = self._identity_model.from_dict(payload)
|
|
468
473
|
except Exception:
|
|
469
474
|
identity = None
|
|
470
475
|
|
|
@@ -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,20 +1,21 @@
|
|
|
1
|
-
alpha/__init__.py,sha256=
|
|
1
|
+
alpha/__init__.py,sha256=UAk8NgfhXqPTrNJH_bVTGPCuAHCp77vf63ycZ5FBnp0,6810
|
|
2
2
|
alpha/cli.py,sha256=YTWM7lzmydYazXMJ6LULywvJTMHzvfTO6yNuPrUgHCY,5813
|
|
3
3
|
alpha/encoder.py,sha256=dPFDfCofX2ehhOZuC7EEoj00l5sclE-BAvq01oQpo4o,2891
|
|
4
4
|
alpha/exceptions.py,sha256=AHoFMPyHvjj6j_1X2TS40dSaFBzCDtgzAmucimSZjfc,5793
|
|
5
5
|
alpha/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
alpha/adapters/__init__.py,sha256=pGpAtLuVs-jOyRShjO1BwSHpB5MyStJ3Hs9wPm49Q2s,201
|
|
7
7
|
alpha/adapters/rest_api_unit_of_work.py,sha256=ttUjCaNW8wKL93O_dH4XfjSgwMOBhubdxcyt3G_1Qrk,3589
|
|
8
|
-
alpha/adapters/sqla_unit_of_work.py,sha256=
|
|
8
|
+
alpha/adapters/sqla_unit_of_work.py,sha256=eRpNc0EQC8RKQpqP8Sxmo0EHCn2ur5DRupLkhxye-wc,4304
|
|
9
9
|
alpha/containers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
alpha/containers/container.py,sha256=NJW3ApUauhtwd2ifHGo0J6-F8eJwGdq3xAe5KGQXXqU,6509
|
|
11
|
-
alpha/domain/__init__.py,sha256=
|
|
12
|
-
alpha/domain/models/__init__.py,sha256=
|
|
11
|
+
alpha/domain/__init__.py,sha256=RNRqFDYw0KhFb4yPa9kC_otFGNH7wakpm3lk5y6iGVs,578
|
|
12
|
+
alpha/domain/models/__init__.py,sha256=RNRqFDYw0KhFb4yPa9kC_otFGNH7wakpm3lk5y6iGVs,578
|
|
13
13
|
alpha/domain/models/base_model.py,sha256=eI6ff0qugsQhWZahBoGH7xWMVY9Mhss9XdGR3yecG28,1826
|
|
14
|
-
alpha/domain/models/group.py,sha256=
|
|
14
|
+
alpha/domain/models/group.py,sha256=yrSDbt1MsQ-8rYl57jKlA-gJAn8q_qseT7T0LwgvFSs,4144
|
|
15
15
|
alpha/domain/models/life_cycle_base.py,sha256=xRwcTPRR0pHwsBN_dyaN8d1AVuKOEF6iNCS0IWZntDY,875
|
|
16
|
+
alpha/domain/models/permission.py,sha256=-5wcJwS-_LVADpH2sr5wC6s2AqolS5JrdEdZ1jym9RI,3175
|
|
16
17
|
alpha/domain/models/role.py,sha256=95cUc8VDB7o_IXJdTBF0KFJUyvOfRtpNkxTslzpVqIQ,4614
|
|
17
|
-
alpha/domain/models/user.py,sha256=
|
|
18
|
+
alpha/domain/models/user.py,sha256=H64rNrjY9VV-ljux_HIEyxT0O4zlLaIXMzZLEyCKLZY,6868
|
|
18
19
|
alpha/factories/__init__.py,sha256=baxoq0UY7UYust7NiR9wT954nJw5bfgSmOWvLjBt2C4,278
|
|
19
20
|
alpha/factories/_type_conversion_matrix.py,sha256=mhMYpus6OFE0sZB0gQ-b1ndIOhiA1ScprvDa9tLANAM,5055
|
|
20
21
|
alpha/factories/_type_mapping.py,sha256=f8cRfu8KUfw1ggY0Txs6fEX2e6GaXrsNcc2SCeYFRHM,789
|
|
@@ -98,16 +99,16 @@ alpha/interfaces/unit_of_work.py,sha256=P32wTGw4N54QV_x2BBusrApANmI88wgA7bLmC-1A
|
|
|
98
99
|
alpha/interfaces/updatable.py,sha256=vGpxvqrn9dCMumP_Ty3EGs-bp3KiPG8l-v-9rfjOLSQ,472
|
|
99
100
|
alpha/mixins/__init__.py,sha256=aPTNpi4JEzdsFW9Joq0ktgtoP2MJqNLOc1uRPxJkl4A,252
|
|
100
101
|
alpha/mixins/group_lifecycle.py,sha256=MxfecqcGaAjtYlQxd-ryX29VYNJVoCBJXWXE3RsGPeQ,4464
|
|
101
|
-
alpha/mixins/jwt_provider.py,sha256=
|
|
102
|
+
alpha/mixins/jwt_provider.py,sha256=b_-9_AAVgG6HZ3vGnS8g6S_caIW5Wn4AssM95B-5mhk,2080
|
|
102
103
|
alpha/mixins/user_lifecycle.py,sha256=iQPHnAobRhNJyZ5is0WfKFpOKy0y_9w_VNiMwQxYp4U,5418
|
|
103
104
|
alpha/providers/__init__.py,sha256=DM0Zu_hYNc9JI7cKVQD7J84lwpv5-GNTntMzM1k_ZPI,938
|
|
104
105
|
alpha/providers/api_key_provider.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
105
|
-
alpha/providers/database_provider.py,sha256=
|
|
106
|
-
alpha/providers/ldap_provider.py,sha256=
|
|
107
|
-
alpha/providers/oidc_provider.py,sha256=
|
|
106
|
+
alpha/providers/database_provider.py,sha256=uzNTXUHL1uRQNwsAujgEVqjuL5su_HKDSqmp-zqZunM,8261
|
|
107
|
+
alpha/providers/ldap_provider.py,sha256=nZhSvLXl-dWN6bEBQ6UO8r-xpb_QzY2m7mkzQA7aigc,14267
|
|
108
|
+
alpha/providers/oidc_provider.py,sha256=8-VWHzWYXpgwfqi64hnMYcT7Hp1p8Gr8I1EIPDgY6nA,14603
|
|
108
109
|
alpha/providers/models/__init__.py,sha256=SI-qTA3JMOxXx4o1h7frk8x3PRj0g_GSCEbLzyvw7CI,409
|
|
109
110
|
alpha/providers/models/credentials.py,sha256=IjGm1MpnUrwnTo1Of5eacNtkh72Iqj2uKTsoyq1po_o,1304
|
|
110
|
-
alpha/providers/models/identity.py,sha256=
|
|
111
|
+
alpha/providers/models/identity.py,sha256=M8b7uiaVYvtCgGV2nxEWrPPgbxUiWPbI-tGcMmaHtbE,16285
|
|
111
112
|
alpha/providers/models/token.py,sha256=xLiE-bWufbcWgkKpar5R2VAE8YhjyhWF44yN0Me9sJw,4793
|
|
112
113
|
alpha/repositories/__init__.py,sha256=i4kyfLbLSHUzrqDF9Q96CgEaK-srPtEIspUD6uUjElk,787
|
|
113
114
|
alpha/repositories/rest_api_repository.py,sha256=BC_M5_HPwWKwMRsHI2PNCmqu0SC5Tw9ZGhZ7eaLIFgw,48702
|
|
@@ -120,7 +121,7 @@ alpha/repositories/refresh/database_repository.py,sha256=b5Z0_nVairZOaqQ6OBb6_X2
|
|
|
120
121
|
alpha/repositories/refresh/file_repository.py,sha256=EoygSFD7XLzSmnY1W2d3pdlNbQ0rcxwhRGNrlbMH62g,5490
|
|
121
122
|
alpha/repositories/refresh/memory_repository.py,sha256=r0P2DVgTTDSqV8g1OaNf8knqpiysb-LaWW0pdS2wZaQ,3926
|
|
122
123
|
alpha/services/__init__.py,sha256=yeUbS-N0127DSejbCAv9jbdKuHRa_H5Ue0CtsWf4ghI,224
|
|
123
|
-
alpha/services/authentication_service.py,sha256=
|
|
124
|
+
alpha/services/authentication_service.py,sha256=X1CPvXel_R9PRBlwh-6RDJdodgPq1MbbiUUUKnAMc3Y,29846
|
|
124
125
|
alpha/services/user_lifecycle_management.py,sha256=yj0ITtD_WOCVo54KaNSC29XPixxdKi9ArvCMeQm6WKA,2696
|
|
125
126
|
alpha/services/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
126
127
|
alpha/services/models/cookie.py,sha256=vxNLPtMmOGlxFLKdccxEV7hd_mPKmFCEao8SczNOilo,132
|
|
@@ -143,9 +144,9 @@ alpha/utils/openapi_test/models.py,sha256=POqDD2v2VqPmVLnbvgZ0kVlVzL9Are1oFHmuyy
|
|
|
143
144
|
alpha/utils/openapi_test/orm.py,sha256=Py95GV_0e7wI1MYDZR1_EHkvTgev6eNo70SVmnD7kGc,3045
|
|
144
145
|
alpha/utils/openapi_test/response.py,sha256=IxbQ6Nw258LLViHkgfjOpc7zWGTP8ofVOX1zrwDoR50,270
|
|
145
146
|
alpha/utils/openapi_test/service.py,sha256=ycrEUlQmygKthnOhEiir-wtbPFi5cT-bepVclaL9FAk,5003
|
|
146
|
-
alpha_python-0.7.
|
|
147
|
-
alpha_python-0.7.
|
|
148
|
-
alpha_python-0.7.
|
|
149
|
-
alpha_python-0.7.
|
|
150
|
-
alpha_python-0.7.
|
|
151
|
-
alpha_python-0.7.
|
|
147
|
+
alpha_python-0.7.6.dist-info/licenses/LICENSE,sha256=5KwEqC3KUoH4lVXgZ9tGriKOl-LGxHkXBWo16mFmAYM,1070
|
|
148
|
+
alpha_python-0.7.6.dist-info/METADATA,sha256=7cdXcm4nOikzottZr80DfxfLbr95LIAX9TSmX_b3xas,8639
|
|
149
|
+
alpha_python-0.7.6.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
150
|
+
alpha_python-0.7.6.dist-info/entry_points.txt,sha256=LBEXdcofOugYYdZ46nz5Dxj_aj1QbRBkumfPGhy-GXI,41
|
|
151
|
+
alpha_python-0.7.6.dist-info/top_level.txt,sha256=tqmNnOmi2RSSiPo99C03fD5Cc3r9za9xTjPAoQC1EGA,6
|
|
152
|
+
alpha_python-0.7.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|