baobab-auth-core 0.4.0__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.
- baobab_auth_core/__init__.py +111 -0
- baobab_auth_core/application/__init__.py +1 -0
- baobab_auth_core/application/commands/__init__.py +35 -0
- baobab_auth_core/application/commands/assign_role_command.py +22 -0
- baobab_auth_core/application/commands/authenticate_user_command.py +20 -0
- baobab_auth_core/application/commands/change_password_command.py +22 -0
- baobab_auth_core/application/commands/logout_command.py +20 -0
- baobab_auth_core/application/commands/refresh_session_command.py +20 -0
- baobab_auth_core/application/commands/register_user_command.py +20 -0
- baobab_auth_core/application/commands/remove_role_command.py +22 -0
- baobab_auth_core/application/commands/revoke_all_sessions_command.py +20 -0
- baobab_auth_core/application/commands/revoke_session_command.py +20 -0
- baobab_auth_core/application/results/__init__.py +17 -0
- baobab_auth_core/application/results/auth_context.py +40 -0
- baobab_auth_core/application/results/authenticate_user_result.py +22 -0
- baobab_auth_core/application/results/refresh_session_result.py +20 -0
- baobab_auth_core/application/results/register_user_result.py +16 -0
- baobab_auth_core/application/services/__init__.py +7 -0
- baobab_auth_core/application/services/authorization_service.py +56 -0
- baobab_auth_core/application/use_cases/__init__.py +23 -0
- baobab_auth_core/application/use_cases/assign_role.py +96 -0
- baobab_auth_core/application/use_cases/authenticate_user.py +185 -0
- baobab_auth_core/application/use_cases/change_password.py +99 -0
- baobab_auth_core/application/use_cases/logout.py +73 -0
- baobab_auth_core/application/use_cases/refresh_session.py +98 -0
- baobab_auth_core/application/use_cases/register_user.py +114 -0
- baobab_auth_core/application/use_cases/remove_role.py +103 -0
- baobab_auth_core/application/use_cases/revoke_all_sessions.py +94 -0
- baobab_auth_core/application/use_cases/revoke_session.py +97 -0
- baobab_auth_core/domain/__init__.py +1 -0
- baobab_auth_core/domain/entities/__init__.py +10 -0
- baobab_auth_core/domain/entities/audit_event.py +50 -0
- baobab_auth_core/domain/entities/permission.py +49 -0
- baobab_auth_core/domain/entities/role.py +69 -0
- baobab_auth_core/domain/entities/session.py +91 -0
- baobab_auth_core/domain/entities/user.py +144 -0
- baobab_auth_core/domain/entities/user_profile.py +103 -0
- baobab_auth_core/domain/enums/__init__.py +7 -0
- baobab_auth_core/domain/enums/audit_event_type.py +27 -0
- baobab_auth_core/domain/enums/session_status.py +14 -0
- baobab_auth_core/domain/enums/user_status.py +16 -0
- baobab_auth_core/domain/policies/__init__.py +15 -0
- baobab_auth_core/domain/policies/lockout_policy.py +25 -0
- baobab_auth_core/domain/policies/password_policy.py +62 -0
- baobab_auth_core/domain/policies/permission_policy.py +15 -0
- baobab_auth_core/domain/policies/role_policy.py +19 -0
- baobab_auth_core/domain/policies/session_policy.py +37 -0
- baobab_auth_core/domain/ports/__init__.py +25 -0
- baobab_auth_core/domain/ports/audit_repository.py +29 -0
- baobab_auth_core/domain/ports/clock.py +20 -0
- baobab_auth_core/domain/ports/id_generator.py +18 -0
- baobab_auth_core/domain/ports/password_hasher.py +31 -0
- baobab_auth_core/domain/ports/permission_repository.py +51 -0
- baobab_auth_core/domain/ports/role_repository.py +51 -0
- baobab_auth_core/domain/ports/session_repository.py +46 -0
- baobab_auth_core/domain/ports/token_provider.py +20 -0
- baobab_auth_core/domain/ports/unit_of_work.py +48 -0
- baobab_auth_core/domain/ports/user_repository.py +60 -0
- baobab_auth_core/domain/value_objects/__init__.py +23 -0
- baobab_auth_core/domain/value_objects/auth_subject.py +24 -0
- baobab_auth_core/domain/value_objects/email.py +27 -0
- baobab_auth_core/domain/value_objects/password.py +26 -0
- baobab_auth_core/domain/value_objects/password_hash.py +25 -0
- baobab_auth_core/domain/value_objects/permission_name.py +27 -0
- baobab_auth_core/domain/value_objects/role_name.py +27 -0
- baobab_auth_core/domain/value_objects/session_id.py +24 -0
- baobab_auth_core/domain/value_objects/token_id.py +24 -0
- baobab_auth_core/domain/value_objects/utc_datetime.py +27 -0
- baobab_auth_core/exceptions/__init__.py +74 -0
- baobab_auth_core/exceptions/authentication.py +23 -0
- baobab_auth_core/exceptions/authorization.py +19 -0
- baobab_auth_core/exceptions/base.py +10 -0
- baobab_auth_core/exceptions/role.py +19 -0
- baobab_auth_core/exceptions/session.py +15 -0
- baobab_auth_core/exceptions/user.py +27 -0
- baobab_auth_core/exceptions/validation.py +19 -0
- baobab_auth_core/testing/__init__.py +29 -0
- baobab_auth_core/testing/fake_clock.py +34 -0
- baobab_auth_core/testing/fake_id_generator.py +25 -0
- baobab_auth_core/testing/fake_password_hasher.py +33 -0
- baobab_auth_core/testing/fake_token_provider.py +27 -0
- baobab_auth_core/testing/in_memory_audit_repository.py +32 -0
- baobab_auth_core/testing/in_memory_permission_repository.py +55 -0
- baobab_auth_core/testing/in_memory_role_repository.py +55 -0
- baobab_auth_core/testing/in_memory_session_repository.py +54 -0
- baobab_auth_core/testing/in_memory_unit_of_work.py +64 -0
- baobab_auth_core/testing/in_memory_user_repository.py +67 -0
- baobab_auth_core-0.4.0.dist-info/METADATA +247 -0
- baobab_auth_core-0.4.0.dist-info/RECORD +91 -0
- baobab_auth_core-0.4.0.dist-info/WHEEL +4 -0
- baobab_auth_core-0.4.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"""baobab-auth-core — librairie métier d'authentification de l'écosystème Baobab."""
|
|
2
|
+
|
|
3
|
+
# --- Couche domaine : entités, énumérations, ports, politiques ---
|
|
4
|
+
# --- Couche application : commandes, résultats, services, cas d'usage ---
|
|
5
|
+
from baobab_auth_core.application.commands import (
|
|
6
|
+
AssignRoleCommand,
|
|
7
|
+
AuthenticateUserCommand,
|
|
8
|
+
ChangePasswordCommand,
|
|
9
|
+
LogoutCommand,
|
|
10
|
+
RefreshSessionCommand,
|
|
11
|
+
RegisterUserCommand,
|
|
12
|
+
RemoveRoleCommand,
|
|
13
|
+
RevokeAllSessionsCommand,
|
|
14
|
+
RevokeSessionCommand,
|
|
15
|
+
)
|
|
16
|
+
from baobab_auth_core.application.results import (
|
|
17
|
+
AuthContext,
|
|
18
|
+
AuthenticateUserResult,
|
|
19
|
+
RefreshSessionResult,
|
|
20
|
+
RegisterUserResult,
|
|
21
|
+
)
|
|
22
|
+
from baobab_auth_core.application.services import AuthorizationService
|
|
23
|
+
from baobab_auth_core.application.use_cases import (
|
|
24
|
+
AssignRole,
|
|
25
|
+
AuthenticateUser,
|
|
26
|
+
ChangePassword,
|
|
27
|
+
Logout,
|
|
28
|
+
RefreshSession,
|
|
29
|
+
RegisterUser,
|
|
30
|
+
RemoveRole,
|
|
31
|
+
RevokeAllSessions,
|
|
32
|
+
RevokeSession,
|
|
33
|
+
)
|
|
34
|
+
from baobab_auth_core.domain.entities import (
|
|
35
|
+
AuditEvent,
|
|
36
|
+
Permission,
|
|
37
|
+
Role,
|
|
38
|
+
Session,
|
|
39
|
+
User,
|
|
40
|
+
UserProfile,
|
|
41
|
+
)
|
|
42
|
+
from baobab_auth_core.domain.enums import AuditEventType, SessionStatus, UserStatus
|
|
43
|
+
from baobab_auth_core.domain.policies import (
|
|
44
|
+
LockoutPolicy,
|
|
45
|
+
PasswordPolicy,
|
|
46
|
+
PermissionPolicy,
|
|
47
|
+
RolePolicy,
|
|
48
|
+
SessionPolicy,
|
|
49
|
+
)
|
|
50
|
+
from baobab_auth_core.domain.ports import (
|
|
51
|
+
AuditRepository,
|
|
52
|
+
Clock,
|
|
53
|
+
IdGenerator,
|
|
54
|
+
PasswordHasher,
|
|
55
|
+
PermissionRepository,
|
|
56
|
+
RoleRepository,
|
|
57
|
+
SessionRepository,
|
|
58
|
+
TokenProvider,
|
|
59
|
+
UnitOfWork,
|
|
60
|
+
UserRepository,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"AssignRole",
|
|
65
|
+
"AssignRoleCommand",
|
|
66
|
+
"AuditEvent",
|
|
67
|
+
"AuditEventType",
|
|
68
|
+
"AuditRepository",
|
|
69
|
+
"AuthContext",
|
|
70
|
+
"AuthenticateUser",
|
|
71
|
+
"AuthenticateUserCommand",
|
|
72
|
+
"AuthenticateUserResult",
|
|
73
|
+
"AuthorizationService",
|
|
74
|
+
"ChangePassword",
|
|
75
|
+
"ChangePasswordCommand",
|
|
76
|
+
"Clock",
|
|
77
|
+
"IdGenerator",
|
|
78
|
+
"LockoutPolicy",
|
|
79
|
+
"Logout",
|
|
80
|
+
"LogoutCommand",
|
|
81
|
+
"PasswordHasher",
|
|
82
|
+
"PasswordPolicy",
|
|
83
|
+
"Permission",
|
|
84
|
+
"PermissionPolicy",
|
|
85
|
+
"PermissionRepository",
|
|
86
|
+
"RefreshSession",
|
|
87
|
+
"RefreshSessionCommand",
|
|
88
|
+
"RefreshSessionResult",
|
|
89
|
+
"RegisterUser",
|
|
90
|
+
"RegisterUserCommand",
|
|
91
|
+
"RegisterUserResult",
|
|
92
|
+
"RemoveRole",
|
|
93
|
+
"RemoveRoleCommand",
|
|
94
|
+
"RevokeAllSessions",
|
|
95
|
+
"RevokeAllSessionsCommand",
|
|
96
|
+
"RevokeSession",
|
|
97
|
+
"RevokeSessionCommand",
|
|
98
|
+
"Role",
|
|
99
|
+
"RolePolicy",
|
|
100
|
+
"RoleRepository",
|
|
101
|
+
"Session",
|
|
102
|
+
"SessionPolicy",
|
|
103
|
+
"SessionRepository",
|
|
104
|
+
"SessionStatus",
|
|
105
|
+
"TokenProvider",
|
|
106
|
+
"UnitOfWork",
|
|
107
|
+
"User",
|
|
108
|
+
"UserProfile",
|
|
109
|
+
"UserRepository",
|
|
110
|
+
"UserStatus",
|
|
111
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Couche application : commandes, résultats et cas d'usage."""
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Commandes de la couche application."""
|
|
2
|
+
|
|
3
|
+
from baobab_auth_core.application.commands.assign_role_command import AssignRoleCommand
|
|
4
|
+
from baobab_auth_core.application.commands.authenticate_user_command import (
|
|
5
|
+
AuthenticateUserCommand,
|
|
6
|
+
)
|
|
7
|
+
from baobab_auth_core.application.commands.change_password_command import (
|
|
8
|
+
ChangePasswordCommand,
|
|
9
|
+
)
|
|
10
|
+
from baobab_auth_core.application.commands.logout_command import LogoutCommand
|
|
11
|
+
from baobab_auth_core.application.commands.refresh_session_command import (
|
|
12
|
+
RefreshSessionCommand,
|
|
13
|
+
)
|
|
14
|
+
from baobab_auth_core.application.commands.register_user_command import (
|
|
15
|
+
RegisterUserCommand,
|
|
16
|
+
)
|
|
17
|
+
from baobab_auth_core.application.commands.remove_role_command import RemoveRoleCommand
|
|
18
|
+
from baobab_auth_core.application.commands.revoke_all_sessions_command import (
|
|
19
|
+
RevokeAllSessionsCommand,
|
|
20
|
+
)
|
|
21
|
+
from baobab_auth_core.application.commands.revoke_session_command import (
|
|
22
|
+
RevokeSessionCommand,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"AssignRoleCommand",
|
|
27
|
+
"AuthenticateUserCommand",
|
|
28
|
+
"ChangePasswordCommand",
|
|
29
|
+
"LogoutCommand",
|
|
30
|
+
"RefreshSessionCommand",
|
|
31
|
+
"RegisterUserCommand",
|
|
32
|
+
"RemoveRoleCommand",
|
|
33
|
+
"RevokeAllSessionsCommand",
|
|
34
|
+
"RevokeSessionCommand",
|
|
35
|
+
]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Commande : attribution d'un rôle à un utilisateur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class AssignRoleCommand:
|
|
8
|
+
"""Commande d'attribution d'un rôle à un utilisateur cible.
|
|
9
|
+
|
|
10
|
+
:param target_user_id: identifiant interne de l'utilisateur cible.
|
|
11
|
+
:param role_name: nom du rôle à attribuer.
|
|
12
|
+
:param actor_subject: sujet d'authentification de l'acteur (doit être ADMIN+).
|
|
13
|
+
:param ip_address: adresse IP de la requête (audit).
|
|
14
|
+
:param user_agent: user-agent de la requête (audit).
|
|
15
|
+
:spec: FEAT-003.1
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
target_user_id: str
|
|
19
|
+
role_name: str
|
|
20
|
+
actor_subject: str
|
|
21
|
+
ip_address: str
|
|
22
|
+
user_agent: str
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Commande d'authentification d'un utilisateur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class AuthenticateUserCommand:
|
|
8
|
+
"""Données d'entrée pour le cas d'usage AuthenticateUser.
|
|
9
|
+
|
|
10
|
+
:param email: adresse e-mail brute de l'utilisateur.
|
|
11
|
+
:param password: mot de passe en clair.
|
|
12
|
+
:param ip_address: adresse IP de l'appelant.
|
|
13
|
+
:param user_agent: user-agent de l'appelant.
|
|
14
|
+
:spec: FEAT-002.2
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
email: str
|
|
18
|
+
password: str
|
|
19
|
+
ip_address: str
|
|
20
|
+
user_agent: str
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Commande : changement de mot de passe par l'utilisateur lui-même."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class ChangePasswordCommand:
|
|
8
|
+
"""Données d'entrée pour le cas d'usage ChangePassword.
|
|
9
|
+
|
|
10
|
+
:param actor_subject: sujet d'authentification de l'acteur.
|
|
11
|
+
:param old_password: mot de passe actuel (en clair).
|
|
12
|
+
:param new_password: nouveau mot de passe souhaité (en clair).
|
|
13
|
+
:param ip_address: adresse IP de l'appelant.
|
|
14
|
+
:param user_agent: user-agent de l'appelant.
|
|
15
|
+
:spec: FEAT-004.2
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
actor_subject: str
|
|
19
|
+
old_password: str
|
|
20
|
+
new_password: str
|
|
21
|
+
ip_address: str
|
|
22
|
+
user_agent: str
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Commande de déconnexion d'un utilisateur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class LogoutCommand:
|
|
8
|
+
"""Données d'entrée pour le cas d'usage Logout.
|
|
9
|
+
|
|
10
|
+
:param session_id: identifiant de la session à révoquer.
|
|
11
|
+
:param actor_subject: sujet de l'acteur initiant la déconnexion (ou ``None``).
|
|
12
|
+
:param ip_address: adresse IP de l'appelant.
|
|
13
|
+
:param user_agent: user-agent de l'appelant.
|
|
14
|
+
:spec: FEAT-002.4
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
session_id: str
|
|
18
|
+
actor_subject: str | None
|
|
19
|
+
ip_address: str
|
|
20
|
+
user_agent: str
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Commande de rafraîchissement d'une session."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class RefreshSessionCommand:
|
|
8
|
+
"""Données d'entrée pour le cas d'usage RefreshSession.
|
|
9
|
+
|
|
10
|
+
:param session_id: identifiant de la session à rafraîchir.
|
|
11
|
+
:param refresh_token_id: valeur du refresh token présenté par le client.
|
|
12
|
+
:param ip_address: adresse IP de l'appelant.
|
|
13
|
+
:param user_agent: user-agent de l'appelant.
|
|
14
|
+
:spec: FEAT-002.3
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
session_id: str
|
|
18
|
+
refresh_token_id: str
|
|
19
|
+
ip_address: str
|
|
20
|
+
user_agent: str
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Commande d'inscription d'un nouvel utilisateur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class RegisterUserCommand:
|
|
8
|
+
"""Données d'entrée pour le cas d'usage RegisterUser.
|
|
9
|
+
|
|
10
|
+
:param email: adresse e-mail brute de l'utilisateur.
|
|
11
|
+
:param password: mot de passe en clair (validé et haché par le use case).
|
|
12
|
+
:param ip_address: adresse IP de l'appelant.
|
|
13
|
+
:param user_agent: user-agent de l'appelant.
|
|
14
|
+
:spec: FEAT-002.1
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
email: str
|
|
18
|
+
password: str
|
|
19
|
+
ip_address: str
|
|
20
|
+
user_agent: str
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Commande : retrait d'un rôle d'un utilisateur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class RemoveRoleCommand:
|
|
8
|
+
"""Commande de retrait d'un rôle d'un utilisateur cible.
|
|
9
|
+
|
|
10
|
+
:param target_user_id: identifiant interne de l'utilisateur cible.
|
|
11
|
+
:param role_name: nom du rôle à retirer.
|
|
12
|
+
:param actor_subject: sujet d'authentification de l'acteur (doit être ADMIN+).
|
|
13
|
+
:param ip_address: adresse IP de la requête (audit).
|
|
14
|
+
:param user_agent: user-agent de la requête (audit).
|
|
15
|
+
:spec: FEAT-003.2
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
target_user_id: str
|
|
19
|
+
role_name: str
|
|
20
|
+
actor_subject: str
|
|
21
|
+
ip_address: str
|
|
22
|
+
user_agent: str
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Commande : révocation globale de toutes les sessions d'un utilisateur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class RevokeAllSessionsCommand:
|
|
8
|
+
"""Données d'entrée pour le cas d'usage RevokeAllSessions.
|
|
9
|
+
|
|
10
|
+
:param target_user_id: identifiant interne de l'utilisateur cible.
|
|
11
|
+
:param actor_subject: sujet d'authentification de l'acteur.
|
|
12
|
+
:param ip_address: adresse IP de l'appelant.
|
|
13
|
+
:param user_agent: user-agent de l'appelant.
|
|
14
|
+
:spec: FEAT-004.3
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
target_user_id: str
|
|
18
|
+
actor_subject: str
|
|
19
|
+
ip_address: str
|
|
20
|
+
user_agent: str
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Commande de révocation d'une session par un acteur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class RevokeSessionCommand:
|
|
8
|
+
"""Données d'entrée pour le cas d'usage RevokeSession.
|
|
9
|
+
|
|
10
|
+
:param session_id: identifiant de la session à révoquer.
|
|
11
|
+
:param actor_subject: sujet de l'acteur demandant la révocation.
|
|
12
|
+
:param ip_address: adresse IP de l'appelant.
|
|
13
|
+
:param user_agent: user-agent de l'appelant.
|
|
14
|
+
:spec: FEAT-002.5
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
session_id: str
|
|
18
|
+
actor_subject: str
|
|
19
|
+
ip_address: str
|
|
20
|
+
user_agent: str
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Résultats de la couche application."""
|
|
2
|
+
|
|
3
|
+
from baobab_auth_core.application.results.auth_context import AuthContext
|
|
4
|
+
from baobab_auth_core.application.results.authenticate_user_result import (
|
|
5
|
+
AuthenticateUserResult,
|
|
6
|
+
)
|
|
7
|
+
from baobab_auth_core.application.results.refresh_session_result import (
|
|
8
|
+
RefreshSessionResult,
|
|
9
|
+
)
|
|
10
|
+
from baobab_auth_core.application.results.register_user_result import RegisterUserResult
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"AuthContext",
|
|
14
|
+
"AuthenticateUserResult",
|
|
15
|
+
"RefreshSessionResult",
|
|
16
|
+
"RegisterUserResult",
|
|
17
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Contexte d'autorisation d'un acteur authentifié."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from baobab_auth_core.domain.value_objects.auth_subject import AuthSubject
|
|
6
|
+
from baobab_auth_core.domain.value_objects.permission_name import PermissionName
|
|
7
|
+
from baobab_auth_core.domain.value_objects.role_name import RoleName
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True, slots=True)
|
|
11
|
+
class AuthContext:
|
|
12
|
+
"""Contexte d'autorisation construit depuis le référentiel des rôles.
|
|
13
|
+
|
|
14
|
+
Regroupe les rôles et les permissions effectifs d'un acteur authentifié.
|
|
15
|
+
|
|
16
|
+
:param auth_subject: identifiant stable de l'acteur.
|
|
17
|
+
:param roles: ensemble figé des rôles de l'acteur.
|
|
18
|
+
:param permissions: ensemble figé des permissions cumulées.
|
|
19
|
+
:spec: FEAT-003.3
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
auth_subject: AuthSubject
|
|
23
|
+
roles: frozenset[RoleName]
|
|
24
|
+
permissions: frozenset[PermissionName]
|
|
25
|
+
|
|
26
|
+
def has_role(self, role: RoleName) -> bool:
|
|
27
|
+
"""Retourne ``True`` si l'acteur possède le rôle donné.
|
|
28
|
+
|
|
29
|
+
:param role: rôle à vérifier.
|
|
30
|
+
:returns: ``True`` si le rôle est présent.
|
|
31
|
+
"""
|
|
32
|
+
return role in self.roles
|
|
33
|
+
|
|
34
|
+
def has_permission(self, permission: PermissionName) -> bool:
|
|
35
|
+
"""Retourne ``True`` si l'acteur possède la permission donnée.
|
|
36
|
+
|
|
37
|
+
:param permission: permission à vérifier.
|
|
38
|
+
:returns: ``True`` si la permission est présente.
|
|
39
|
+
"""
|
|
40
|
+
return permission in self.permissions
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Résultat de l'authentification d'un utilisateur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from baobab_auth_core.domain.value_objects.utc_datetime import UtcDatetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class AuthenticateUserResult:
|
|
10
|
+
"""Résultat du cas d'usage AuthenticateUser.
|
|
11
|
+
|
|
12
|
+
:param session_id: identifiant de la session créée.
|
|
13
|
+
:param refresh_token_id: identifiant du refresh token émis.
|
|
14
|
+
:param user_id: identifiant de l'utilisateur authentifié.
|
|
15
|
+
:param expires_at: date d'expiration de la session (UTC).
|
|
16
|
+
:spec: FEAT-002.2
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
session_id: str
|
|
20
|
+
refresh_token_id: str
|
|
21
|
+
user_id: str
|
|
22
|
+
expires_at: UtcDatetime
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""Résultat du rafraîchissement d'une session."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
from baobab_auth_core.domain.value_objects.utc_datetime import UtcDatetime
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True, slots=True)
|
|
9
|
+
class RefreshSessionResult:
|
|
10
|
+
"""Résultat du cas d'usage RefreshSession.
|
|
11
|
+
|
|
12
|
+
:param session_id: identifiant de la session rafraîchie.
|
|
13
|
+
:param refresh_token_id: nouvel identifiant du refresh token (rotation).
|
|
14
|
+
:param expires_at: date d'expiration de la session (UTC).
|
|
15
|
+
:spec: FEAT-002.3
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
session_id: str
|
|
19
|
+
refresh_token_id: str
|
|
20
|
+
expires_at: UtcDatetime
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Résultat de l'inscription d'un utilisateur."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@dataclass(frozen=True, slots=True)
|
|
7
|
+
class RegisterUserResult:
|
|
8
|
+
"""Résultat du cas d'usage RegisterUser.
|
|
9
|
+
|
|
10
|
+
:param user_id: identifiant interne du nouvel utilisateur.
|
|
11
|
+
:param auth_subject: sujet d'authentification opaque du nouvel utilisateur.
|
|
12
|
+
:spec: FEAT-002.1
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
user_id: str
|
|
16
|
+
auth_subject: str
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Service d'autorisation : construction du contexte d'autorisation."""
|
|
2
|
+
|
|
3
|
+
from baobab_auth_core.application.results.auth_context import AuthContext
|
|
4
|
+
from baobab_auth_core.domain.ports.role_repository import RoleRepository
|
|
5
|
+
from baobab_auth_core.domain.ports.user_repository import UserRepository
|
|
6
|
+
from baobab_auth_core.domain.value_objects.auth_subject import AuthSubject
|
|
7
|
+
from baobab_auth_core.domain.value_objects.permission_name import PermissionName
|
|
8
|
+
from baobab_auth_core.exceptions.user import UserNotFoundError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class AuthorizationService:
|
|
12
|
+
"""Construit le contexte d'autorisation d'un acteur authentifié.
|
|
13
|
+
|
|
14
|
+
Agrège les rôles de l'utilisateur et les permissions associées à chaque rôle
|
|
15
|
+
pour produire un :class:`AuthContext` utilisable dans les guards.
|
|
16
|
+
|
|
17
|
+
:param users: référentiel des utilisateurs.
|
|
18
|
+
:param roles: référentiel des rôles (pour résoudre les permissions).
|
|
19
|
+
:spec: FEAT-003.3
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
*,
|
|
25
|
+
users: UserRepository,
|
|
26
|
+
roles: RoleRepository,
|
|
27
|
+
) -> None:
|
|
28
|
+
"""Initialise le service avec ses dépendances."""
|
|
29
|
+
self._users = users
|
|
30
|
+
self._roles = roles
|
|
31
|
+
|
|
32
|
+
def build_context(self, auth_subject: AuthSubject) -> AuthContext:
|
|
33
|
+
"""Construit le contexte d'autorisation pour un acteur.
|
|
34
|
+
|
|
35
|
+
Charge l'utilisateur, puis résout les permissions de chacun de ses rôles.
|
|
36
|
+
|
|
37
|
+
:param auth_subject: identifiant de l'acteur.
|
|
38
|
+
:returns: contexte d'autorisation peuplé.
|
|
39
|
+
:raises UserNotFoundError: si l'acteur est introuvable.
|
|
40
|
+
:spec: FEAT-003.3
|
|
41
|
+
"""
|
|
42
|
+
user = self._users.get_by_auth_subject(auth_subject)
|
|
43
|
+
if user is None:
|
|
44
|
+
raise UserNotFoundError(f"utilisateur {auth_subject.value!r} introuvable")
|
|
45
|
+
|
|
46
|
+
permissions: set[PermissionName] = set()
|
|
47
|
+
for role_name in user.roles:
|
|
48
|
+
role = self._roles.get_by_name(role_name)
|
|
49
|
+
if role is not None:
|
|
50
|
+
permissions |= role.permissions
|
|
51
|
+
|
|
52
|
+
return AuthContext(
|
|
53
|
+
auth_subject=auth_subject,
|
|
54
|
+
roles=frozenset(user.roles),
|
|
55
|
+
permissions=frozenset(permissions),
|
|
56
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""Cas d'usage de la couche application."""
|
|
2
|
+
|
|
3
|
+
from baobab_auth_core.application.use_cases.assign_role import AssignRole
|
|
4
|
+
from baobab_auth_core.application.use_cases.authenticate_user import AuthenticateUser
|
|
5
|
+
from baobab_auth_core.application.use_cases.change_password import ChangePassword
|
|
6
|
+
from baobab_auth_core.application.use_cases.logout import Logout
|
|
7
|
+
from baobab_auth_core.application.use_cases.refresh_session import RefreshSession
|
|
8
|
+
from baobab_auth_core.application.use_cases.register_user import RegisterUser
|
|
9
|
+
from baobab_auth_core.application.use_cases.remove_role import RemoveRole
|
|
10
|
+
from baobab_auth_core.application.use_cases.revoke_all_sessions import RevokeAllSessions
|
|
11
|
+
from baobab_auth_core.application.use_cases.revoke_session import RevokeSession
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AssignRole",
|
|
15
|
+
"AuthenticateUser",
|
|
16
|
+
"ChangePassword",
|
|
17
|
+
"Logout",
|
|
18
|
+
"RefreshSession",
|
|
19
|
+
"RegisterUser",
|
|
20
|
+
"RemoveRole",
|
|
21
|
+
"RevokeAllSessions",
|
|
22
|
+
"RevokeSession",
|
|
23
|
+
]
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Cas d'usage : attribution d'un rôle à un utilisateur."""
|
|
2
|
+
|
|
3
|
+
from baobab_auth_core.application.commands.assign_role_command import AssignRoleCommand
|
|
4
|
+
from baobab_auth_core.domain.entities.audit_event import AuditEvent
|
|
5
|
+
from baobab_auth_core.domain.enums.audit_event_type import AuditEventType
|
|
6
|
+
from baobab_auth_core.domain.ports.audit_repository import AuditRepository
|
|
7
|
+
from baobab_auth_core.domain.ports.clock import Clock
|
|
8
|
+
from baobab_auth_core.domain.ports.id_generator import IdGenerator
|
|
9
|
+
from baobab_auth_core.domain.ports.role_repository import RoleRepository
|
|
10
|
+
from baobab_auth_core.domain.ports.unit_of_work import UnitOfWork
|
|
11
|
+
from baobab_auth_core.domain.ports.user_repository import UserRepository
|
|
12
|
+
from baobab_auth_core.domain.value_objects.auth_subject import AuthSubject
|
|
13
|
+
from baobab_auth_core.domain.value_objects.role_name import RoleName
|
|
14
|
+
from baobab_auth_core.exceptions.authorization import ForbiddenError
|
|
15
|
+
from baobab_auth_core.exceptions.role import RoleNotFoundError
|
|
16
|
+
from baobab_auth_core.exceptions.user import UserNotFoundError
|
|
17
|
+
|
|
18
|
+
_ADMIN_ROLES = {RoleName(value="ADMIN"), RoleName(value="SUPER_ADMIN")}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class AssignRole:
|
|
22
|
+
"""Attribue un rôle à un utilisateur au nom d'un acteur autorisé.
|
|
23
|
+
|
|
24
|
+
L'acteur doit posséder le rôle ``ADMIN`` ou ``SUPER_ADMIN``.
|
|
25
|
+
L'opération est idempotente : attribuer un rôle déjà présent n'a pas d'effet.
|
|
26
|
+
|
|
27
|
+
:param users: référentiel des utilisateurs.
|
|
28
|
+
:param roles: référentiel des rôles.
|
|
29
|
+
:param id_gen: générateur d'identifiants (audit).
|
|
30
|
+
:param clock: source de temps courante.
|
|
31
|
+
:param audit: référentiel d'événements d'audit.
|
|
32
|
+
:param uow: gestionnaire de transaction.
|
|
33
|
+
:spec: FEAT-003.1
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
*,
|
|
39
|
+
users: UserRepository,
|
|
40
|
+
roles: RoleRepository,
|
|
41
|
+
id_gen: IdGenerator,
|
|
42
|
+
clock: Clock,
|
|
43
|
+
audit: AuditRepository,
|
|
44
|
+
uow: UnitOfWork,
|
|
45
|
+
) -> None:
|
|
46
|
+
"""Initialise le use case avec ses dépendances."""
|
|
47
|
+
self._users = users
|
|
48
|
+
self._roles = roles
|
|
49
|
+
self._id_gen = id_gen
|
|
50
|
+
self._clock = clock
|
|
51
|
+
self._audit = audit
|
|
52
|
+
self._uow = uow
|
|
53
|
+
|
|
54
|
+
def execute(self, command: AssignRoleCommand) -> None:
|
|
55
|
+
"""Exécute l'attribution d'un rôle.
|
|
56
|
+
|
|
57
|
+
:param command: données d'entrée de l'attribution.
|
|
58
|
+
:raises ForbiddenError: si l'acteur n'est pas ADMIN ni SUPER_ADMIN.
|
|
59
|
+
:raises UserNotFoundError: si l'utilisateur cible est introuvable.
|
|
60
|
+
:raises RoleNotFoundError: si le rôle n'existe pas.
|
|
61
|
+
:spec: FEAT-003.1
|
|
62
|
+
"""
|
|
63
|
+
now = self._clock.now()
|
|
64
|
+
actor_subject = AuthSubject(value=command.actor_subject)
|
|
65
|
+
|
|
66
|
+
actor = self._users.get_by_auth_subject(actor_subject)
|
|
67
|
+
if actor is None or not bool(actor.roles & _ADMIN_ROLES):
|
|
68
|
+
raise ForbiddenError("accès refusé : droits insuffisants")
|
|
69
|
+
|
|
70
|
+
target = self._users.get_by_id(command.target_user_id)
|
|
71
|
+
if target is None:
|
|
72
|
+
raise UserNotFoundError(f"utilisateur {command.target_user_id!r} introuvable")
|
|
73
|
+
|
|
74
|
+
role_name = RoleName(value=command.role_name)
|
|
75
|
+
if self._roles.get_by_name(role_name) is None:
|
|
76
|
+
raise RoleNotFoundError(f"rôle {command.role_name!r} introuvable")
|
|
77
|
+
|
|
78
|
+
target.assign_role(role_name, now)
|
|
79
|
+
|
|
80
|
+
with self._uow:
|
|
81
|
+
self._users.save(target)
|
|
82
|
+
self._audit.save(
|
|
83
|
+
AuditEvent(
|
|
84
|
+
id=self._id_gen.generate(),
|
|
85
|
+
actor_subject=actor_subject,
|
|
86
|
+
event_type=AuditEventType.ROLE_ASSIGNED,
|
|
87
|
+
target_type="user",
|
|
88
|
+
target_id=target.id,
|
|
89
|
+
ip_address=command.ip_address,
|
|
90
|
+
user_agent=command.user_agent,
|
|
91
|
+
metadata={"role": command.role_name},
|
|
92
|
+
created_at=now,
|
|
93
|
+
severity="INFO",
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
self._uow.commit()
|