baobab-auth-core 0.1.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.
Files changed (71) hide show
  1. baobab_auth_core/__init__.py +13 -0
  2. baobab_auth_core/application/__init__.py +6 -0
  3. baobab_auth_core/application/commands/__init__.py +6 -0
  4. baobab_auth_core/application/results/__init__.py +6 -0
  5. baobab_auth_core/application/use_cases/__init__.py +6 -0
  6. baobab_auth_core/domain/__init__.py +6 -0
  7. baobab_auth_core/domain/entities/__init__.py +20 -0
  8. baobab_auth_core/domain/entities/audit_event.py +46 -0
  9. baobab_auth_core/domain/entities/permission.py +32 -0
  10. baobab_auth_core/domain/entities/role.py +33 -0
  11. baobab_auth_core/domain/entities/session.py +42 -0
  12. baobab_auth_core/domain/entities/user.py +157 -0
  13. baobab_auth_core/domain/entities/user_profile.py +34 -0
  14. baobab_auth_core/domain/enums/__init__.py +18 -0
  15. baobab_auth_core/domain/enums/audit_event_type.py +37 -0
  16. baobab_auth_core/domain/enums/audit_severity.py +19 -0
  17. baobab_auth_core/domain/enums/session_status.py +20 -0
  18. baobab_auth_core/domain/enums/user_status.py +23 -0
  19. baobab_auth_core/domain/policies/__init__.py +18 -0
  20. baobab_auth_core/domain/policies/password_policy.py +80 -0
  21. baobab_auth_core/domain/policies/role_policy.py +30 -0
  22. baobab_auth_core/domain/policies/session_policy.py +33 -0
  23. baobab_auth_core/domain/services/__init__.py +6 -0
  24. baobab_auth_core/domain/value_objects/__init__.py +44 -0
  25. baobab_auth_core/domain/value_objects/audit_event_id.py +43 -0
  26. baobab_auth_core/domain/value_objects/auth_subject.py +44 -0
  27. baobab_auth_core/domain/value_objects/email.py +46 -0
  28. baobab_auth_core/domain/value_objects/password_hash.py +44 -0
  29. baobab_auth_core/domain/value_objects/permission_id.py +43 -0
  30. baobab_auth_core/domain/value_objects/permission_name.py +49 -0
  31. baobab_auth_core/domain/value_objects/plain_password.py +44 -0
  32. baobab_auth_core/domain/value_objects/role_id.py +43 -0
  33. baobab_auth_core/domain/value_objects/role_name.py +49 -0
  34. baobab_auth_core/domain/value_objects/session_id.py +43 -0
  35. baobab_auth_core/domain/value_objects/token_id.py +43 -0
  36. baobab_auth_core/domain/value_objects/user_id.py +43 -0
  37. baobab_auth_core/exceptions/__init__.py +98 -0
  38. baobab_auth_core/exceptions/auth.py +29 -0
  39. baobab_auth_core/exceptions/authorization.py +27 -0
  40. baobab_auth_core/exceptions/base.py +22 -0
  41. baobab_auth_core/exceptions/role.py +29 -0
  42. baobab_auth_core/exceptions/session.py +27 -0
  43. baobab_auth_core/exceptions/user.py +41 -0
  44. baobab_auth_core/exceptions/validation.py +43 -0
  45. baobab_auth_core/ports/__init__.py +32 -0
  46. baobab_auth_core/ports/audit_repository.py +33 -0
  47. baobab_auth_core/ports/clock.py +22 -0
  48. baobab_auth_core/ports/id_generator.py +21 -0
  49. baobab_auth_core/ports/password_hasher.py +35 -0
  50. baobab_auth_core/ports/permission_repository.py +45 -0
  51. baobab_auth_core/ports/role_repository.py +45 -0
  52. baobab_auth_core/ports/session_repository.py +45 -0
  53. baobab_auth_core/ports/token_provider.py +49 -0
  54. baobab_auth_core/ports/unit_of_work.py +58 -0
  55. baobab_auth_core/ports/user_repository.py +65 -0
  56. baobab_auth_core/py.typed +0 -0
  57. baobab_auth_core/testing/__init__.py +46 -0
  58. baobab_auth_core/testing/fake_clock.py +45 -0
  59. baobab_auth_core/testing/fake_id_generator.py +36 -0
  60. baobab_auth_core/testing/fake_password_hasher.py +34 -0
  61. baobab_auth_core/testing/fake_token_provider.py +66 -0
  62. baobab_auth_core/testing/in_memory_audit_repository.py +42 -0
  63. baobab_auth_core/testing/in_memory_permission_repository.py +53 -0
  64. baobab_auth_core/testing/in_memory_role_repository.py +53 -0
  65. baobab_auth_core/testing/in_memory_session_repository.py +55 -0
  66. baobab_auth_core/testing/in_memory_unit_of_work.py +68 -0
  67. baobab_auth_core/testing/in_memory_user_repository.py +76 -0
  68. baobab_auth_core-0.1.0.dist-info/METADATA +206 -0
  69. baobab_auth_core-0.1.0.dist-info/RECORD +71 -0
  70. baobab_auth_core-0.1.0.dist-info/WHEEL +4 -0
  71. baobab_auth_core-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,13 @@
1
+ """baobab-auth-core — socle métier d'authentification et d'autorisation.
2
+
3
+ Version : 0.1.0
4
+
5
+ Ce package expose les entités, value objects, policies, ports et fakes
6
+ de test du domaine d'authentification. Il ne contient aucune dépendance
7
+ sur des technologies concrètes (ORM, framework web, JWT, Argon2, etc.).
8
+
9
+ :spec: BL-010-001
10
+ """
11
+
12
+ __version__ = "0.1.0"
13
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """Couche application de baobab-auth-core (réservé aux versions suivantes).
2
+
3
+ :spec: BL-010-001
4
+ """
5
+
6
+ __all__: list[str] = []
@@ -0,0 +1,6 @@
1
+ """Commandes de la couche application (réservé aux versions suivantes).
2
+
3
+ :spec: BL-010-001
4
+ """
5
+
6
+ __all__: list[str] = []
@@ -0,0 +1,6 @@
1
+ """Résultats de la couche application (réservé aux versions suivantes).
2
+
3
+ :spec: BL-010-001
4
+ """
5
+
6
+ __all__: list[str] = []
@@ -0,0 +1,6 @@
1
+ """Use cases de la couche application (réservé aux versions suivantes).
2
+
3
+ :spec: BL-010-001
4
+ """
5
+
6
+ __all__: list[str] = []
@@ -0,0 +1,6 @@
1
+ """Domaine métier de baobab-auth-core.
2
+
3
+ :spec: BL-010-001
4
+ """
5
+
6
+ __all__: list[str] = []
@@ -0,0 +1,20 @@
1
+ """Entités du domaine baobab-auth-core.
2
+
3
+ :spec: BL-010-003
4
+ """
5
+
6
+ from baobab_auth_core.domain.entities.audit_event import AuditEvent as AuditEvent
7
+ from baobab_auth_core.domain.entities.permission import Permission as Permission
8
+ from baobab_auth_core.domain.entities.role import Role as Role
9
+ from baobab_auth_core.domain.entities.session import Session as Session
10
+ from baobab_auth_core.domain.entities.user import User as User
11
+ from baobab_auth_core.domain.entities.user_profile import UserProfile as UserProfile
12
+
13
+ __all__ = [
14
+ "AuditEvent",
15
+ "Permission",
16
+ "Role",
17
+ "Session",
18
+ "User",
19
+ "UserProfile",
20
+ ]
@@ -0,0 +1,46 @@
1
+ """Entité AuditEvent — événement d'audit immuable.
2
+
3
+ :spec: BL-010-003
4
+ """
5
+
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass, field
8
+ from datetime import datetime
9
+ from typing import Any
10
+
11
+ from baobab_auth_core.domain.enums.audit_event_type import AuditEventType
12
+ from baobab_auth_core.domain.enums.audit_severity import AuditSeverity
13
+ from baobab_auth_core.domain.value_objects.audit_event_id import AuditEventId
14
+ from baobab_auth_core.domain.value_objects.auth_subject import AuthSubject
15
+
16
+
17
+ @dataclass(frozen=True)
18
+ class AuditEvent:
19
+ """Événement d'audit immuable tracé dans le journal de sécurité.
20
+
21
+ Cette entité est frozen (immuable) car les événements d'audit
22
+ ne doivent jamais être modifiés après enregistrement.
23
+
24
+ :param id: Identifiant unique de l'événement.
25
+ :param event_type: Type de l'événement.
26
+ :param severity: Niveau de sévérité.
27
+ :param created_at: Horodatage de l'événement (UTC).
28
+ :param actor_subject: Sujet initiateur de l'action
29
+ (ou None pour des actions système).
30
+ :param target_type: Type de la ressource ciblée (ex. ``user``, ``session``).
31
+ :param target_id: Identifiant de la ressource ciblée.
32
+ :param ip_address: Adresse IP de l'acteur.
33
+ :param user_agent: User-Agent HTTP de l'acteur.
34
+ :param metadata: Métadonnées additionnelles contextuelles.
35
+ """
36
+
37
+ id: AuditEventId
38
+ event_type: AuditEventType
39
+ severity: AuditSeverity
40
+ created_at: datetime
41
+ actor_subject: AuthSubject | None = field(default=None)
42
+ target_type: str | None = field(default=None)
43
+ target_id: str | None = field(default=None)
44
+ ip_address: str | None = field(default=None)
45
+ user_agent: str | None = field(default=None)
46
+ metadata: Mapping[str, Any] = field(default_factory=dict)
@@ -0,0 +1,32 @@
1
+ """Entité Permission — permission atomique du système d'autorisation.
2
+
3
+ :spec: BL-010-003
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime
8
+
9
+ from baobab_auth_core.domain.value_objects.permission_id import PermissionId
10
+ from baobab_auth_core.domain.value_objects.permission_name import PermissionName
11
+
12
+
13
+ @dataclass
14
+ class Permission:
15
+ """Permission atomique du système d'autorisation.
16
+
17
+ :param id: Identifiant unique de la permission.
18
+ :param name: Nom unique au format ``scope:resource:action``.
19
+ :param resource: Ressource ciblée.
20
+ :param action: Action autorisée sur la ressource.
21
+ :param is_system: Si ``True``, permission système non supprimable.
22
+ :param created_at: Date de création (UTC).
23
+ :param description: Description optionnelle.
24
+ """
25
+
26
+ id: PermissionId
27
+ name: PermissionName
28
+ resource: str
29
+ action: str
30
+ is_system: bool
31
+ created_at: datetime
32
+ description: str | None = field(default=None)
@@ -0,0 +1,33 @@
1
+ """Entité Role — rôle du système d'autorisation.
2
+
3
+ :spec: BL-010-003
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime
8
+
9
+ from baobab_auth_core.domain.value_objects.permission_name import PermissionName
10
+ from baobab_auth_core.domain.value_objects.role_id import RoleId
11
+ from baobab_auth_core.domain.value_objects.role_name import RoleName
12
+
13
+
14
+ @dataclass
15
+ class Role:
16
+ """Rôle du système d'autorisation regroupant un ensemble de permissions.
17
+
18
+ :param id: Identifiant unique du rôle.
19
+ :param name: Nom unique du rôle.
20
+ :param is_system: Si ``True``, rôle système non supprimable.
21
+ :param created_at: Date de création (UTC).
22
+ :param updated_at: Date de dernière mise à jour (UTC).
23
+ :param description: Description optionnelle.
24
+ :param permission_names: Permissions associées (sans doublon).
25
+ """
26
+
27
+ id: RoleId
28
+ name: RoleName
29
+ is_system: bool
30
+ created_at: datetime
31
+ updated_at: datetime
32
+ description: str | None = field(default=None)
33
+ permission_names: tuple[PermissionName, ...] = field(default_factory=tuple)
@@ -0,0 +1,42 @@
1
+ """Entité Session — session utilisateur active.
2
+
3
+ :spec: BL-010-003
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime
8
+
9
+ from baobab_auth_core.domain.enums.session_status import SessionStatus
10
+ from baobab_auth_core.domain.value_objects.session_id import SessionId
11
+ from baobab_auth_core.domain.value_objects.token_id import TokenId
12
+ from baobab_auth_core.domain.value_objects.user_id import UserId
13
+
14
+
15
+ @dataclass
16
+ class Session:
17
+ """Session utilisateur associant un token de rafraîchissement à un utilisateur.
18
+
19
+ :param id: Identifiant unique de la session.
20
+ :param user_id: Identifiant de l'utilisateur propriétaire.
21
+ :param refresh_token_id: Identifiant du token de rafraîchissement.
22
+ :param status: Statut courant de la session.
23
+ :param created_at: Date de création (UTC).
24
+ :param expires_at: Date d'expiration (UTC).
25
+ :param revoked_at: Date de révocation (UTC ou None).
26
+ :param last_used_at: Date de dernière utilisation (UTC ou None).
27
+ :param user_agent: User-Agent HTTP du client (ou None).
28
+ :param ip_address: Adresse IP du client (ou None).
29
+ :param device_label: Libellé lisible du device (ou None).
30
+ """
31
+
32
+ id: SessionId
33
+ user_id: UserId
34
+ refresh_token_id: TokenId
35
+ status: SessionStatus
36
+ created_at: datetime
37
+ expires_at: datetime
38
+ revoked_at: datetime | None = field(default=None)
39
+ last_used_at: datetime | None = field(default=None)
40
+ user_agent: str | None = field(default=None)
41
+ ip_address: str | None = field(default=None)
42
+ device_label: str | None = field(default=None)
@@ -0,0 +1,157 @@
1
+ """Entité User — compte utilisateur du domaine.
2
+
3
+ :spec: BL-010-003
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from datetime import datetime
8
+
9
+ from baobab_auth_core.domain.enums.user_status import UserStatus
10
+ from baobab_auth_core.domain.value_objects.auth_subject import AuthSubject
11
+ from baobab_auth_core.domain.value_objects.email import Email
12
+ from baobab_auth_core.domain.value_objects.password_hash import PasswordHash
13
+ from baobab_auth_core.domain.value_objects.role_name import RoleName
14
+ from baobab_auth_core.domain.value_objects.user_id import UserId
15
+ from baobab_auth_core.exceptions.validation import ValidationError
16
+
17
+
18
+ @dataclass
19
+ class User:
20
+ """Compte utilisateur — agrégat racine du domaine d'authentification.
21
+
22
+ Invariants :
23
+ - ``email`` obligatoire et normalisé ;
24
+ - ``auth_subject`` obligatoire et stable ;
25
+ - ``password_hash`` jamais vide ;
26
+ - ``failed_login_count >= 0`` ;
27
+ - dates UTC aware ;
28
+ - rôles sans doublon.
29
+
30
+ :param id: Identifiant unique de l'utilisateur.
31
+ :param auth_subject: Identifiant stable du sujet d'authentification.
32
+ :param email: Adresse email normalisée.
33
+ :param password_hash: Hash du mot de passe.
34
+ :param status: Statut actuel du compte.
35
+ :param role_names: Rôles assignés (sans doublon).
36
+ :param created_at: Date de création (UTC).
37
+ :param updated_at: Date de dernière mise à jour (UTC).
38
+ :param last_login_at: Date de dernière connexion réussie (UTC ou None).
39
+ :param failed_login_count: Nombre de tentatives de connexion échouées consécutives.
40
+ :param locked_until: Date de fin de verrouillage (UTC ou None).
41
+ """
42
+
43
+ id: UserId
44
+ auth_subject: AuthSubject
45
+ email: Email
46
+ password_hash: PasswordHash
47
+ status: UserStatus
48
+ role_names: tuple[RoleName, ...]
49
+ created_at: datetime
50
+ updated_at: datetime
51
+ last_login_at: datetime | None = field(default=None)
52
+ failed_login_count: int = field(default=0)
53
+ locked_until: datetime | None = field(default=None)
54
+
55
+ def __post_init__(self) -> None:
56
+ """Valide les invariants à la construction.
57
+
58
+ :raises ValidationError: Si ``failed_login_count`` est négatif ou si
59
+ ``role_names`` contient des doublons.
60
+ """
61
+ if self.failed_login_count < 0:
62
+ raise ValidationError(
63
+ "failed_login_count doit être supérieur ou égal à zéro."
64
+ )
65
+ unique = list(dict.fromkeys(self.role_names))
66
+ if len(unique) != len(self.role_names):
67
+ raise ValidationError("role_names ne peut pas contenir de doublons.")
68
+
69
+ def activate(self, now: datetime) -> None:
70
+ """Active le compte utilisateur.
71
+
72
+ :param now: Horodatage courant (UTC).
73
+ """
74
+ self.status = UserStatus.ACTIVE
75
+ self.updated_at = now
76
+
77
+ def disable(self, now: datetime) -> None:
78
+ """Désactive le compte utilisateur.
79
+
80
+ :param now: Horodatage courant (UTC).
81
+ """
82
+ self.status = UserStatus.DISABLED
83
+ self.updated_at = now
84
+
85
+ def lock(self, until: datetime, now: datetime) -> None:
86
+ """Verrouille le compte jusqu'à une date donnée.
87
+
88
+ :param until: Date de fin de verrouillage (UTC).
89
+ :param now: Horodatage courant (UTC).
90
+ """
91
+ self.status = UserStatus.LOCKED
92
+ self.locked_until = until
93
+ self.updated_at = now
94
+
95
+ def unlock(self, now: datetime) -> None:
96
+ """Déverrouille le compte et remet le compteur d'échecs à zéro.
97
+
98
+ :param now: Horodatage courant (UTC).
99
+ """
100
+ self.status = UserStatus.ACTIVE
101
+ self.locked_until = None
102
+ self.failed_login_count = 0
103
+ self.updated_at = now
104
+
105
+ def mark_login_success(self, now: datetime) -> None:
106
+ """Enregistre une connexion réussie et réinitialise le compteur d'échecs.
107
+
108
+ :param now: Horodatage courant (UTC).
109
+ """
110
+ self.last_login_at = now
111
+ self.failed_login_count = 0
112
+ self.updated_at = now
113
+
114
+ def mark_login_failure(self, now: datetime) -> None:
115
+ """Incrémente le compteur de tentatives de connexion échouées.
116
+
117
+ :param now: Horodatage courant (UTC).
118
+ """
119
+ self.failed_login_count += 1
120
+ self.updated_at = now
121
+
122
+ def change_password_hash(self, password_hash: PasswordHash, now: datetime) -> None:
123
+ """Remplace le hash de mot de passe.
124
+
125
+ :param password_hash: Nouveau hash.
126
+ :param now: Horodatage courant (UTC).
127
+ """
128
+ self.password_hash = password_hash
129
+ self.updated_at = now
130
+
131
+ def assign_role(self, role_name: RoleName, now: datetime) -> None:
132
+ """Assigne un rôle si non déjà présent.
133
+
134
+ :param role_name: Nom du rôle à assigner.
135
+ :param now: Horodatage courant (UTC).
136
+ """
137
+ if role_name not in self.role_names:
138
+ self.role_names = (*self.role_names, role_name)
139
+ self.updated_at = now
140
+
141
+ def remove_role(self, role_name: RoleName, now: datetime) -> None:
142
+ """Retire un rôle de l'utilisateur.
143
+
144
+ :param role_name: Nom du rôle à retirer.
145
+ :param now: Horodatage courant (UTC).
146
+ """
147
+ if role_name in self.role_names:
148
+ self.role_names = tuple(r for r in self.role_names if r != role_name)
149
+ self.updated_at = now
150
+
151
+ def has_role(self, role_name: RoleName) -> bool:
152
+ """Vérifie si l'utilisateur possède un rôle donné.
153
+
154
+ :param role_name: Nom du rôle à vérifier.
155
+ :returns: ``True`` si le rôle est présent.
156
+ """
157
+ return role_name in self.role_names
@@ -0,0 +1,34 @@
1
+ """Entité UserProfile — profil public d'un utilisateur.
2
+
3
+ :spec: BL-010-003
4
+ """
5
+
6
+ from dataclasses import dataclass
7
+ from datetime import datetime
8
+
9
+ from baobab_auth_core.domain.value_objects.user_id import UserId
10
+
11
+
12
+ @dataclass
13
+ class UserProfile:
14
+ """Profil public d'un utilisateur, sans aucun secret.
15
+
16
+ Cette entité ne contient aucune information d'authentification
17
+ (pas de mot de passe, hash, token ou secret).
18
+
19
+ :param user_id: Identifiant de l'utilisateur associé.
20
+ :param display_name: Nom d'affichage public (ou None).
21
+ :param locale: Code de locale BCP-47 (ex. ``fr-FR``) ou None.
22
+ :param timezone: Fuseau horaire IANA (ex. ``Europe/Paris``) ou None.
23
+ :param avatar_url: URL publique de l'avatar (ou None).
24
+ :param created_at: Date de création (UTC).
25
+ :param updated_at: Date de dernière mise à jour (UTC).
26
+ """
27
+
28
+ user_id: UserId
29
+ created_at: datetime
30
+ updated_at: datetime
31
+ display_name: str | None = None
32
+ locale: str | None = None
33
+ timezone: str | None = None
34
+ avatar_url: str | None = None
@@ -0,0 +1,18 @@
1
+ """Énumérations du domaine baobab-auth-core.
2
+
3
+ :spec: BL-010-004
4
+ """
5
+
6
+ from baobab_auth_core.domain.enums.audit_event_type import (
7
+ AuditEventType as AuditEventType,
8
+ )
9
+ from baobab_auth_core.domain.enums.audit_severity import AuditSeverity as AuditSeverity
10
+ from baobab_auth_core.domain.enums.session_status import SessionStatus as SessionStatus
11
+ from baobab_auth_core.domain.enums.user_status import UserStatus as UserStatus
12
+
13
+ __all__ = [
14
+ "AuditEventType",
15
+ "AuditSeverity",
16
+ "SessionStatus",
17
+ "UserStatus",
18
+ ]
@@ -0,0 +1,37 @@
1
+ """Types d'événements d'audit.
2
+
3
+ :spec: BL-010-004
4
+ """
5
+
6
+ from enum import StrEnum
7
+
8
+
9
+ class AuditEventType(StrEnum):
10
+ """Catalogue des types d'événements traçables.
11
+
12
+ :cvar USER_REGISTERED: Nouvel utilisateur enregistré.
13
+ :cvar LOGIN_SUCCESS: Connexion réussie.
14
+ :cvar LOGIN_FAILURE: Échec de connexion (identifiants invalides).
15
+ :cvar LOGOUT: Déconnexion explicite.
16
+ :cvar SESSION_REFRESHED: Token de session renouvelé.
17
+ :cvar SESSION_REVOKED: Session révoquée.
18
+ :cvar ROLE_ASSIGNED: Rôle attribué à un utilisateur.
19
+ :cvar ROLE_REMOVED: Rôle retiré d'un utilisateur.
20
+ :cvar PASSWORD_CHANGED: Mot de passe modifié.
21
+ :cvar ACCOUNT_LOCKED: Compte verrouillé suite à trop d'échecs.
22
+ :cvar ACCOUNT_DISABLED: Compte désactivé par un administrateur.
23
+ :cvar ACCOUNT_DELETED: Compte supprimé.
24
+ """
25
+
26
+ USER_REGISTERED = "USER_REGISTERED"
27
+ LOGIN_SUCCESS = "LOGIN_SUCCESS"
28
+ LOGIN_FAILURE = "LOGIN_FAILURE"
29
+ LOGOUT = "LOGOUT"
30
+ SESSION_REFRESHED = "SESSION_REFRESHED"
31
+ SESSION_REVOKED = "SESSION_REVOKED"
32
+ ROLE_ASSIGNED = "ROLE_ASSIGNED"
33
+ ROLE_REMOVED = "ROLE_REMOVED"
34
+ PASSWORD_CHANGED = "PASSWORD_CHANGED" # nosec B105
35
+ ACCOUNT_LOCKED = "ACCOUNT_LOCKED"
36
+ ACCOUNT_DISABLED = "ACCOUNT_DISABLED"
37
+ ACCOUNT_DELETED = "ACCOUNT_DELETED"
@@ -0,0 +1,19 @@
1
+ """Niveau de sévérité d'un événement d'audit.
2
+
3
+ :spec: BL-010-004
4
+ """
5
+
6
+ from enum import StrEnum
7
+
8
+
9
+ class AuditSeverity(StrEnum):
10
+ """Sévérité d'un événement d'audit.
11
+
12
+ :cvar INFO: Événement informatif, opération normale.
13
+ :cvar WARNING: Situation anormale mais non critique.
14
+ :cvar CRITICAL: Incident de sécurité ou erreur grave.
15
+ """
16
+
17
+ INFO = "INFO"
18
+ WARNING = "WARNING"
19
+ CRITICAL = "CRITICAL"
@@ -0,0 +1,20 @@
1
+ """Statut d'une session utilisateur.
2
+
3
+ :spec: BL-010-004
4
+ """
5
+
6
+ from enum import StrEnum
7
+
8
+
9
+ class SessionStatus(StrEnum):
10
+ """Cycle de vie d'une session.
11
+
12
+ :cvar ACTIVE: Session active et valide.
13
+ :cvar REVOKED: Session révoquée explicitement
14
+ (déconnexion, changement de mot de passe).
15
+ :cvar EXPIRED: Session expirée (TTL dépassé).
16
+ """
17
+
18
+ ACTIVE = "ACTIVE"
19
+ REVOKED = "REVOKED"
20
+ EXPIRED = "EXPIRED"
@@ -0,0 +1,23 @@
1
+ """Statut d'un compte utilisateur.
2
+
3
+ :spec: BL-010-004
4
+ """
5
+
6
+ from enum import StrEnum
7
+
8
+
9
+ class UserStatus(StrEnum):
10
+ """Cycle de vie d'un compte utilisateur.
11
+
12
+ :cvar PENDING: Compte créé, en attente de validation.
13
+ :cvar ACTIVE: Compte actif, l'utilisateur peut se connecter.
14
+ :cvar LOCKED: Compte temporairement verrouillé (trop d'échecs de connexion).
15
+ :cvar DISABLED: Compte désactivé par un administrateur.
16
+ :cvar DELETED: Compte supprimé (soft-delete).
17
+ """
18
+
19
+ PENDING = "PENDING"
20
+ ACTIVE = "ACTIVE"
21
+ LOCKED = "LOCKED"
22
+ DISABLED = "DISABLED"
23
+ DELETED = "DELETED"
@@ -0,0 +1,18 @@
1
+ """Politiques du domaine baobab-auth-core.
2
+
3
+ :spec: BL-010-004
4
+ """
5
+
6
+ from baobab_auth_core.domain.policies.password_policy import (
7
+ PasswordPolicy as PasswordPolicy,
8
+ )
9
+ from baobab_auth_core.domain.policies.role_policy import RolePolicy as RolePolicy
10
+ from baobab_auth_core.domain.policies.session_policy import (
11
+ SessionPolicy as SessionPolicy,
12
+ )
13
+
14
+ __all__ = [
15
+ "PasswordPolicy",
16
+ "RolePolicy",
17
+ "SessionPolicy",
18
+ ]
@@ -0,0 +1,80 @@
1
+ """Politique de mot de passe.
2
+
3
+ :spec: BL-010-004
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+
8
+ from baobab_auth_core.domain.value_objects.email import Email
9
+ from baobab_auth_core.domain.value_objects.plain_password import PlainPassword
10
+ from baobab_auth_core.exceptions.validation import WeakPasswordError
11
+
12
+ _DEFAULT_MIN_LENGTH = 12
13
+ _DEFAULT_MAX_LENGTH = 256
14
+
15
+
16
+ @dataclass(frozen=True)
17
+ class PasswordPolicy:
18
+ """Politique de validation des mots de passe.
19
+
20
+ Les valeurs par défaut correspondent au niveau de sécurité minimal
21
+ recommandé par OWASP.
22
+
23
+ :param min_length: Longueur minimale (défaut : 12).
24
+ :param max_length: Longueur maximale (défaut : 256).
25
+ :param require_letter: Exige au moins une lettre (défaut : True).
26
+ :param require_digit_or_symbol: Exige au moins un chiffre ou symbole
27
+ (défaut : True).
28
+ :param forbid_email_as_password: Interdit l'email comme mot de passe
29
+ (défaut : True).
30
+ """
31
+
32
+ min_length: int = field(default=_DEFAULT_MIN_LENGTH)
33
+ max_length: int = field(default=_DEFAULT_MAX_LENGTH)
34
+ require_letter: bool = field(default=True)
35
+ require_digit_or_symbol: bool = field(default=True)
36
+ forbid_email_as_password: bool = field(default=True)
37
+
38
+ def validate(
39
+ self,
40
+ password: PlainPassword,
41
+ email: Email | None = None,
42
+ ) -> None:
43
+ """Valide un mot de passe selon la politique.
44
+
45
+ :param password: Mot de passe en clair à valider.
46
+ :param email: Adresse email de l'utilisateur (pour la règle d'exclusion).
47
+ :raises WeakPasswordError: Si le mot de passe ne respecte pas la politique.
48
+ """
49
+ value = password.value
50
+
51
+ if len(value) < self.min_length:
52
+ raise WeakPasswordError(
53
+ f"Le mot de passe doit contenir au moins {self.min_length} caractères."
54
+ )
55
+
56
+ if len(value) > self.max_length:
57
+ raise WeakPasswordError(
58
+ f"Le mot de passe ne peut pas dépasser {self.max_length} caractères."
59
+ )
60
+
61
+ if self.require_letter and not any(c.isalpha() for c in value):
62
+ raise WeakPasswordError(
63
+ "Le mot de passe doit contenir au moins une lettre."
64
+ )
65
+
66
+ if self.require_digit_or_symbol and not any(
67
+ c.isdigit() or not c.isalnum() for c in value
68
+ ):
69
+ raise WeakPasswordError(
70
+ "Le mot de passe doit contenir au moins un chiffre ou un symbole."
71
+ )
72
+
73
+ if (
74
+ self.forbid_email_as_password
75
+ and email is not None
76
+ and value.strip().lower() == email.value
77
+ ):
78
+ raise WeakPasswordError(
79
+ "Le mot de passe ne peut pas être identique à l'adresse email."
80
+ )
@@ -0,0 +1,30 @@
1
+ """Politique de gestion des rôles.
2
+
3
+ :spec: BL-010-004
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+
8
+ from baobab_auth_core.domain.value_objects.role_name import RoleName
9
+
10
+ _DEFAULT_ROLE = "USER"
11
+ _SUPER_ADMIN_ROLE = "SUPER_ADMIN"
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class RolePolicy:
16
+ """Politique de gestion des rôles et de la hiérarchie.
17
+
18
+ :param default_role_name: Rôle attribué automatiquement à tout nouvel utilisateur
19
+ (défaut : ``USER``).
20
+ :param super_admin_role_name: Nom du rôle super-administrateur
21
+ (défaut : ``SUPER_ADMIN``).
22
+ :param enforce_last_super_admin: Interdit la suppression du dernier super-admin
23
+ (défaut : True).
24
+ """
25
+
26
+ default_role_name: RoleName = field(default_factory=lambda: RoleName(_DEFAULT_ROLE))
27
+ super_admin_role_name: RoleName = field(
28
+ default_factory=lambda: RoleName(_SUPER_ADMIN_ROLE)
29
+ )
30
+ enforce_last_super_admin: bool = field(default=True)