oxutils 0.1.8__py3-none-any.whl → 0.1.10__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.
- oxutils/__init__.py +2 -1
- oxutils/constants.py +4 -0
- oxutils/jwt/auth.py +150 -1
- oxutils/jwt/models.py +13 -0
- oxutils/logger/receivers.py +10 -6
- oxutils/models/base.py +102 -0
- oxutils/models/fields.py +79 -0
- oxutils/oxiliere/apps.py +6 -1
- oxutils/oxiliere/authorization.py +45 -0
- oxutils/oxiliere/caches.py +8 -7
- oxutils/oxiliere/checks.py +31 -0
- oxutils/oxiliere/constants.py +3 -0
- oxutils/oxiliere/context.py +18 -0
- oxutils/oxiliere/exceptions.py +16 -0
- oxutils/oxiliere/management/commands/grant_tenant_owners.py +19 -0
- oxutils/oxiliere/management/commands/init_oxiliere_system.py +20 -8
- oxutils/oxiliere/middleware.py +29 -13
- oxutils/oxiliere/models.py +130 -19
- oxutils/oxiliere/permissions.py +6 -5
- oxutils/oxiliere/schemas.py +13 -4
- oxutils/oxiliere/signals.py +5 -0
- oxutils/oxiliere/utils.py +18 -0
- oxutils/pagination/__init__.py +0 -0
- oxutils/pagination/cursor.py +367 -0
- oxutils/permissions/__init__.py +0 -0
- oxutils/permissions/actions.py +57 -0
- oxutils/permissions/admin.py +3 -0
- oxutils/permissions/apps.py +10 -0
- oxutils/permissions/caches.py +19 -0
- oxutils/permissions/checks.py +188 -0
- oxutils/permissions/constants.py +0 -0
- oxutils/permissions/controllers.py +344 -0
- oxutils/permissions/exceptions.py +60 -0
- oxutils/permissions/management/__init__.py +0 -0
- oxutils/permissions/management/commands/__init__.py +0 -0
- oxutils/permissions/management/commands/load_permission_preset.py +112 -0
- oxutils/permissions/migrations/0001_initial.py +112 -0
- oxutils/permissions/migrations/0002_alter_grant_role.py +19 -0
- oxutils/permissions/migrations/0003_alter_grant_options_alter_group_options_and_more.py +33 -0
- oxutils/permissions/migrations/__init__.py +0 -0
- oxutils/permissions/models.py +171 -0
- oxutils/permissions/perms.py +95 -0
- oxutils/permissions/queryset.py +92 -0
- oxutils/permissions/schemas.py +276 -0
- oxutils/permissions/services.py +663 -0
- oxutils/permissions/tests.py +3 -0
- oxutils/permissions/utils.py +628 -0
- oxutils/settings.py +1 -0
- oxutils/users/migrations/0002_alter_user_first_name_alter_user_last_name.py +23 -0
- oxutils/users/models.py +2 -0
- {oxutils-0.1.8.dist-info → oxutils-0.1.10.dist-info}/METADATA +1 -1
- {oxutils-0.1.8.dist-info → oxutils-0.1.10.dist-info}/RECORD +53 -19
- {oxutils-0.1.8.dist-info → oxutils-0.1.10.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from django.db import models
|
|
3
|
+
from django.contrib.postgres.fields import ArrayField
|
|
4
|
+
from django.contrib.postgres.indexes import GinIndex
|
|
5
|
+
from oxutils.models import TimestampMixin
|
|
6
|
+
from .actions import expand_actions
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Role(TimestampMixin):
|
|
12
|
+
"""
|
|
13
|
+
A role.
|
|
14
|
+
"""
|
|
15
|
+
slug = models.SlugField(unique=True, primary_key=True)
|
|
16
|
+
name = models.CharField(max_length=100)
|
|
17
|
+
|
|
18
|
+
def __str__(self):
|
|
19
|
+
return self.slug
|
|
20
|
+
|
|
21
|
+
class Meta:
|
|
22
|
+
indexes = [
|
|
23
|
+
models.Index(fields=["slug"]),
|
|
24
|
+
]
|
|
25
|
+
ordering = ["slug"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Group(TimestampMixin):
|
|
29
|
+
"""
|
|
30
|
+
A group of roles. for UI Template purposes.
|
|
31
|
+
"""
|
|
32
|
+
slug = models.SlugField(unique=True, primary_key=True)
|
|
33
|
+
name = models.CharField(max_length=100)
|
|
34
|
+
roles = models.ManyToManyField(Role, related_name="groups")
|
|
35
|
+
|
|
36
|
+
def __str__(self):
|
|
37
|
+
return self.slug
|
|
38
|
+
|
|
39
|
+
class Meta:
|
|
40
|
+
indexes = [
|
|
41
|
+
models.Index(fields=["slug"]),
|
|
42
|
+
]
|
|
43
|
+
ordering = ["slug"]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class UserGroup(TimestampMixin):
|
|
47
|
+
"""
|
|
48
|
+
A user group that links users to groups.
|
|
49
|
+
"""
|
|
50
|
+
user = models.ForeignKey(
|
|
51
|
+
settings.AUTH_USER_MODEL,
|
|
52
|
+
on_delete=models.CASCADE,
|
|
53
|
+
related_name="user_groups",
|
|
54
|
+
)
|
|
55
|
+
group = models.ForeignKey(
|
|
56
|
+
Group,
|
|
57
|
+
on_delete=models.CASCADE,
|
|
58
|
+
related_name="user_groups",
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
class Meta:
|
|
62
|
+
constraints = [
|
|
63
|
+
models.UniqueConstraint(fields=['user', 'group'], name='unique_user_group')
|
|
64
|
+
]
|
|
65
|
+
indexes = [
|
|
66
|
+
models.Index(fields=['user', 'group']),
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class RoleGrant(models.Model):
|
|
71
|
+
"""
|
|
72
|
+
A grant template of permissions to a role.
|
|
73
|
+
Peut être lié à un groupe spécifique pour des comportements distincts.
|
|
74
|
+
"""
|
|
75
|
+
role = models.ForeignKey(Role, on_delete=models.CASCADE, related_name="grants")
|
|
76
|
+
group = models.ForeignKey(
|
|
77
|
+
Group,
|
|
78
|
+
null=True,
|
|
79
|
+
blank=True,
|
|
80
|
+
on_delete=models.CASCADE,
|
|
81
|
+
related_name="role_grants",
|
|
82
|
+
help_text="Groupe optionnel pour des comportements spécifiques"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
scope = models.CharField(max_length=100)
|
|
86
|
+
actions = ArrayField(models.CharField(max_length=5))
|
|
87
|
+
context = models.JSONField(default=dict, blank=True)
|
|
88
|
+
|
|
89
|
+
def clean(self):
|
|
90
|
+
self.actions = expand_actions(self.actions)
|
|
91
|
+
|
|
92
|
+
class Meta:
|
|
93
|
+
constraints = [
|
|
94
|
+
models.UniqueConstraint(
|
|
95
|
+
fields=["role", "scope", "group"], name="unique_role_scope_group"
|
|
96
|
+
)
|
|
97
|
+
]
|
|
98
|
+
indexes = [
|
|
99
|
+
models.Index(fields=["role"]),
|
|
100
|
+
models.Index(fields=["group"]),
|
|
101
|
+
models.Index(fields=["role", "group"]),
|
|
102
|
+
]
|
|
103
|
+
ordering = ["role__slug", "group__slug"]
|
|
104
|
+
|
|
105
|
+
def __str__(self):
|
|
106
|
+
group_str = f"[{self.group.slug}]" if self.group else ""
|
|
107
|
+
return f"{self.role}:{self.scope}{group_str}:{self.actions}"
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def save(self, *args, **kwargs):
|
|
111
|
+
self.clean()
|
|
112
|
+
super().save(*args, **kwargs)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
class Grant(TimestampMixin):
|
|
116
|
+
"""
|
|
117
|
+
A grant of permissions to a user.
|
|
118
|
+
"""
|
|
119
|
+
user = models.ForeignKey(
|
|
120
|
+
settings.AUTH_USER_MODEL,
|
|
121
|
+
on_delete=models.CASCADE,
|
|
122
|
+
related_name="grants",
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# traçabilité
|
|
126
|
+
role = models.ForeignKey(
|
|
127
|
+
Role,
|
|
128
|
+
null=True,
|
|
129
|
+
blank=True,
|
|
130
|
+
related_name="user_grants",
|
|
131
|
+
on_delete=models.SET_NULL,
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Lien avec UserGroup pour tracer l'origine du grant
|
|
135
|
+
user_group = models.ForeignKey(
|
|
136
|
+
'UserGroup',
|
|
137
|
+
null=True,
|
|
138
|
+
blank=True,
|
|
139
|
+
related_name="grants",
|
|
140
|
+
on_delete=models.SET_NULL,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
created_by = models.ForeignKey(
|
|
144
|
+
settings.AUTH_USER_MODEL,
|
|
145
|
+
null=True,
|
|
146
|
+
blank=True,
|
|
147
|
+
related_name="created_grants",
|
|
148
|
+
on_delete=models.SET_NULL,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
scope = models.CharField(max_length=100)
|
|
152
|
+
actions = ArrayField(models.CharField(max_length=5))
|
|
153
|
+
context = models.JSONField(default=dict, blank=True)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class Meta:
|
|
157
|
+
constraints = [
|
|
158
|
+
models.UniqueConstraint(
|
|
159
|
+
fields=["user", "scope", "role", "user_group"], name="unique_user_scope_role"
|
|
160
|
+
)
|
|
161
|
+
]
|
|
162
|
+
indexes = [
|
|
163
|
+
models.Index(fields=["user", "scope"]),
|
|
164
|
+
models.Index(fields=["user_group"]),
|
|
165
|
+
GinIndex(fields=["actions"]),
|
|
166
|
+
GinIndex(fields=["context"]),
|
|
167
|
+
]
|
|
168
|
+
ordering = ["scope"]
|
|
169
|
+
|
|
170
|
+
def __str__(self):
|
|
171
|
+
return f"{self.user} {self.scope} {self.actions}"
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from typing import Optional
|
|
2
|
+
from django.conf import settings
|
|
3
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
4
|
+
from django.http import HttpRequest
|
|
5
|
+
from ninja_extra.permissions import BasePermission
|
|
6
|
+
from ninja_extra.controllers import ControllerBase
|
|
7
|
+
|
|
8
|
+
from oxutils.permissions.utils import str_check
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ScopePermission(BasePermission):
|
|
13
|
+
"""
|
|
14
|
+
Permission class for checking user permissions using the string format.
|
|
15
|
+
|
|
16
|
+
Format: "<scope>:<actions>:<group>?key=value"
|
|
17
|
+
|
|
18
|
+
Example:
|
|
19
|
+
@api_controller('/articles', permissions=[ScopePermission('articles:w:staff')])
|
|
20
|
+
class ArticleController:
|
|
21
|
+
pass
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, perm: str, ctx: Optional[dict] = None):
|
|
25
|
+
"""
|
|
26
|
+
Initialize the permission checker.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
perm: Permission string in format "<scope>:<actions>:<group>?context"
|
|
30
|
+
"""
|
|
31
|
+
self.perm = perm
|
|
32
|
+
self.ctx = ctx if ctx else dict()
|
|
33
|
+
|
|
34
|
+
def has_permission(self, request: HttpRequest, controller: ControllerBase) -> bool:
|
|
35
|
+
"""
|
|
36
|
+
Check if the user has the required permission.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
request: HTTP request object
|
|
40
|
+
controller: Controller instance
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
True if user has permission, False otherwise
|
|
44
|
+
"""
|
|
45
|
+
return str_check(request.user, self.perm, **self.ctx)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def access_manager(actions: str):
|
|
49
|
+
"""
|
|
50
|
+
Factory function for creating ScopePermission instances for access manager.
|
|
51
|
+
|
|
52
|
+
Builds a permission string from settings:
|
|
53
|
+
- ACCESS_MANAGER_SCOPE: The scope to check
|
|
54
|
+
- ACCESS_MANAGER_GROUP: Optional group filter
|
|
55
|
+
- ACCESS_MANAGER_CONTEXT: Optional context dict converted to query params
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
actions: Actions required (e.g., 'r', 'rw', 'rwd')
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
ScopePermission instance configured with access manager settings
|
|
62
|
+
|
|
63
|
+
Raises:
|
|
64
|
+
ImproperlyConfigured: If required settings are missing
|
|
65
|
+
|
|
66
|
+
Example:
|
|
67
|
+
@api_controller('/access', permissions=[access_manager('w')])
|
|
68
|
+
class AccessController:
|
|
69
|
+
pass
|
|
70
|
+
"""
|
|
71
|
+
# Validate required settings
|
|
72
|
+
if not hasattr(settings, 'ACCESS_MANAGER_SCOPE'):
|
|
73
|
+
raise ImproperlyConfigured(
|
|
74
|
+
'ACCESS_MANAGER_SCOPE is not defined. '
|
|
75
|
+
'Add ACCESS_MANAGER_SCOPE = "access" to your settings.'
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Build base permission string: scope:actions
|
|
79
|
+
perm = f"{settings.ACCESS_MANAGER_SCOPE}:{actions}"
|
|
80
|
+
|
|
81
|
+
# Add group if defined and not None
|
|
82
|
+
if hasattr(settings, 'ACCESS_MANAGER_GROUP') and settings.ACCESS_MANAGER_GROUP is not None:
|
|
83
|
+
perm += f":{settings.ACCESS_MANAGER_GROUP}"
|
|
84
|
+
|
|
85
|
+
# Get context if defined and not empty
|
|
86
|
+
context = {}
|
|
87
|
+
if hasattr(settings, 'ACCESS_MANAGER_CONTEXT') and settings.ACCESS_MANAGER_CONTEXT:
|
|
88
|
+
context = settings.ACCESS_MANAGER_CONTEXT
|
|
89
|
+
if not isinstance(context, dict):
|
|
90
|
+
raise ImproperlyConfigured(
|
|
91
|
+
'ACCESS_MANAGER_CONTEXT must be a dictionary. '
|
|
92
|
+
f'Got {type(context).__name__} instead.'
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
return ScopePermission(perm, context)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
from django.db import models
|
|
3
|
+
from django.db.models import Q
|
|
4
|
+
from django.contrib.auth.models import AbstractBaseUser
|
|
5
|
+
|
|
6
|
+
from .models import Grant
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class PermissionQuerySet(models.QuerySet):
|
|
10
|
+
"""
|
|
11
|
+
QuerySet personnalisé pour filtrer des objets selon les permissions d'un utilisateur.
|
|
12
|
+
Permet de filtrer des querysets en fonction des grants de permissions.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def allowed_for(
|
|
16
|
+
self,
|
|
17
|
+
user: AbstractBaseUser,
|
|
18
|
+
scope: str,
|
|
19
|
+
required_actions: list[str],
|
|
20
|
+
**context: Any
|
|
21
|
+
) -> "PermissionQuerySet":
|
|
22
|
+
"""
|
|
23
|
+
Filtre les objets si l'utilisateur a les permissions requises.
|
|
24
|
+
Vérifie l'existence d'un grant valide avant de retourner le queryset.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
user: L'utilisateur dont on vérifie les permissions
|
|
28
|
+
scope: Le scope à vérifier (ex: 'articles', 'users')
|
|
29
|
+
required_actions: Liste des actions requises (ex: ['r'], ['w', 'r'])
|
|
30
|
+
**context: Contexte additionnel pour filtrer (ex: tenant_id=123)
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
QuerySet complet si autorisé, QuerySet vide sinon
|
|
34
|
+
|
|
35
|
+
Example:
|
|
36
|
+
>>> Article.objects.allowed_for(user, 'articles', ['r'])
|
|
37
|
+
>>> Article.objects.allowed_for(user, 'articles', ['w'], tenant_id=123)
|
|
38
|
+
"""
|
|
39
|
+
# Construire le filtre pour vérifier l'existence d'un grant
|
|
40
|
+
grant_filter = Q(
|
|
41
|
+
user__pk=user.pk,
|
|
42
|
+
scope=scope,
|
|
43
|
+
actions__contains=list(required_actions),
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Ajouter les filtres de contexte si fournis
|
|
47
|
+
if context:
|
|
48
|
+
grant_filter &= Q(context__contains=context)
|
|
49
|
+
|
|
50
|
+
# Si un grant existe, retourner le queryset complet, sinon vide
|
|
51
|
+
if Grant.objects.filter(grant_filter).exists():
|
|
52
|
+
return self
|
|
53
|
+
return self.none()
|
|
54
|
+
|
|
55
|
+
def denied_for(
|
|
56
|
+
self,
|
|
57
|
+
user: AbstractBaseUser,
|
|
58
|
+
scope: str,
|
|
59
|
+
required_actions: list[str],
|
|
60
|
+
**context: Any
|
|
61
|
+
) -> "PermissionQuerySet":
|
|
62
|
+
"""
|
|
63
|
+
Filtre les objets si l'utilisateur N'A PAS les permissions requises.
|
|
64
|
+
Inverse de allowed_for.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
user: L'utilisateur dont on vérifie les permissions
|
|
68
|
+
scope: Le scope à vérifier
|
|
69
|
+
required_actions: Liste des actions requises
|
|
70
|
+
**context: Contexte additionnel pour filtrer
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
QuerySet complet si NON autorisé, QuerySet vide si autorisé
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
>>> Article.objects.denied_for(user, 'articles', ['w'])
|
|
77
|
+
"""
|
|
78
|
+
# Construire le filtre pour vérifier l'existence d'un grant
|
|
79
|
+
grant_filter = Q(
|
|
80
|
+
user__pk=user.pk,
|
|
81
|
+
scope=scope,
|
|
82
|
+
actions__contains=list(required_actions),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
# Ajouter les filtres de contexte si fournis
|
|
86
|
+
if context:
|
|
87
|
+
grant_filter &= Q(context__contains=context)
|
|
88
|
+
|
|
89
|
+
# Si un grant existe, retourner vide, sinon le queryset complet
|
|
90
|
+
if Grant.objects.filter(grant_filter).exists():
|
|
91
|
+
return self.none()
|
|
92
|
+
return self
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from ninja import Schema
|
|
4
|
+
from pydantic import field_validator
|
|
5
|
+
|
|
6
|
+
from .actions import ACTIONS
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def validate_actions_list(actions: list[str]) -> list[str]:
|
|
10
|
+
"""
|
|
11
|
+
Valide qu'une liste d'actions contient uniquement des actions valides.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
actions: Liste des actions à valider
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
La liste d'actions si valide
|
|
18
|
+
|
|
19
|
+
Raises:
|
|
20
|
+
ValueError: Si des actions invalides sont présentes
|
|
21
|
+
"""
|
|
22
|
+
invalid_actions = [a for a in actions if a not in ACTIONS]
|
|
23
|
+
if invalid_actions:
|
|
24
|
+
raise ValueError(
|
|
25
|
+
f"Actions invalides: {invalid_actions}. "
|
|
26
|
+
f"Actions valides: {ACTIONS}"
|
|
27
|
+
)
|
|
28
|
+
return actions
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class RoleSchema(Schema):
|
|
32
|
+
"""
|
|
33
|
+
Schéma pour un rôle.
|
|
34
|
+
"""
|
|
35
|
+
slug: str
|
|
36
|
+
name: str
|
|
37
|
+
created_at: datetime
|
|
38
|
+
updated_at: datetime
|
|
39
|
+
|
|
40
|
+
class Config:
|
|
41
|
+
from_attributes = True
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class RoleCreateSchema(Schema):
|
|
45
|
+
"""
|
|
46
|
+
Schéma pour la création d'un rôle.
|
|
47
|
+
"""
|
|
48
|
+
slug: str
|
|
49
|
+
name: str
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RoleUpdateSchema(Schema):
|
|
53
|
+
"""
|
|
54
|
+
Schéma pour la mise à jour d'un rôle.
|
|
55
|
+
"""
|
|
56
|
+
name: Optional[str] = None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class GroupSchema(Schema):
|
|
60
|
+
"""
|
|
61
|
+
Schéma pour un groupe.
|
|
62
|
+
"""
|
|
63
|
+
slug: str
|
|
64
|
+
name: str
|
|
65
|
+
roles: list[RoleSchema] = []
|
|
66
|
+
created_at: datetime
|
|
67
|
+
updated_at: datetime
|
|
68
|
+
|
|
69
|
+
class Config:
|
|
70
|
+
from_attributes = True
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class GroupCreateSchema(Schema):
|
|
74
|
+
"""
|
|
75
|
+
Schéma pour la création d'un groupe.
|
|
76
|
+
"""
|
|
77
|
+
slug: str
|
|
78
|
+
name: str
|
|
79
|
+
roles: list[str] = []
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class GroupUpdateSchema(Schema):
|
|
83
|
+
"""
|
|
84
|
+
Schéma pour la mise à jour d'un groupe.
|
|
85
|
+
"""
|
|
86
|
+
name: Optional[str] = None
|
|
87
|
+
roles: Optional[list[str]] = None
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
class RoleGrantSchema(Schema):
|
|
91
|
+
"""
|
|
92
|
+
Schéma pour un role grant.
|
|
93
|
+
"""
|
|
94
|
+
id: int
|
|
95
|
+
role: RoleSchema
|
|
96
|
+
scope: str
|
|
97
|
+
actions: list[str]
|
|
98
|
+
context: dict[str, Any] = {}
|
|
99
|
+
|
|
100
|
+
class Config:
|
|
101
|
+
from_attributes = True
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class RoleGrantCreateSchema(Schema):
|
|
105
|
+
"""
|
|
106
|
+
Schéma pour la création d'un role grant.
|
|
107
|
+
"""
|
|
108
|
+
role: str
|
|
109
|
+
scope: str
|
|
110
|
+
actions: list[str]
|
|
111
|
+
context: dict[str, Any] = {}
|
|
112
|
+
|
|
113
|
+
@field_validator('actions')
|
|
114
|
+
@classmethod
|
|
115
|
+
def validate_actions(cls, v: list[str]) -> list[str]:
|
|
116
|
+
"""Valide que toutes les actions sont valides."""
|
|
117
|
+
return validate_actions_list(v)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class RoleGrantUpdateSchema(Schema):
|
|
121
|
+
"""
|
|
122
|
+
Schéma pour la mise à jour d'un role grant.
|
|
123
|
+
"""
|
|
124
|
+
actions: Optional[list[str]] = None
|
|
125
|
+
context: Optional[dict[str, Any]] = None
|
|
126
|
+
|
|
127
|
+
@field_validator('actions')
|
|
128
|
+
@classmethod
|
|
129
|
+
def validate_actions(cls, v: Optional[list[str]]) -> Optional[list[str]]:
|
|
130
|
+
"""Valide que toutes les actions sont valides."""
|
|
131
|
+
if v is not None:
|
|
132
|
+
return validate_actions_list(v)
|
|
133
|
+
return v
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class GrantSchema(Schema):
|
|
137
|
+
"""
|
|
138
|
+
Schéma pour un grant utilisateur.
|
|
139
|
+
"""
|
|
140
|
+
id: int
|
|
141
|
+
user_id: int
|
|
142
|
+
role: Optional[RoleSchema] = None
|
|
143
|
+
scope: str
|
|
144
|
+
actions: list[str]
|
|
145
|
+
context: dict[str, Any] = {}
|
|
146
|
+
created_at: datetime
|
|
147
|
+
updated_at: datetime
|
|
148
|
+
|
|
149
|
+
class Config:
|
|
150
|
+
from_attributes = True
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class GrantCreateSchema(Schema):
|
|
154
|
+
"""
|
|
155
|
+
Schéma pour la création d'un grant utilisateur.
|
|
156
|
+
"""
|
|
157
|
+
user_id: int
|
|
158
|
+
scope: str
|
|
159
|
+
actions: list[str]
|
|
160
|
+
context: dict[str, Any] = {}
|
|
161
|
+
role: Optional[str] = None
|
|
162
|
+
|
|
163
|
+
@field_validator('actions')
|
|
164
|
+
@classmethod
|
|
165
|
+
def validate_actions(cls, v: list[str]) -> list[str]:
|
|
166
|
+
"""Valide que toutes les actions sont valides."""
|
|
167
|
+
return validate_actions_list(v)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
class GrantUpdateSchema(Schema):
|
|
171
|
+
"""
|
|
172
|
+
Schéma pour la mise à jour d'un grant utilisateur.
|
|
173
|
+
"""
|
|
174
|
+
actions: Optional[list[str]] = None
|
|
175
|
+
context: Optional[dict[str, Any]] = None
|
|
176
|
+
role: Optional[str] = None
|
|
177
|
+
|
|
178
|
+
@field_validator('actions')
|
|
179
|
+
@classmethod
|
|
180
|
+
def validate_actions(cls, v: Optional[list[str]]) -> Optional[list[str]]:
|
|
181
|
+
"""Valide que toutes les actions sont valides."""
|
|
182
|
+
if v is not None:
|
|
183
|
+
return validate_actions_list(v)
|
|
184
|
+
return v
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class PermissionCheckSchema(Schema):
|
|
188
|
+
"""
|
|
189
|
+
Schéma pour une requête de vérification de permissions.
|
|
190
|
+
"""
|
|
191
|
+
user_id: int
|
|
192
|
+
scope: str
|
|
193
|
+
required_actions: list[str]
|
|
194
|
+
context: dict[str, Any] = {}
|
|
195
|
+
|
|
196
|
+
@field_validator('required_actions')
|
|
197
|
+
@classmethod
|
|
198
|
+
def validate_actions(cls, v: list[str]) -> list[str]:
|
|
199
|
+
"""Valide que toutes les actions sont valides."""
|
|
200
|
+
return validate_actions_list(v)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class PermissionCheckResponseSchema(Schema):
|
|
204
|
+
"""
|
|
205
|
+
Schéma pour la réponse d'une vérification de permissions.
|
|
206
|
+
"""
|
|
207
|
+
allowed: bool
|
|
208
|
+
user_id: int
|
|
209
|
+
scope: str
|
|
210
|
+
required_actions: list[str]
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class AssignRoleSchema(Schema):
|
|
214
|
+
"""
|
|
215
|
+
Schéma pour assigner un rôle à un utilisateur.
|
|
216
|
+
"""
|
|
217
|
+
user_id: int
|
|
218
|
+
role: str
|
|
219
|
+
by_user_id: Optional[int] = None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class RevokeRoleSchema(Schema):
|
|
223
|
+
"""
|
|
224
|
+
Schéma pour révoquer un rôle d'un utilisateur.
|
|
225
|
+
"""
|
|
226
|
+
user_id: int
|
|
227
|
+
role: str
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class AssignGroupSchema(Schema):
|
|
231
|
+
"""
|
|
232
|
+
Schéma pour assigner un groupe à un utilisateur.
|
|
233
|
+
"""
|
|
234
|
+
user_id: int
|
|
235
|
+
group: str
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class RevokeGroupSchema(Schema):
|
|
239
|
+
"""
|
|
240
|
+
Schéma pour révoquer un groupe d'un utilisateur.
|
|
241
|
+
"""
|
|
242
|
+
user_id: int
|
|
243
|
+
group: str
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class OverrideGrantSchema(Schema):
|
|
247
|
+
"""
|
|
248
|
+
Schéma pour modifier un grant en retirant des actions.
|
|
249
|
+
"""
|
|
250
|
+
user_id: int
|
|
251
|
+
scope: str
|
|
252
|
+
remove_actions: list[str]
|
|
253
|
+
|
|
254
|
+
@field_validator('remove_actions')
|
|
255
|
+
@classmethod
|
|
256
|
+
def validate_actions(cls, v: list[str]) -> list[str]:
|
|
257
|
+
"""Valide que toutes les actions sont valides."""
|
|
258
|
+
return validate_actions_list(v)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
class GroupSyncResponseSchema(Schema):
|
|
262
|
+
"""
|
|
263
|
+
Schéma pour la réponse de la synchronisation d'un groupe.
|
|
264
|
+
"""
|
|
265
|
+
users_synced: int
|
|
266
|
+
grants_updated: int
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
class PresetLoadResponseSchema(Schema):
|
|
270
|
+
"""
|
|
271
|
+
Schéma pour la réponse du chargement d'un preset.
|
|
272
|
+
"""
|
|
273
|
+
roles_created: int
|
|
274
|
+
groups_created: int
|
|
275
|
+
role_grants_created: int
|
|
276
|
+
message: str = "Preset chargé avec succès"
|