maquinaweb-shared-auth 0.2.60__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.
@@ -0,0 +1,266 @@
1
+ """
2
+ Permissões customizadas para DRF
3
+ """
4
+
5
+ from rest_framework import permissions
6
+
7
+ from shared_auth.middleware import get_member
8
+ from shared_auth.utils import get_organization_model
9
+
10
+
11
+ class IsAuthenticated(permissions.BasePermission):
12
+ """
13
+ Verifica se usuário está autenticado via SharedToken
14
+ """
15
+
16
+ message = "Autenticação necessária."
17
+
18
+ def has_permission(self, request, view):
19
+ return bool(
20
+ request.user and hasattr(request.user, "pk") and request.user.is_active
21
+ )
22
+
23
+
24
+ class HasActiveOrganization(permissions.BasePermission):
25
+ """
26
+ Verifica se usuário tem organização ativa
27
+ """
28
+
29
+ message = "Organização ativa necessária."
30
+
31
+ def has_permission(self, request, view):
32
+ if not request.user or not hasattr(request, "organization_id"):
33
+ return False
34
+
35
+ if not request.organization_id:
36
+ return False
37
+
38
+ # Verificar se organização está ativa
39
+ Organization = get_organization_model()
40
+
41
+ try:
42
+ org = Organization.objects.get(pk=request.organization_id)
43
+ return org.is_active()
44
+ except Organization.DoesNotExist:
45
+ return False
46
+
47
+
48
+ class IsSameOrganization(permissions.BasePermission):
49
+ """
50
+ Verifica se o objeto pertence à mesma organização do usuário
51
+
52
+ O model deve ter organization_id
53
+ """
54
+
55
+ message = "Você não tem permissão para acessar este recurso."
56
+
57
+ def has_object_permission(self, request, view, obj):
58
+ if not hasattr(request, "organization_id"):
59
+ return False
60
+
61
+ if not hasattr(obj, "organization_id"):
62
+ return True # Se objeto não tem org, permite
63
+
64
+ # Verifica se o usuário é membro da organização do objeto
65
+ if not get_member(request.user.pk, obj.organization_id):
66
+ return False
67
+
68
+ return obj.organization_id == request.organization_id
69
+
70
+
71
+ class IsOwnerOrSameOrganization(permissions.BasePermission):
72
+ """
73
+ Verifica se é o dono do objeto OU da mesma organização
74
+
75
+ O model deve ter user_id e/ou organization_id
76
+ """
77
+
78
+ message = "Você não tem permissão para acessar este recurso."
79
+
80
+ def has_object_permission(self, request, view, obj):
81
+ # Verificar se é o dono
82
+ if hasattr(obj, "user_id") and obj.user_id == request.user.pk:
83
+ return True
84
+
85
+ # Verificar se é da mesma organização
86
+ if hasattr(obj, "organization_id") and hasattr(request, "organization_id"):
87
+ # Verifica se o usuário é membro da organização do objeto
88
+ if get_member(request.user.pk, obj.organization_id):
89
+ return obj.organization_id == request.organization_id
90
+
91
+ return False
92
+
93
+
94
+ class HasSystemPermission(permissions.BasePermission):
95
+ """
96
+ Verifica se usuário tem permissão específica no sistema.
97
+
98
+ Usage:
99
+ class MyViewSet(viewsets.ModelViewSet):
100
+ permission_classes = [HasSystemPermission]
101
+ required_permission = 'create_invoices'
102
+ """
103
+
104
+ message = "Você não tem permissão para realizar esta ação."
105
+
106
+ def has_permission(self, request, view):
107
+ """Verifica permissão no nível da view"""
108
+ from django.conf import settings
109
+ from shared_auth.permissions_helpers import user_has_permission
110
+
111
+ # Pegar permissão requerida
112
+ permission_codename = getattr(view, 'required_permission', None)
113
+
114
+ if not permission_codename:
115
+ # Se não tem permissão definida, permite
116
+ return True
117
+
118
+ # Verificar autenticação
119
+ if not request.user or not request.user.is_authenticated:
120
+ return False
121
+
122
+ # Pegar organização
123
+ organization_id = getattr(request, 'organization_id', None)
124
+ if not organization_id:
125
+ return False
126
+
127
+ # Pegar sistema
128
+ system_id = getattr(settings, 'SYSTEM_ID', None)
129
+ if not system_id:
130
+ # Tentar pegar do header
131
+ header_value = request.headers.get('X-System-ID')
132
+ if header_value:
133
+ try:
134
+ system_id = int(header_value)
135
+ except (ValueError, TypeError):
136
+ return False
137
+ else:
138
+ return False
139
+
140
+ # Verificar permissão
141
+ return user_has_permission(
142
+ request.user.id,
143
+ organization_id,
144
+ permission_codename,
145
+ system_id,
146
+ request=request
147
+ )
148
+
149
+
150
+ class HasAnyPermission(permissions.BasePermission):
151
+ """
152
+ Verifica se usuário tem pelo menos uma das permissões.
153
+
154
+ Usage:
155
+ class MyViewSet(viewsets.ModelViewSet):
156
+ permission_classes = [HasAnyPermission]
157
+ required_permissions = ['view_reports', 'create_reports']
158
+ """
159
+
160
+ message = "Você não tem nenhuma das permissões necessárias."
161
+
162
+ def has_permission(self, request, view):
163
+ """Verifica se tem pelo menos uma permissão"""
164
+ from django.conf import settings
165
+ from shared_auth.permissions_helpers import user_has_permission
166
+
167
+ # Pegar permissões requeridas
168
+ permission_codenames = getattr(view, 'required_permissions', [])
169
+
170
+ if not permission_codenames:
171
+ # Se não tem permissões definidas, permite
172
+ return True
173
+
174
+ # Verificar autenticação
175
+ if not request.user or not request.user.is_authenticated:
176
+ return False
177
+
178
+ # Pegar organização
179
+ organization_id = getattr(request, 'organization_id', None)
180
+ if not organization_id:
181
+ return False
182
+
183
+ # Pegar sistema
184
+ system_id = getattr(settings, 'SYSTEM_ID', None)
185
+ if not system_id:
186
+ # Tentar pegar do header
187
+ header_value = request.headers.get('X-System-ID')
188
+ if header_value:
189
+ try:
190
+ system_id = int(header_value)
191
+ except (ValueError, TypeError):
192
+ return False
193
+ else:
194
+ return False
195
+
196
+ # Verificar se tem pelo menos uma permissão
197
+ return any(
198
+ user_has_permission(
199
+ request.user.id,
200
+ organization_id,
201
+ perm,
202
+ system_id,
203
+ request=request
204
+ )
205
+ for perm in permission_codenames
206
+ )
207
+
208
+
209
+ class HasAllPermissions(permissions.BasePermission):
210
+ """
211
+ Verifica se usuário tem todas as permissões.
212
+
213
+ Usage:
214
+ class MyViewSet(viewsets.ModelViewSet):
215
+ permission_classes = [HasAllPermissions]
216
+ required_permissions = ['create_invoices', 'edit_invoices']
217
+ """
218
+
219
+ message = "Você não tem todas as permissões necessárias."
220
+
221
+ def has_permission(self, request, view):
222
+ """Verifica se tem todas as permissões"""
223
+ from django.conf import settings
224
+ from shared_auth.permissions_helpers import user_has_permission
225
+
226
+ # Pegar permissões requeridas
227
+ permission_codenames = getattr(view, 'required_permissions', [])
228
+
229
+ if not permission_codenames:
230
+ # Se não tem permissões definidas, permite
231
+ return True
232
+
233
+ # Verificar autenticação
234
+ if not request.user or not request.user.is_authenticated:
235
+ return False
236
+
237
+ # Pegar organização
238
+ organization_id = getattr(request, 'organization_id', None)
239
+ if not organization_id:
240
+ return False
241
+
242
+ # Pegar sistema
243
+ system_id = getattr(settings, 'SYSTEM_ID', None)
244
+ if not system_id:
245
+ # Tentar pegar do header
246
+ header_value = request.headers.get('X-System-ID')
247
+ if header_value:
248
+ try:
249
+ system_id = int(header_value)
250
+ except (ValueError, TypeError):
251
+ return False
252
+ else:
253
+ return False
254
+
255
+ # Verificar se tem todas as permissões
256
+ return all(
257
+ user_has_permission(
258
+ request.user.id,
259
+ organization_id,
260
+ perm,
261
+ system_id,
262
+ request=request
263
+ )
264
+ for perm in permission_codenames
265
+ )
266
+
@@ -0,0 +1,249 @@
1
+ """
2
+ Sistema de cache de permissões por request.
3
+ Elimina queries duplicadas durante verificação de permissões.
4
+ """
5
+
6
+ from threading import local
7
+
8
+ # Thread-local storage para cache quando não há request disponível
9
+ _thread_local = local()
10
+
11
+
12
+ def _get_cache_dict(request=None):
13
+ """
14
+ Obtém o dicionário de cache de permissões.
15
+
16
+ Args:
17
+ request: Request object (opcional)
18
+
19
+ Returns:
20
+ dict: Dicionário de cache
21
+ """
22
+ if request and hasattr(request, '_permissions_cache'):
23
+ return request._permissions_cache
24
+
25
+ # Fallback para thread-local (útil em contextos sem request)
26
+ if not hasattr(_thread_local, 'permissions_cache'):
27
+ _thread_local.permissions_cache = {}
28
+ return _thread_local.permissions_cache
29
+
30
+
31
+ def get_cached_member(user_id, organization_id, request=None):
32
+ """
33
+ Busca e cacheia Member para o par (user_id, organization_id).
34
+
35
+ Args:
36
+ user_id: ID do usuário
37
+ organization_id: ID da organização
38
+ request: Request object (opcional)
39
+
40
+ Returns:
41
+ Member instance ou None
42
+ """
43
+ from .utils import get_member_model
44
+
45
+ cache = _get_cache_dict(request)
46
+ cache_key = f'member_{user_id}_{organization_id}'
47
+
48
+ if cache_key in cache:
49
+ return cache[cache_key]
50
+
51
+ Member = get_member_model()
52
+ member = Member.objects.filter(
53
+ user_id=user_id,
54
+ organization_id=organization_id
55
+ ).first()
56
+
57
+ cache[cache_key] = member
58
+ return member
59
+
60
+
61
+ def get_cached_member_group(member_id, system_id, request=None):
62
+ """
63
+ Busca e cacheia MemberSystemGroup para o par (member_id, system_id).
64
+
65
+ Args:
66
+ member_id: ID do membro
67
+ system_id: ID do sistema
68
+ request: Request object (opcional)
69
+
70
+ Returns:
71
+ MemberSystemGroup instance ou None
72
+ """
73
+ from .utils import get_member_system_group_model
74
+
75
+ cache = _get_cache_dict(request)
76
+ cache_key = f'member_group_{member_id}_{system_id}'
77
+
78
+ if cache_key in cache:
79
+ return cache[cache_key]
80
+
81
+ MemberSystemGroup = get_member_system_group_model()
82
+ member_group = MemberSystemGroup.objects.get_group_for_member_and_system(
83
+ member_id,
84
+ system_id
85
+ )
86
+
87
+ cache[cache_key] = member_group
88
+ return member_group
89
+
90
+
91
+ def get_cached_group_permissions(group_id, system_id, request=None):
92
+ """
93
+ Busca e cacheia GroupOrganizationPermissions com suas permissões.
94
+
95
+ Args:
96
+ group_id: ID do grupo de permissões
97
+ system_id: ID do sistema
98
+ request: Request object (opcional)
99
+
100
+ Returns:
101
+ GroupOrganizationPermissions instance ou None
102
+ """
103
+ from .utils import get_group_organization_permissions_model
104
+
105
+ cache = _get_cache_dict(request)
106
+ cache_key = f'group_perms_{group_id}_{system_id}'
107
+
108
+ if cache_key in cache:
109
+ return cache[cache_key]
110
+
111
+ GroupOrgPermissions = get_group_organization_permissions_model()
112
+ try:
113
+ # Busca o grupo sem prefetch ainda
114
+ group = GroupOrgPermissions.objects.get(pk=group_id)
115
+ except GroupOrgPermissions.DoesNotExist:
116
+ cache[cache_key] = None
117
+ return None
118
+
119
+ cache[cache_key] = group
120
+ return group
121
+
122
+
123
+ def get_cached_permission_codenames(group_id, system_id, request=None):
124
+ """
125
+ Busca e cacheia SET de codenames de permissões do grupo.
126
+
127
+ Args:
128
+ group_id: ID do grupo de permissões
129
+ system_id: ID do sistema
130
+ request: Request object (opcional)
131
+
132
+ Returns:
133
+ set: Set de codenames de permissões
134
+ """
135
+ cache = _get_cache_dict(request)
136
+ cache_key = f'perm_codenames_{group_id}_{system_id}'
137
+
138
+ if cache_key in cache:
139
+ return cache[cache_key]
140
+
141
+ group = get_cached_group_permissions(group_id, system_id, request)
142
+ if not group:
143
+ cache[cache_key] = set()
144
+ return set()
145
+
146
+ # Buscar todos os codenames de uma vez
147
+ codenames = set(
148
+ group.permissions.filter(system_id=system_id)
149
+ .values_list('codename', flat=True)
150
+ )
151
+
152
+ cache[cache_key] = codenames
153
+ return codenames
154
+
155
+
156
+ def get_cached_all_permissions(group_id, system_id, request=None):
157
+ """
158
+ Busca e cacheia lista de Permission objects do grupo.
159
+
160
+ Args:
161
+ group_id: ID do grupo de permissões
162
+ system_id: ID do sistema
163
+ request: Request object (opcional)
164
+
165
+ Returns:
166
+ list: Lista de Permission objects
167
+ """
168
+ cache = _get_cache_dict(request)
169
+ cache_key = f'all_perms_{group_id}_{system_id}'
170
+
171
+ if cache_key in cache:
172
+ return cache[cache_key]
173
+
174
+ group = get_cached_group_permissions(group_id, system_id, request)
175
+ if not group:
176
+ cache[cache_key] = []
177
+ return []
178
+
179
+ # Buscar todas as permissões de uma vez e converter para lista
180
+ permissions = list(group.permissions.filter(system_id=system_id))
181
+
182
+ cache[cache_key] = permissions
183
+ return permissions
184
+
185
+
186
+ def warmup_permissions_cache(user_id, organization_id, system_id, request=None):
187
+ """
188
+ Pré-carrega (warm-up) todas as permissões do usuário no cache.
189
+ Reduz 4 queries para apenas as necessárias no início da request.
190
+
191
+ Esta função deve ser chamada pelo middleware após autenticação.
192
+
193
+ Args:
194
+ user_id: ID do usuário
195
+ organization_id: ID da organização
196
+ system_id: ID do sistema
197
+ request: Request object
198
+
199
+ Returns:
200
+ bool: True se conseguiu fazer warm-up, False caso contrário
201
+ """
202
+ # 1. Buscar e cachear member
203
+ member = get_cached_member(user_id, organization_id, request)
204
+ if not member:
205
+ return False
206
+
207
+ # 2. Buscar e cachear member group
208
+ member_group = get_cached_member_group(member.id, system_id, request)
209
+ if not member_group:
210
+ return False
211
+
212
+ # 3. Buscar e cachear group permissions
213
+ group = get_cached_group_permissions(member_group.group_id, system_id, request)
214
+ if not group:
215
+ return False
216
+
217
+ # 4. OTIMIZAÇÃO PRINCIPAL: Carregar TODAS as permissões e codenames de uma vez
218
+ # Isso faz com que verificações subsequentes sejam O(1) em memória
219
+ get_cached_permission_codenames(member_group.group_id, system_id, request)
220
+ get_cached_all_permissions(member_group.group_id, system_id, request)
221
+
222
+ return True
223
+
224
+
225
+ def clear_permissions_cache(request=None):
226
+ """
227
+ Limpa o cache de permissões.
228
+ Útil para testes ou quando necessário forçar reload.
229
+
230
+ Args:
231
+ request: Request object (opcional)
232
+ """
233
+ if request and hasattr(request, '_permissions_cache'):
234
+ request._permissions_cache.clear()
235
+
236
+ if hasattr(_thread_local, 'permissions_cache'):
237
+ _thread_local.permissions_cache.clear()
238
+
239
+
240
+ def init_permissions_cache(request):
241
+ """
242
+ Inicializa o cache de permissões no request.
243
+ Chamado pelo middleware no início de cada request.
244
+
245
+ Args:
246
+ request: Request object
247
+ """
248
+ if not hasattr(request, '_permissions_cache'):
249
+ request._permissions_cache = {}