maquinaweb-shared-auth 0.2.32__py3-none-any.whl → 0.2.34__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.
Potentially problematic release.
This version of maquinaweb-shared-auth might be problematic. Click here for more details.
- {maquinaweb_shared_auth-0.2.32.dist-info → maquinaweb_shared_auth-0.2.34.dist-info}/METADATA +1 -1
- maquinaweb_shared_auth-0.2.34.dist-info/RECORD +23 -0
- shared_auth/__init__.py +16 -1
- shared_auth/abstract_models.py +328 -0
- shared_auth/authentication.py +12 -4
- shared_auth/decorators.py +10 -5
- shared_auth/managers.py +15 -8
- shared_auth/middleware.py +18 -8
- shared_auth/mixins.py +15 -11
- shared_auth/models.py +36 -213
- shared_auth/permissions.py +4 -3
- shared_auth/router.py +1 -1
- shared_auth/serializers.py +29 -4
- shared_auth/utils.py +113 -0
- maquinaweb_shared_auth-0.2.32.dist-info/RECORD +0 -21
- {maquinaweb_shared_auth-0.2.32.dist-info → maquinaweb_shared_auth-0.2.34.dist-info}/WHEEL +0 -0
- {maquinaweb_shared_auth-0.2.32.dist-info → maquinaweb_shared_auth-0.2.34.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
shared_auth/__init__.py,sha256=VVZP2qk5NCIQacyiVEkgTIVemYYTsISJh2Rn2VBvUXw,434
|
|
2
|
+
shared_auth/abstract_models.py,sha256=jaixzhGl4B8Pc6ppG3BxSBNPZxVw6S3o9IDbcVmtGns,9436
|
|
3
|
+
shared_auth/app.py,sha256=EHoLKlpW41o6ZxcH184aMhnWQxkVp9fH2_-89RjMz-4,215
|
|
4
|
+
shared_auth/authentication.py,sha256=fwqhGUAslRFXVLBxuxrzB3q8eAWlAyRIuaZ3BL_s-zA,1653
|
|
5
|
+
shared_auth/conf.py,sha256=WlSXQB7p3BfE3BL6WR6EDYpCHQEjDlxQlyf8dTfClsk,621
|
|
6
|
+
shared_auth/decorators.py,sha256=-2RHRzvPJzN0c8Q-cB1_hf5tk386jcHZ9-GixKSe0ds,3346
|
|
7
|
+
shared_auth/exceptions.py,sha256=VKamHjBl2yjXG2RsMrLrXru1_Q9IJXmy4xmDcXlpWsU,627
|
|
8
|
+
shared_auth/fields.py,sha256=RAcmFh1D_nkbai_7t_OrPZhfhAipesy5kKnEj4LUvvM,1254
|
|
9
|
+
shared_auth/managers.py,sha256=OQd76OeAkeyRoFnifQ0zZdp45h27QVwBpR0BgiiTFbQ,6968
|
|
10
|
+
shared_auth/middleware.py,sha256=A8yc8Ld4EslEcPCdekfCrU7eAXgql_AntYjy1xMchu8,6335
|
|
11
|
+
shared_auth/mixins.py,sha256=kcTkjrNu-UGLwXQHsq9vQDx7qQjILa3Na8HvouLU3Ms,7588
|
|
12
|
+
shared_auth/models.py,sha256=HCVIog61J0xeygYIs_amSA-MsSRhxIlGTSkegA9b51c,1444
|
|
13
|
+
shared_auth/permissions.py,sha256=l48n9qCUMrhQOTa5_Cv1vynv-bb3i5T05yLoyKvKmOM,2713
|
|
14
|
+
shared_auth/router.py,sha256=JhlDjosViMBmuvYm08vJzKyvrg5P5ECtrU4tKAGsuoU,684
|
|
15
|
+
shared_auth/serializers.py,sha256=PZENjBfD4GK8SnJL0Xp1Z6SzZlNNeyzJILp8uPxkEEI,14224
|
|
16
|
+
shared_auth/storage_backend.py,sha256=Eqkjz8aF5UrOpRwYl-J0Td95IObfxnJ8eLQDJVFM3Io,184
|
|
17
|
+
shared_auth/urls.py,sha256=591wWEWJPaHEGkcOZ8yvfgxddRyOcZLgOc0vNtF7XRI,289
|
|
18
|
+
shared_auth/utils.py,sha256=hItxL-spzyS2jUHSm9gHoATGH1m5bNl9DYGrule_35s,3524
|
|
19
|
+
shared_auth/views.py,sha256=2hyLnYSWUscfq-jVcskt-ukzDt4vg6IXeKjRDRu9RXk,1519
|
|
20
|
+
maquinaweb_shared_auth-0.2.34.dist-info/METADATA,sha256=0ll4Oy9MyeitIUNiYs9V-984k-G_bOyabSydESbkkVA,26325
|
|
21
|
+
maquinaweb_shared_auth-0.2.34.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
22
|
+
maquinaweb_shared_auth-0.2.34.dist-info/top_level.txt,sha256=msyYRy02ZV7zz7GR1raUI5LXGFIFn2TIkgkeKZqKufE,12
|
|
23
|
+
maquinaweb_shared_auth-0.2.34.dist-info/RECORD,,
|
shared_auth/__init__.py
CHANGED
|
@@ -3,4 +3,19 @@ Biblioteca compartilhada para acesso aos models de autenticação
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
__version__ = '1.0.0'
|
|
6
|
-
default_app_config = "shared_auth.app.SharedAuthConfig"
|
|
6
|
+
default_app_config = "shared_auth.app.SharedAuthConfig"
|
|
7
|
+
|
|
8
|
+
# Exportar utilitários para facilitar importação
|
|
9
|
+
from .utils import (
|
|
10
|
+
get_member_model,
|
|
11
|
+
get_organization_model,
|
|
12
|
+
get_token_model,
|
|
13
|
+
get_user_model,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
'get_token_model',
|
|
18
|
+
'get_organization_model',
|
|
19
|
+
'get_user_model',
|
|
20
|
+
'get_member_model',
|
|
21
|
+
]
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Models abstratos para customização
|
|
3
|
+
Estes models podem ser herdados nos apps clientes para adicionar campos e métodos customizados
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
from django.contrib.auth.models import AbstractUser
|
|
9
|
+
from django.db import models
|
|
10
|
+
|
|
11
|
+
from .conf import MEMBER_TABLE, ORGANIZATION_TABLE, TOKEN_TABLE, USER_TABLE
|
|
12
|
+
from .exceptions import OrganizationNotFoundError
|
|
13
|
+
from .managers import SharedMemberManager, SharedOrganizationManager, UserManager
|
|
14
|
+
from .storage_backend import Storage
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def organization_image_path(instance, filename):
|
|
18
|
+
return os.path.join(
|
|
19
|
+
"organization",
|
|
20
|
+
str(instance.pk),
|
|
21
|
+
"images",
|
|
22
|
+
filename,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class AbstractSharedToken(models.Model):
|
|
27
|
+
"""
|
|
28
|
+
Model abstrato READ-ONLY da tabela authtoken_token
|
|
29
|
+
Usado para validar tokens em outros sistemas
|
|
30
|
+
|
|
31
|
+
Para customizar, crie um model no seu app:
|
|
32
|
+
|
|
33
|
+
from shared_auth.abstract_models import AbstractSharedToken
|
|
34
|
+
|
|
35
|
+
class CustomToken(AbstractSharedToken):
|
|
36
|
+
# Adicione campos customizados
|
|
37
|
+
custom_field = models.CharField(max_length=100)
|
|
38
|
+
|
|
39
|
+
class Meta(AbstractSharedToken.Meta):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
E configure no settings.py:
|
|
43
|
+
SHARED_AUTH_TOKEN_MODEL = 'seu_app.CustomToken'
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
key = models.CharField(max_length=40, primary_key=True)
|
|
47
|
+
user_id = models.IntegerField()
|
|
48
|
+
created = models.DateTimeField()
|
|
49
|
+
|
|
50
|
+
objects = models.Manager()
|
|
51
|
+
|
|
52
|
+
class Meta:
|
|
53
|
+
abstract = True
|
|
54
|
+
managed = False
|
|
55
|
+
db_table = TOKEN_TABLE
|
|
56
|
+
|
|
57
|
+
def __str__(self):
|
|
58
|
+
return self.key
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def user(self):
|
|
62
|
+
"""Acessa usuário do token"""
|
|
63
|
+
from .utils import get_user_model
|
|
64
|
+
|
|
65
|
+
if not hasattr(self, "_cached_user"):
|
|
66
|
+
User = get_user_model()
|
|
67
|
+
self._cached_user = User.objects.get_or_fail(self.user_id)
|
|
68
|
+
return self._cached_user
|
|
69
|
+
|
|
70
|
+
def is_valid(self):
|
|
71
|
+
"""Verifica se token ainda é válido"""
|
|
72
|
+
# Implementar lógica de expiração se necessário
|
|
73
|
+
return True
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class AbstractSharedOrganization(models.Model):
|
|
77
|
+
"""
|
|
78
|
+
Model abstrato READ-ONLY da tabela organization
|
|
79
|
+
Usado para acessar dados de organizações em outros sistemas
|
|
80
|
+
|
|
81
|
+
Para customizar, crie um model no seu app:
|
|
82
|
+
|
|
83
|
+
from shared_auth.abstract_models import AbstractSharedOrganization
|
|
84
|
+
|
|
85
|
+
class CustomOrganization(AbstractSharedOrganization):
|
|
86
|
+
# Adicione campos customizados
|
|
87
|
+
custom_field = models.CharField(max_length=100)
|
|
88
|
+
|
|
89
|
+
class Meta(AbstractSharedOrganization.Meta):
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
E configure no settings.py:
|
|
93
|
+
SHARED_AUTH_ORGANIZATION_MODEL = 'seu_app.CustomOrganization'
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
# Campos principais
|
|
97
|
+
name = models.CharField(max_length=255)
|
|
98
|
+
fantasy_name = models.CharField(max_length=255, blank=True, null=True)
|
|
99
|
+
cnpj = models.CharField(max_length=255, blank=True, null=True)
|
|
100
|
+
telephone = models.CharField(max_length=50, blank=True, null=True)
|
|
101
|
+
cellphone = models.CharField(max_length=50, blank=True, null=True)
|
|
102
|
+
email = models.EmailField(blank=True, null=True)
|
|
103
|
+
image_organization = models.ImageField(
|
|
104
|
+
storage=Storage, upload_to=organization_image_path, null=True
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
# Relacionamentos
|
|
108
|
+
main_organization_id = models.IntegerField(null=True, blank=True)
|
|
109
|
+
is_branch = models.BooleanField(default=False)
|
|
110
|
+
metadata = models.JSONField(default=dict)
|
|
111
|
+
|
|
112
|
+
# Metadados
|
|
113
|
+
created_at = models.DateTimeField()
|
|
114
|
+
updated_at = models.DateTimeField()
|
|
115
|
+
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
116
|
+
|
|
117
|
+
objects = SharedOrganizationManager()
|
|
118
|
+
|
|
119
|
+
class Meta:
|
|
120
|
+
abstract = True
|
|
121
|
+
managed = False
|
|
122
|
+
db_table = ORGANIZATION_TABLE
|
|
123
|
+
|
|
124
|
+
def __str__(self):
|
|
125
|
+
return self.fantasy_name or self.name or f"Org #{self.pk}"
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def main_organization(self):
|
|
129
|
+
"""
|
|
130
|
+
Acessa organização principal (lazy loading)
|
|
131
|
+
|
|
132
|
+
Usage:
|
|
133
|
+
if org.is_branch:
|
|
134
|
+
main = org.main_organization
|
|
135
|
+
"""
|
|
136
|
+
from .utils import get_organization_model
|
|
137
|
+
|
|
138
|
+
if self.main_organization_id:
|
|
139
|
+
Organization = get_organization_model()
|
|
140
|
+
return Organization.objects.get_or_fail(self.main_organization_id)
|
|
141
|
+
return None
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def branches(self):
|
|
145
|
+
"""
|
|
146
|
+
Retorna filiais desta organização
|
|
147
|
+
|
|
148
|
+
Usage:
|
|
149
|
+
branches = org.branches
|
|
150
|
+
"""
|
|
151
|
+
from .utils import get_organization_model
|
|
152
|
+
|
|
153
|
+
Organization = get_organization_model()
|
|
154
|
+
return Organization.objects.filter(main_organization_id=self.pk)
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def members(self):
|
|
158
|
+
"""
|
|
159
|
+
Retorna membros desta organização
|
|
160
|
+
|
|
161
|
+
Usage:
|
|
162
|
+
members = org.members
|
|
163
|
+
for member in members:
|
|
164
|
+
print(member.user.email)
|
|
165
|
+
"""
|
|
166
|
+
from .utils import get_member_model
|
|
167
|
+
|
|
168
|
+
Member = get_member_model()
|
|
169
|
+
return Member.objects.for_organization(self.pk)
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def users(self):
|
|
173
|
+
"""
|
|
174
|
+
Retorna usuários desta organização
|
|
175
|
+
|
|
176
|
+
Usage:
|
|
177
|
+
users = org.users
|
|
178
|
+
"""
|
|
179
|
+
from .utils import get_user_model
|
|
180
|
+
|
|
181
|
+
User = get_user_model()
|
|
182
|
+
return User.objects.filter(
|
|
183
|
+
id__in=self.members.values_list("user_id", flat=True)
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def is_active(self):
|
|
187
|
+
"""Verifica se organização está ativa"""
|
|
188
|
+
return self.deleted_at is None
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class AbstractUser(AbstractUser):
|
|
192
|
+
"""
|
|
193
|
+
Model abstrato READ-ONLY da tabela auth_user
|
|
194
|
+
|
|
195
|
+
Para customizar, crie um model no seu app:
|
|
196
|
+
|
|
197
|
+
from shared_auth.abstract_models import AbstractUser
|
|
198
|
+
|
|
199
|
+
class CustomUser(AbstractUser):
|
|
200
|
+
# Adicione campos customizados
|
|
201
|
+
custom_field = models.CharField(max_length=100)
|
|
202
|
+
|
|
203
|
+
class Meta(AbstractUser.Meta):
|
|
204
|
+
pass
|
|
205
|
+
|
|
206
|
+
E configure no settings.py:
|
|
207
|
+
SHARED_AUTH_USER_MODEL = 'seu_app.CustomUser'
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
date_joined = models.DateTimeField()
|
|
211
|
+
last_login = models.DateTimeField(null=True, blank=True)
|
|
212
|
+
avatar = models.ImageField(storage=Storage, blank=True, null=True)
|
|
213
|
+
|
|
214
|
+
# Campos customizados
|
|
215
|
+
createdat = models.DateTimeField()
|
|
216
|
+
updatedat = models.DateTimeField()
|
|
217
|
+
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
218
|
+
|
|
219
|
+
objects = UserManager()
|
|
220
|
+
|
|
221
|
+
class Meta:
|
|
222
|
+
abstract = True
|
|
223
|
+
managed = False
|
|
224
|
+
db_table = USER_TABLE
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def organizations(self):
|
|
228
|
+
"""
|
|
229
|
+
Retorna todas as organizações associadas ao usuário.
|
|
230
|
+
"""
|
|
231
|
+
from .utils import get_organization_model, get_member_model
|
|
232
|
+
|
|
233
|
+
Organization = get_organization_model()
|
|
234
|
+
Member = get_member_model()
|
|
235
|
+
|
|
236
|
+
return Organization.objects.filter(
|
|
237
|
+
id__in=Member.objects.filter(user_id=self.id).values_list(
|
|
238
|
+
"organization_id", flat=True
|
|
239
|
+
)
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
def get_org(self, organization_id):
|
|
243
|
+
"""
|
|
244
|
+
Retorna a organização especificada pelo ID, se o usuário for membro.
|
|
245
|
+
"""
|
|
246
|
+
from .utils import get_organization_model, get_member_model
|
|
247
|
+
|
|
248
|
+
Organization = get_organization_model()
|
|
249
|
+
Member = get_member_model()
|
|
250
|
+
|
|
251
|
+
try:
|
|
252
|
+
organization = Organization.objects.get(id=organization_id)
|
|
253
|
+
except Organization.DoesNotExist:
|
|
254
|
+
raise OrganizationNotFoundError(
|
|
255
|
+
f"Organização com ID {organization_id} não encontrada."
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if not Member.objects.filter(
|
|
259
|
+
user_id=self.id, organization_id=organization.id
|
|
260
|
+
).exists():
|
|
261
|
+
raise OrganizationNotFoundError("Usuário não é membro desta organização.")
|
|
262
|
+
|
|
263
|
+
return organization
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class AbstractSharedMember(models.Model):
|
|
267
|
+
"""
|
|
268
|
+
Model abstrato READ-ONLY da tabela organization_member
|
|
269
|
+
Relacionamento entre User e Organization
|
|
270
|
+
|
|
271
|
+
Para customizar, crie um model no seu app:
|
|
272
|
+
|
|
273
|
+
from shared_auth.abstract_models import AbstractSharedMember
|
|
274
|
+
|
|
275
|
+
class CustomMember(AbstractSharedMember):
|
|
276
|
+
# Adicione campos customizados
|
|
277
|
+
custom_field = models.CharField(max_length=100)
|
|
278
|
+
|
|
279
|
+
class Meta(AbstractSharedMember.Meta):
|
|
280
|
+
pass
|
|
281
|
+
|
|
282
|
+
E configure no settings.py:
|
|
283
|
+
SHARED_AUTH_MEMBER_MODEL = 'seu_app.CustomMember'
|
|
284
|
+
"""
|
|
285
|
+
|
|
286
|
+
user_id = models.IntegerField()
|
|
287
|
+
organization_id = models.IntegerField()
|
|
288
|
+
metadata = models.JSONField(default=dict)
|
|
289
|
+
|
|
290
|
+
objects = SharedMemberManager()
|
|
291
|
+
|
|
292
|
+
class Meta:
|
|
293
|
+
abstract = True
|
|
294
|
+
managed = False
|
|
295
|
+
db_table = MEMBER_TABLE
|
|
296
|
+
|
|
297
|
+
def __str__(self):
|
|
298
|
+
return f"Member: User {self.user_id} - Org {self.organization_id}"
|
|
299
|
+
|
|
300
|
+
@property
|
|
301
|
+
def user(self):
|
|
302
|
+
"""
|
|
303
|
+
Acessa usuário (lazy loading)
|
|
304
|
+
|
|
305
|
+
Usage:
|
|
306
|
+
member = SharedMember.objects.first()
|
|
307
|
+
user = member.user
|
|
308
|
+
print(user.email)
|
|
309
|
+
"""
|
|
310
|
+
from .utils import get_user_model
|
|
311
|
+
|
|
312
|
+
User = get_user_model()
|
|
313
|
+
return User.objects.get_or_fail(self.user_id)
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def organization(self):
|
|
317
|
+
"""
|
|
318
|
+
Acessa organização (lazy loading)
|
|
319
|
+
|
|
320
|
+
Usage:
|
|
321
|
+
member = SharedMember.objects.first()
|
|
322
|
+
org = member.organization
|
|
323
|
+
print(org.name)
|
|
324
|
+
"""
|
|
325
|
+
from .utils import get_organization_model
|
|
326
|
+
|
|
327
|
+
Organization = get_organization_model()
|
|
328
|
+
return Organization.objects.get_or_fail(self.organization_id)
|
shared_auth/authentication.py
CHANGED
|
@@ -6,13 +6,15 @@ from django.utils.translation import gettext_lazy as _
|
|
|
6
6
|
from rest_framework import exceptions
|
|
7
7
|
from rest_framework.authentication import TokenAuthentication
|
|
8
8
|
|
|
9
|
-
from .
|
|
9
|
+
from .utils import get_token_model, get_user_model
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
class SharedTokenAuthentication(TokenAuthentication):
|
|
13
13
|
"""
|
|
14
14
|
Autentica usando tokens do banco de dados compartilhado
|
|
15
15
|
|
|
16
|
+
Usa get_token_model() e get_user_model() para suportar models customizados.
|
|
17
|
+
|
|
16
18
|
Usage em settings.py:
|
|
17
19
|
REST_FRAMEWORK = {
|
|
18
20
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
|
@@ -21,15 +23,21 @@ class SharedTokenAuthentication(TokenAuthentication):
|
|
|
21
23
|
}
|
|
22
24
|
"""
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
@property
|
|
27
|
+
def model(self):
|
|
28
|
+
"""Retorna o model de Token configurado"""
|
|
29
|
+
return get_token_model()
|
|
25
30
|
|
|
26
31
|
def authenticate_credentials(self, key):
|
|
27
32
|
"""
|
|
28
33
|
Valida o token no banco de dados compartilhado
|
|
29
34
|
"""
|
|
35
|
+
Token = get_token_model()
|
|
36
|
+
User = get_user_model()
|
|
37
|
+
|
|
30
38
|
try:
|
|
31
|
-
token =
|
|
32
|
-
except
|
|
39
|
+
token = Token.objects.get(key=key)
|
|
40
|
+
except Token.DoesNotExist:
|
|
33
41
|
raise exceptions.AuthenticationFailed(_("Token inválido."))
|
|
34
42
|
|
|
35
43
|
# Buscar usuário completo
|
shared_auth/decorators.py
CHANGED
|
@@ -6,7 +6,7 @@ from functools import wraps
|
|
|
6
6
|
|
|
7
7
|
from django.http import JsonResponse
|
|
8
8
|
|
|
9
|
-
from .
|
|
9
|
+
from .utils import get_organization_model, get_token_model, get_user_model
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def require_auth(view_func):
|
|
@@ -28,8 +28,11 @@ def require_auth(view_func):
|
|
|
28
28
|
return JsonResponse({"error": "Token não fornecido"}, status=401)
|
|
29
29
|
|
|
30
30
|
# Validar token
|
|
31
|
+
Token = get_token_model()
|
|
32
|
+
User = get_user_model()
|
|
33
|
+
|
|
31
34
|
try:
|
|
32
|
-
token_obj =
|
|
35
|
+
token_obj = Token.objects.get(key=token)
|
|
33
36
|
user = User.objects.get(pk=token_obj.user_id)
|
|
34
37
|
|
|
35
38
|
if not user.is_active or user.deleted_at is not None:
|
|
@@ -38,7 +41,7 @@ def require_auth(view_func):
|
|
|
38
41
|
request.user = user
|
|
39
42
|
request.auth = token_obj
|
|
40
43
|
|
|
41
|
-
except (
|
|
44
|
+
except (Token.DoesNotExist, User.DoesNotExist):
|
|
42
45
|
return JsonResponse({"error": "Token inválido"}, status=401)
|
|
43
46
|
|
|
44
47
|
return view_func(request, *args, **kwargs)
|
|
@@ -58,15 +61,17 @@ def require_organization(view_func):
|
|
|
58
61
|
return JsonResponse({"error": "Organização não definida"}, status=403)
|
|
59
62
|
|
|
60
63
|
# Buscar organização
|
|
64
|
+
Organization = get_organization_model()
|
|
65
|
+
|
|
61
66
|
try:
|
|
62
|
-
org =
|
|
67
|
+
org = Organization.objects.get(pk=request.organization_id)
|
|
63
68
|
|
|
64
69
|
if not org.is_active():
|
|
65
70
|
return JsonResponse({"error": "Organização inativa"}, status=403)
|
|
66
71
|
|
|
67
72
|
request.organization = org
|
|
68
73
|
|
|
69
|
-
except
|
|
74
|
+
except Organization.DoesNotExist:
|
|
70
75
|
return JsonResponse({"error": "Organização não encontrada"}, status=404)
|
|
71
76
|
|
|
72
77
|
return view_func(request, *args, **kwargs)
|
shared_auth/managers.py
CHANGED
|
@@ -95,7 +95,7 @@ class OrganizationQuerySetMixin:
|
|
|
95
95
|
Lista de objetos com _cached_organization
|
|
96
96
|
"""
|
|
97
97
|
objects = list(self.all())
|
|
98
|
-
from .
|
|
98
|
+
from .utils import get_organization_model
|
|
99
99
|
|
|
100
100
|
if not objects:
|
|
101
101
|
return objects
|
|
@@ -104,8 +104,9 @@ class OrganizationQuerySetMixin:
|
|
|
104
104
|
org_ids = set(obj.organization_id for obj in objects)
|
|
105
105
|
|
|
106
106
|
# Buscar todas de uma vez
|
|
107
|
+
Organization = get_organization_model()
|
|
107
108
|
organizations = {
|
|
108
|
-
org.pk: org for org in
|
|
109
|
+
org.pk: org for org in Organization.objects.filter(pk__in=org_ids)
|
|
109
110
|
}
|
|
110
111
|
|
|
111
112
|
# Cachear nos objetos
|
|
@@ -130,7 +131,7 @@ class UserQuerySetMixin:
|
|
|
130
131
|
"""
|
|
131
132
|
Pré-carrega dados de usuários (evita N+1)
|
|
132
133
|
"""
|
|
133
|
-
from .
|
|
134
|
+
from .utils import get_user_model
|
|
134
135
|
|
|
135
136
|
objects = list(self.all())
|
|
136
137
|
|
|
@@ -139,6 +140,7 @@ class UserQuerySetMixin:
|
|
|
139
140
|
|
|
140
141
|
user_ids = set(obj.user_id for obj in objects)
|
|
141
142
|
|
|
143
|
+
User = get_user_model()
|
|
142
144
|
users = {user.pk: user for user in User.objects.filter(pk__in=user_ids)}
|
|
143
145
|
|
|
144
146
|
for obj in objects:
|
|
@@ -154,7 +156,7 @@ class OrganizationUserQuerySetMixin(OrganizationQuerySetMixin, UserQuerySetMixin
|
|
|
154
156
|
"""
|
|
155
157
|
Pré-carrega dados de organizações E usuários (evita N+1)
|
|
156
158
|
"""
|
|
157
|
-
from .
|
|
159
|
+
from .utils import get_organization_model, get_user_model
|
|
158
160
|
|
|
159
161
|
objects = list(self.all())
|
|
160
162
|
|
|
@@ -166,8 +168,11 @@ class OrganizationUserQuerySetMixin(OrganizationQuerySetMixin, UserQuerySetMixin
|
|
|
166
168
|
user_ids = set(obj.user_id for obj in objects)
|
|
167
169
|
|
|
168
170
|
# Buscar em batch
|
|
171
|
+
Organization = get_organization_model()
|
|
172
|
+
User = get_user_model()
|
|
173
|
+
|
|
169
174
|
organizations = {
|
|
170
|
-
org.pk: org for org in
|
|
175
|
+
org.pk: org for org in Organization.objects.filter(pk__in=org_ids)
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
users = {user.pk: user for user in User.objects.filter(pk__in=user_ids)}
|
|
@@ -183,13 +188,15 @@ class OrganizationUserQuerySetMixin(OrganizationQuerySetMixin, UserQuerySetMixin
|
|
|
183
188
|
"""
|
|
184
189
|
Cria objeto com validação de organização e usuário
|
|
185
190
|
"""
|
|
186
|
-
from .
|
|
191
|
+
from .utils import get_member_model, get_organization_model
|
|
187
192
|
|
|
188
193
|
# Valida organização
|
|
189
|
-
|
|
194
|
+
Organization = get_organization_model()
|
|
195
|
+
Organization.objects.get_or_fail(organization_id)
|
|
190
196
|
|
|
191
197
|
# Valida usuário pertence à organização
|
|
192
|
-
|
|
198
|
+
Member = get_member_model()
|
|
199
|
+
if not Member.objects.filter(
|
|
193
200
|
user_id=user_id, organization_id=organization_id
|
|
194
201
|
).exists():
|
|
195
202
|
raise ValueError(
|
shared_auth/middleware.py
CHANGED
|
@@ -6,7 +6,12 @@ from django.http import JsonResponse
|
|
|
6
6
|
from django.utils.deprecation import MiddlewareMixin
|
|
7
7
|
|
|
8
8
|
from .authentication import SharedTokenAuthentication
|
|
9
|
-
from .
|
|
9
|
+
from .utils import (
|
|
10
|
+
get_member_model,
|
|
11
|
+
get_organization_model,
|
|
12
|
+
get_token_model,
|
|
13
|
+
get_user_model,
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
class SharedAuthMiddleware(MiddlewareMixin):
|
|
@@ -50,8 +55,11 @@ class SharedAuthMiddleware(MiddlewareMixin):
|
|
|
50
55
|
return None
|
|
51
56
|
|
|
52
57
|
# Validar token e buscar usuário
|
|
58
|
+
Token = get_token_model()
|
|
59
|
+
User = get_user_model()
|
|
60
|
+
|
|
53
61
|
try:
|
|
54
|
-
token_obj =
|
|
62
|
+
token_obj = Token.objects.get(key=token)
|
|
55
63
|
user = User.objects.get(pk=token_obj.user_id)
|
|
56
64
|
|
|
57
65
|
if not user.is_active or user.deleted_at is not None:
|
|
@@ -63,7 +71,7 @@ class SharedAuthMiddleware(MiddlewareMixin):
|
|
|
63
71
|
# request.user = user
|
|
64
72
|
request.auth = token_obj
|
|
65
73
|
|
|
66
|
-
except (
|
|
74
|
+
except (Token.DoesNotExist, User.DoesNotExist):
|
|
67
75
|
# request.user = None
|
|
68
76
|
request.auth = None
|
|
69
77
|
|
|
@@ -159,9 +167,8 @@ class OrganizationMiddleware(MiddlewareMixin):
|
|
|
159
167
|
return
|
|
160
168
|
|
|
161
169
|
request.organization_id = organization_id
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
).first()
|
|
170
|
+
Organization = get_organization_model()
|
|
171
|
+
request.organization = Organization.objects.filter(pk=organization_id).first()
|
|
165
172
|
|
|
166
173
|
@staticmethod
|
|
167
174
|
def _authenticate_user(request):
|
|
@@ -194,7 +201,8 @@ class OrganizationMiddleware(MiddlewareMixin):
|
|
|
194
201
|
return None
|
|
195
202
|
|
|
196
203
|
# Buscar a primeira organização que o usuário pertence
|
|
197
|
-
|
|
204
|
+
Member = get_member_model()
|
|
205
|
+
member = Member.objects.filter(user_id=request.user.pk).first()
|
|
198
206
|
|
|
199
207
|
return member.organization_id if member else None
|
|
200
208
|
|
|
@@ -210,6 +218,8 @@ class OrganizationMiddleware(MiddlewareMixin):
|
|
|
210
218
|
|
|
211
219
|
|
|
212
220
|
def get_member(user_id, organization_id):
|
|
213
|
-
|
|
221
|
+
"""Busca membro usando o model configurado"""
|
|
222
|
+
Member = get_member_model()
|
|
223
|
+
return Member.objects.filter(
|
|
214
224
|
user_id=user_id, organization_id=organization_id
|
|
215
225
|
).first()
|
shared_auth/mixins.py
CHANGED
|
@@ -44,9 +44,10 @@ class OrganizationMixin(models.Model):
|
|
|
44
44
|
Acessa organização do banco de auth (lazy loading com cache)
|
|
45
45
|
"""
|
|
46
46
|
if not hasattr(self, "_cached_organization"):
|
|
47
|
-
from shared_auth.
|
|
47
|
+
from shared_auth.utils import get_organization_model
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
Organization = get_organization_model()
|
|
50
|
+
self._cached_organization = Organization.objects.get_or_fail(
|
|
50
51
|
self.organization_id
|
|
51
52
|
)
|
|
52
53
|
return self._cached_organization
|
|
@@ -69,7 +70,7 @@ class OrganizationMixin(models.Model):
|
|
|
69
70
|
"""Retorna nome da organização (safe)"""
|
|
70
71
|
try:
|
|
71
72
|
return self.organization.name
|
|
72
|
-
except:
|
|
73
|
+
except Exception:
|
|
73
74
|
return None
|
|
74
75
|
|
|
75
76
|
|
|
@@ -108,8 +109,9 @@ class UserMixin(models.Model):
|
|
|
108
109
|
Acessa usuário do banco de auth (lazy loading com cache)
|
|
109
110
|
"""
|
|
110
111
|
if not hasattr(self, "_cached_user"):
|
|
111
|
-
from shared_auth.
|
|
112
|
+
from shared_auth.utils import get_user_model
|
|
112
113
|
|
|
114
|
+
User = get_user_model()
|
|
113
115
|
self._cached_user = User.objects.get_or_fail(self.user_id)
|
|
114
116
|
return self._cached_user
|
|
115
117
|
|
|
@@ -118,7 +120,7 @@ class UserMixin(models.Model):
|
|
|
118
120
|
"""Retorna email do usuário (safe)"""
|
|
119
121
|
try:
|
|
120
122
|
return self.user.email
|
|
121
|
-
except:
|
|
123
|
+
except Exception:
|
|
122
124
|
return None
|
|
123
125
|
|
|
124
126
|
@property
|
|
@@ -126,7 +128,7 @@ class UserMixin(models.Model):
|
|
|
126
128
|
"""Retorna nome completo do usuário (safe)"""
|
|
127
129
|
try:
|
|
128
130
|
return self.user.get_full_name()
|
|
129
|
-
except:
|
|
131
|
+
except Exception:
|
|
130
132
|
return None
|
|
131
133
|
|
|
132
134
|
@property
|
|
@@ -138,7 +140,7 @@ class UserMixin(models.Model):
|
|
|
138
140
|
"""Verifica se o usuário está ativo"""
|
|
139
141
|
try:
|
|
140
142
|
return self.user.is_active and self.user.deleted_at is None
|
|
141
|
-
except:
|
|
143
|
+
except Exception:
|
|
142
144
|
return False
|
|
143
145
|
|
|
144
146
|
|
|
@@ -171,9 +173,10 @@ class OrganizationUserMixin(OrganizationMixin, UserMixin):
|
|
|
171
173
|
Returns:
|
|
172
174
|
bool: True se pertence, False caso contrário
|
|
173
175
|
"""
|
|
174
|
-
from shared_auth.
|
|
176
|
+
from shared_auth.utils import get_member_model
|
|
175
177
|
|
|
176
|
-
|
|
178
|
+
Member = get_member_model()
|
|
179
|
+
return Member.objects.filter(
|
|
177
180
|
user_id=self.user_id, organization_id=self.organization_id
|
|
178
181
|
).exists()
|
|
179
182
|
|
|
@@ -182,9 +185,10 @@ class OrganizationUserMixin(OrganizationMixin, UserMixin):
|
|
|
182
185
|
Verifica se um usuário pode acessar este registro
|
|
183
186
|
(se pertence à mesma organização)
|
|
184
187
|
"""
|
|
185
|
-
from shared_auth.
|
|
188
|
+
from shared_auth.utils import get_member_model
|
|
186
189
|
|
|
187
|
-
|
|
190
|
+
Member = get_member_model()
|
|
191
|
+
return Member.objects.filter(
|
|
188
192
|
user_id=user_id, organization_id=self.organization_id
|
|
189
193
|
).exists()
|
|
190
194
|
|
shared_auth/models.py
CHANGED
|
@@ -1,235 +1,58 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Models READ-ONLY para acesso aos dados de autenticação
|
|
3
3
|
ATENÇÃO: Estes models NÃO devem ser usados para criar migrations
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
4
|
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Para customizar estes models, herde dos models abstratos em shared_auth.abstract_models
|
|
6
|
+
e configure no settings.py. Veja a documentação em abstract_models.py
|
|
7
|
+
"""
|
|
10
8
|
|
|
11
|
-
from .
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
from .abstract_models import (
|
|
10
|
+
AbstractSharedMember,
|
|
11
|
+
AbstractSharedOrganization,
|
|
12
|
+
AbstractSharedToken,
|
|
13
|
+
AbstractUser,
|
|
14
|
+
)
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
class SharedToken(
|
|
17
|
+
class SharedToken(AbstractSharedToken):
|
|
18
18
|
"""
|
|
19
|
-
Model READ-ONLY da tabela authtoken_token
|
|
20
|
-
|
|
19
|
+
Model READ-ONLY padrão da tabela authtoken_token
|
|
20
|
+
|
|
21
|
+
Para customizar, crie seu próprio model herdando de AbstractSharedToken
|
|
21
22
|
"""
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
created = models.DateTimeField()
|
|
26
|
-
|
|
27
|
-
objects = models.Manager()
|
|
28
|
-
|
|
29
|
-
class Meta:
|
|
30
|
-
managed = False
|
|
31
|
-
db_table = TOKEN_TABLE
|
|
32
|
-
|
|
33
|
-
def __str__(self):
|
|
34
|
-
return self.key
|
|
35
|
-
|
|
36
|
-
@property
|
|
37
|
-
def user(self):
|
|
38
|
-
"""Acessa usuário do token"""
|
|
39
|
-
if not hasattr(self, "_cached_user"):
|
|
40
|
-
self._cached_user = User.objects.get_or_fail(self.user_id)
|
|
41
|
-
return self._cached_user
|
|
42
|
-
|
|
43
|
-
def is_valid(self):
|
|
44
|
-
"""Verifica se token ainda é válido"""
|
|
45
|
-
# Implementar lógica de expiração se necessário
|
|
46
|
-
return True
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def organization_image_path(instance, filename):
|
|
50
|
-
return os.path.join(
|
|
51
|
-
"organization",
|
|
52
|
-
str(instance.pk),
|
|
53
|
-
"images",
|
|
54
|
-
filename,
|
|
55
|
-
)
|
|
23
|
+
|
|
24
|
+
class Meta(AbstractSharedToken.Meta):
|
|
25
|
+
pass
|
|
56
26
|
|
|
57
27
|
|
|
58
|
-
class SharedOrganization(
|
|
28
|
+
class SharedOrganization(AbstractSharedOrganization):
|
|
59
29
|
"""
|
|
60
|
-
Model READ-ONLY da tabela organization
|
|
61
|
-
|
|
30
|
+
Model READ-ONLY padrão da tabela organization
|
|
31
|
+
|
|
32
|
+
Para customizar, crie seu próprio model herdando de AbstractSharedOrganization
|
|
62
33
|
"""
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
fantasy_name = models.CharField(max_length=255, blank=True, null=True)
|
|
67
|
-
cnpj = models.CharField(max_length=255, blank=True, null=True)
|
|
68
|
-
telephone = models.CharField(max_length=50, blank=True, null=True)
|
|
69
|
-
cellphone = models.CharField(max_length=50, blank=True, null=True)
|
|
70
|
-
email = models.EmailField(blank=True, null=True)
|
|
71
|
-
image_organization = models.ImageField(
|
|
72
|
-
storage=Storage, upload_to=organization_image_path, null=True
|
|
73
|
-
)
|
|
74
|
-
|
|
75
|
-
# Relacionamentos
|
|
76
|
-
main_organization_id = models.IntegerField(null=True, blank=True)
|
|
77
|
-
is_branch = models.BooleanField(default=False)
|
|
78
|
-
|
|
79
|
-
# Metadados
|
|
80
|
-
created_at = models.DateTimeField()
|
|
81
|
-
updated_at = models.DateTimeField()
|
|
82
|
-
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
83
|
-
|
|
84
|
-
objects = SharedOrganizationManager()
|
|
85
|
-
|
|
86
|
-
class Meta:
|
|
87
|
-
managed = False # CRITICAL: Não gera migrations
|
|
88
|
-
db_table = ORGANIZATION_TABLE
|
|
89
|
-
|
|
90
|
-
def __str__(self):
|
|
91
|
-
return self.fantasy_name or self.name or f"Org #{self.pk}"
|
|
92
|
-
|
|
93
|
-
@property
|
|
94
|
-
def main_organization(self):
|
|
95
|
-
"""
|
|
96
|
-
Acessa organização principal (lazy loading)
|
|
97
|
-
|
|
98
|
-
Usage:
|
|
99
|
-
if org.is_branch:
|
|
100
|
-
main = org.main_organization
|
|
101
|
-
"""
|
|
102
|
-
if self.main_organization_id:
|
|
103
|
-
return SharedOrganization.objects.get_or_fail(self.main_organization_id)
|
|
104
|
-
return None
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def branches(self):
|
|
108
|
-
"""
|
|
109
|
-
Retorna filiais desta organização
|
|
110
|
-
|
|
111
|
-
Usage:
|
|
112
|
-
branches = org.branches
|
|
113
|
-
"""
|
|
114
|
-
return SharedOrganization.objects.filter(main_organization_id=self.pk)
|
|
115
|
-
|
|
116
|
-
@property
|
|
117
|
-
def members(self):
|
|
118
|
-
"""
|
|
119
|
-
Retorna membros desta organização
|
|
120
|
-
|
|
121
|
-
Usage:
|
|
122
|
-
members = org.members
|
|
123
|
-
for member in members:
|
|
124
|
-
print(member.user.email)
|
|
125
|
-
"""
|
|
126
|
-
return SharedMember.objects.for_organization(self.pk)
|
|
127
|
-
|
|
128
|
-
@property
|
|
129
|
-
def users(self):
|
|
130
|
-
"""
|
|
131
|
-
Retorna usuários desta organização
|
|
132
|
-
|
|
133
|
-
Usage:
|
|
134
|
-
users = org.users
|
|
135
|
-
"""
|
|
136
|
-
return User.objects.filter(
|
|
137
|
-
id__in=self.members.values_list("user_id", flat=True)
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
def is_active(self):
|
|
141
|
-
"""Verifica se organização está ativa"""
|
|
142
|
-
return self.deleted_at is None
|
|
34
|
+
|
|
35
|
+
class Meta(AbstractSharedOrganization.Meta):
|
|
36
|
+
pass
|
|
143
37
|
|
|
144
38
|
|
|
145
39
|
class User(AbstractUser):
|
|
146
40
|
"""
|
|
147
|
-
Model READ-ONLY da tabela auth_user
|
|
41
|
+
Model READ-ONLY padrão da tabela auth_user
|
|
42
|
+
|
|
43
|
+
Para customizar, crie seu próprio model herdando de AbstractUser
|
|
148
44
|
"""
|
|
45
|
+
|
|
46
|
+
class Meta(AbstractUser.Meta):
|
|
47
|
+
pass
|
|
149
48
|
|
|
150
|
-
date_joined = models.DateTimeField()
|
|
151
|
-
last_login = models.DateTimeField(null=True, blank=True)
|
|
152
|
-
avatar = models.ImageField(storage=Storage, blank=True, null=True)
|
|
153
|
-
# Campos customizados
|
|
154
|
-
createdat = models.DateTimeField()
|
|
155
|
-
updatedat = models.DateTimeField()
|
|
156
|
-
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
157
|
-
|
|
158
|
-
objects = UserManager()
|
|
159
|
-
|
|
160
|
-
class Meta:
|
|
161
|
-
managed = False
|
|
162
|
-
db_table = USER_TABLE
|
|
163
|
-
|
|
164
|
-
@property
|
|
165
|
-
def organizations(self):
|
|
166
|
-
"""
|
|
167
|
-
Retorna todas as organizações associadas ao usuário.
|
|
168
|
-
"""
|
|
169
|
-
return SharedOrganization.objects.filter(
|
|
170
|
-
id__in=SharedMember.objects.filter(user_id=self.id).values_list(
|
|
171
|
-
"organization_id", flat=True
|
|
172
|
-
)
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
def get_org(self, organization_id):
|
|
176
|
-
"""
|
|
177
|
-
Retorna a organização especificada pelo ID, se o usuário for membro.
|
|
178
|
-
"""
|
|
179
|
-
try:
|
|
180
|
-
organization = SharedOrganization.objects.get(id=organization_id)
|
|
181
|
-
except SharedOrganization.DoesNotExist:
|
|
182
|
-
raise OrganizationNotFoundError(
|
|
183
|
-
f"Organização com ID {organization_id} não encontrada."
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
if not SharedMember.objects.filter(
|
|
187
|
-
user_id=self.id, organization_id=organization.id
|
|
188
|
-
).exists():
|
|
189
|
-
raise OrganizationNotFoundError("Usuário não é membro desta organização.")
|
|
190
49
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
class SharedMember(models.Model):
|
|
50
|
+
class SharedMember(AbstractSharedMember):
|
|
195
51
|
"""
|
|
196
|
-
Model READ-ONLY da tabela organization_member
|
|
197
|
-
|
|
52
|
+
Model READ-ONLY padrão da tabela organization_member
|
|
53
|
+
|
|
54
|
+
Para customizar, crie seu próprio model herdando de AbstractSharedMember
|
|
198
55
|
"""
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
metadata = models.JSONField(default=dict)
|
|
203
|
-
|
|
204
|
-
objects = SharedMemberManager()
|
|
205
|
-
|
|
206
|
-
class Meta:
|
|
207
|
-
managed = False
|
|
208
|
-
db_table = MEMBER_TABLE
|
|
209
|
-
|
|
210
|
-
def __str__(self):
|
|
211
|
-
return f"Member: User {self.user_id} - Org {self.organization_id}"
|
|
212
|
-
|
|
213
|
-
@property
|
|
214
|
-
def user(self):
|
|
215
|
-
"""
|
|
216
|
-
Acessa usuário (lazy loading)
|
|
217
|
-
|
|
218
|
-
Usage:
|
|
219
|
-
member = SharedMember.objects.first()
|
|
220
|
-
user = member.user
|
|
221
|
-
print(user.email)
|
|
222
|
-
"""
|
|
223
|
-
return User.objects.get_or_fail(self.user_id)
|
|
224
|
-
|
|
225
|
-
@property
|
|
226
|
-
def organization(self):
|
|
227
|
-
"""
|
|
228
|
-
Acessa organização (lazy loading)
|
|
229
|
-
|
|
230
|
-
Usage:
|
|
231
|
-
member = SharedMember.objects.first()
|
|
232
|
-
org = member.organization
|
|
233
|
-
print(org.name)
|
|
234
|
-
"""
|
|
235
|
-
return SharedOrganization.objects.get_or_fail(self.organization_id)
|
|
56
|
+
|
|
57
|
+
class Meta(AbstractSharedMember.Meta):
|
|
58
|
+
pass
|
shared_auth/permissions.py
CHANGED
|
@@ -5,6 +5,7 @@ Permissões customizadas para DRF
|
|
|
5
5
|
from rest_framework import permissions
|
|
6
6
|
|
|
7
7
|
from shared_auth.middleware import get_member
|
|
8
|
+
from shared_auth.utils import get_organization_model
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class IsAuthenticated(permissions.BasePermission):
|
|
@@ -35,12 +36,12 @@ class HasActiveOrganization(permissions.BasePermission):
|
|
|
35
36
|
return False
|
|
36
37
|
|
|
37
38
|
# Verificar se organização está ativa
|
|
38
|
-
|
|
39
|
+
Organization = get_organization_model()
|
|
39
40
|
|
|
40
41
|
try:
|
|
41
|
-
org =
|
|
42
|
+
org = Organization.objects.get(pk=request.organization_id)
|
|
42
43
|
return org.is_active()
|
|
43
|
-
except
|
|
44
|
+
except Organization.DoesNotExist:
|
|
44
45
|
return False
|
|
45
46
|
|
|
46
47
|
|
shared_auth/router.py
CHANGED
|
@@ -3,7 +3,7 @@ class SharedAuthRouter:
|
|
|
3
3
|
Direciona queries dos models compartilhados para o banco correto
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
|
-
route_app_labels = {"shared_auth", "auth", "admin"}
|
|
6
|
+
route_app_labels = {"shared_auth", "auth", "admin", "contenttypes"}
|
|
7
7
|
|
|
8
8
|
def db_for_read(self, model, **hints):
|
|
9
9
|
if model._meta.app_label in self.route_app_labels:
|
shared_auth/serializers.py
CHANGED
|
@@ -13,7 +13,7 @@ Se quiser usar separadamente:
|
|
|
13
13
|
|
|
14
14
|
from rest_framework import serializers
|
|
15
15
|
|
|
16
|
-
from .
|
|
16
|
+
from .utils import get_organization_model, get_user_model
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class OrganizationCreateSerializerMixin(serializers.ModelSerializer):
|
|
@@ -28,6 +28,8 @@ class OrganizationCreateSerializerMixin(serializers.ModelSerializer):
|
|
|
28
28
|
fields = ['id', 'titulo', 'conteudo']
|
|
29
29
|
"""
|
|
30
30
|
|
|
31
|
+
organization_id = serializers.IntegerField(required=False)
|
|
32
|
+
|
|
31
33
|
def create(self, validated_data):
|
|
32
34
|
"""Automatically set organization_id from request context"""
|
|
33
35
|
if self.context.get("request") and hasattr(
|
|
@@ -116,8 +118,17 @@ class OrganizationListCreateSerializerMixin(
|
|
|
116
118
|
|
|
117
119
|
|
|
118
120
|
class OrganizationSerializer(serializers.ModelSerializer):
|
|
121
|
+
"""
|
|
122
|
+
Serializer para o model de Organization configurado.
|
|
123
|
+
Usa get_organization_model() para suportar models customizados.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def __init__(self, *args, **kwargs):
|
|
127
|
+
super().__init__(*args, **kwargs)
|
|
128
|
+
self.Meta.model = get_organization_model()
|
|
129
|
+
|
|
119
130
|
class Meta:
|
|
120
|
-
model =
|
|
131
|
+
model = None # Será definido dinamicamente no __init__
|
|
121
132
|
fields = "__all__"
|
|
122
133
|
|
|
123
134
|
|
|
@@ -133,6 +144,8 @@ class UserCreateSerializerMixin(serializers.ModelSerializer):
|
|
|
133
144
|
fields = ['id', 'titulo', 'conteudo']
|
|
134
145
|
"""
|
|
135
146
|
|
|
147
|
+
user_id = serializers.IntegerField(required=False)
|
|
148
|
+
|
|
136
149
|
def create(self, validated_data):
|
|
137
150
|
"""Automatically set user_id from request context"""
|
|
138
151
|
if self.context.get("request") and hasattr(self.context["request"], "user"):
|
|
@@ -182,7 +195,7 @@ class UserSerializerMixin(serializers.ModelSerializer):
|
|
|
182
195
|
def get_user(self, obj):
|
|
183
196
|
"""Retorna dados do usuário como objeto"""
|
|
184
197
|
try:
|
|
185
|
-
user
|
|
198
|
+
user = obj.user
|
|
186
199
|
return {
|
|
187
200
|
"id": user.pk,
|
|
188
201
|
"username": user.username,
|
|
@@ -213,8 +226,17 @@ class UserListCreateSerializerMixin(UserSerializerMixin, UserCreateSerializerMix
|
|
|
213
226
|
|
|
214
227
|
|
|
215
228
|
class UserSerializer(serializers.ModelSerializer):
|
|
229
|
+
"""
|
|
230
|
+
Serializer para o model de User configurado.
|
|
231
|
+
Usa get_user_model() para suportar models customizados.
|
|
232
|
+
"""
|
|
233
|
+
|
|
234
|
+
def __init__(self, *args, **kwargs):
|
|
235
|
+
super().__init__(*args, **kwargs)
|
|
236
|
+
self.Meta.model = get_user_model()
|
|
237
|
+
|
|
216
238
|
class Meta:
|
|
217
|
-
model =
|
|
239
|
+
model = None # Será definido dinamicamente no __init__
|
|
218
240
|
fields = [
|
|
219
241
|
"id",
|
|
220
242
|
"username",
|
|
@@ -241,6 +263,9 @@ class OrganizationUserCreateSerializerMixin(serializers.ModelSerializer):
|
|
|
241
263
|
fields = ['id', 'titulo', 'conteudo']
|
|
242
264
|
"""
|
|
243
265
|
|
|
266
|
+
organization_id = serializers.IntegerField(required=False)
|
|
267
|
+
user_id = serializers.IntegerField(required=False)
|
|
268
|
+
|
|
244
269
|
def create(self, validated_data):
|
|
245
270
|
"""Automatically set both organization_id and user_id from request context"""
|
|
246
271
|
if self.context.get("request"):
|
shared_auth/utils.py
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utilitários para obter os models configurados
|
|
3
|
+
Similar ao get_user_model() do Django
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from django.apps import apps
|
|
7
|
+
from django.core.exceptions import ImproperlyConfigured
|
|
8
|
+
|
|
9
|
+
from .conf import get_setting
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_token_model():
|
|
13
|
+
"""
|
|
14
|
+
Retorna o model de Token configurado ou o padrão.
|
|
15
|
+
|
|
16
|
+
Usage:
|
|
17
|
+
from shared_auth.utils import get_token_model
|
|
18
|
+
|
|
19
|
+
Token = get_token_model()
|
|
20
|
+
token = Token.objects.get(key='abc123')
|
|
21
|
+
"""
|
|
22
|
+
model_string = get_setting('SHARED_AUTH_TOKEN_MODEL', 'shared_auth.SharedToken')
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
return apps.get_model(model_string, require_ready=False)
|
|
26
|
+
except ValueError:
|
|
27
|
+
raise ImproperlyConfigured(
|
|
28
|
+
f"SHARED_AUTH_TOKEN_MODEL deve estar no formato 'app_label.model_name'. "
|
|
29
|
+
f"Recebido: '{model_string}'"
|
|
30
|
+
)
|
|
31
|
+
except LookupError:
|
|
32
|
+
raise ImproperlyConfigured(
|
|
33
|
+
f"SHARED_AUTH_TOKEN_MODEL refere-se ao model '{model_string}' "
|
|
34
|
+
f"que não foi instalado ou é inválido."
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_organization_model():
|
|
39
|
+
"""
|
|
40
|
+
Retorna o model de Organization configurado ou o padrão.
|
|
41
|
+
|
|
42
|
+
Usage:
|
|
43
|
+
from shared_auth.utils import get_organization_model
|
|
44
|
+
|
|
45
|
+
Organization = get_organization_model()
|
|
46
|
+
org = Organization.objects.get(id=1)
|
|
47
|
+
"""
|
|
48
|
+
model_string = get_setting('SHARED_AUTH_ORGANIZATION_MODEL', 'shared_auth.SharedOrganization')
|
|
49
|
+
|
|
50
|
+
try:
|
|
51
|
+
return apps.get_model(model_string, require_ready=False)
|
|
52
|
+
except ValueError:
|
|
53
|
+
raise ImproperlyConfigured(
|
|
54
|
+
f"SHARED_AUTH_ORGANIZATION_MODEL deve estar no formato 'app_label.model_name'. "
|
|
55
|
+
f"Recebido: '{model_string}'"
|
|
56
|
+
)
|
|
57
|
+
except LookupError:
|
|
58
|
+
raise ImproperlyConfigured(
|
|
59
|
+
f"SHARED_AUTH_ORGANIZATION_MODEL refere-se ao model '{model_string}' "
|
|
60
|
+
f"que não foi instalado ou é inválido."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_user_model():
|
|
65
|
+
"""
|
|
66
|
+
Retorna o model de User configurado ou o padrão.
|
|
67
|
+
|
|
68
|
+
Usage:
|
|
69
|
+
from shared_auth.utils import get_user_model
|
|
70
|
+
|
|
71
|
+
User = get_user_model()
|
|
72
|
+
user = User.objects.get(id=1)
|
|
73
|
+
"""
|
|
74
|
+
model_string = get_setting('SHARED_AUTH_USER_MODEL', 'shared_auth.User')
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
return apps.get_model(model_string, require_ready=False)
|
|
78
|
+
except ValueError:
|
|
79
|
+
raise ImproperlyConfigured(
|
|
80
|
+
f"SHARED_AUTH_USER_MODEL deve estar no formato 'app_label.model_name'. "
|
|
81
|
+
f"Recebido: '{model_string}'"
|
|
82
|
+
)
|
|
83
|
+
except LookupError:
|
|
84
|
+
raise ImproperlyConfigured(
|
|
85
|
+
f"SHARED_AUTH_USER_MODEL refere-se ao model '{model_string}' "
|
|
86
|
+
f"que não foi instalado ou é inválido."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def get_member_model():
|
|
91
|
+
"""
|
|
92
|
+
Retorna o model de Member configurado ou o padrão.
|
|
93
|
+
|
|
94
|
+
Usage:
|
|
95
|
+
from shared_auth.utils import get_member_model
|
|
96
|
+
|
|
97
|
+
Member = get_member_model()
|
|
98
|
+
member = Member.objects.get(id=1)
|
|
99
|
+
"""
|
|
100
|
+
model_string = get_setting('SHARED_AUTH_MEMBER_MODEL', 'shared_auth.SharedMember')
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
return apps.get_model(model_string, require_ready=False)
|
|
104
|
+
except ValueError:
|
|
105
|
+
raise ImproperlyConfigured(
|
|
106
|
+
f"SHARED_AUTH_MEMBER_MODEL deve estar no formato 'app_label.model_name'. "
|
|
107
|
+
f"Recebido: '{model_string}'"
|
|
108
|
+
)
|
|
109
|
+
except LookupError:
|
|
110
|
+
raise ImproperlyConfigured(
|
|
111
|
+
f"SHARED_AUTH_MEMBER_MODEL refere-se ao model '{model_string}' "
|
|
112
|
+
f"que não foi instalado ou é inválido."
|
|
113
|
+
)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
shared_auth/__init__.py,sha256=Ta8lIbyn22J6wU-TgWeSvdDHS4NBRf4Sv1Ag8VtY-bE,152
|
|
2
|
-
shared_auth/app.py,sha256=EHoLKlpW41o6ZxcH184aMhnWQxkVp9fH2_-89RjMz-4,215
|
|
3
|
-
shared_auth/authentication.py,sha256=AidtDV4zBWkezN2H6iyOiRNrBoWGgcYiXQU1WkhfUIo,1409
|
|
4
|
-
shared_auth/conf.py,sha256=WlSXQB7p3BfE3BL6WR6EDYpCHQEjDlxQlyf8dTfClsk,621
|
|
5
|
-
shared_auth/decorators.py,sha256=RT-Qfi7oGBo6PvWJRR1dqJUQdU6ZOf9p-8mV3rZmqQ0,3237
|
|
6
|
-
shared_auth/exceptions.py,sha256=VKamHjBl2yjXG2RsMrLrXru1_Q9IJXmy4xmDcXlpWsU,627
|
|
7
|
-
shared_auth/fields.py,sha256=RAcmFh1D_nkbai_7t_OrPZhfhAipesy5kKnEj4LUvvM,1254
|
|
8
|
-
shared_auth/managers.py,sha256=NpIDMY7BWl1pKZKCepuNDxz7eekEUhKkOUYsWGbwyos,6715
|
|
9
|
-
shared_auth/middleware.py,sha256=72GF8kGijbhw98v2Q-1sXBXk-7Bamfyvypm9h8xLX_I,6112
|
|
10
|
-
shared_auth/mixins.py,sha256=BSYNTWYjLRGuxfy7x-uAaNZmrTMsm67ogAQzjrU2DoQ,7388
|
|
11
|
-
shared_auth/models.py,sha256=QMY_Oyr4x7AtSNAFV3UfoJY0YBZwFKePvfVNWt-vQTc,6562
|
|
12
|
-
shared_auth/permissions.py,sha256=FNIp12ePOUlXVp26zNMAyEtzX9kwyP7RuNIgaaCXtPA,2671
|
|
13
|
-
shared_auth/router.py,sha256=zYidJ7j40lQLrhkCtAQAp-rQLhua_UF0X7SDzYRcV5w,668
|
|
14
|
-
shared_auth/serializers.py,sha256=5jmyoyAAMCeMqxuVyw-tGPI8MCLfPslG94cdRL19B3Y,13374
|
|
15
|
-
shared_auth/storage_backend.py,sha256=Eqkjz8aF5UrOpRwYl-J0Td95IObfxnJ8eLQDJVFM3Io,184
|
|
16
|
-
shared_auth/urls.py,sha256=591wWEWJPaHEGkcOZ8yvfgxddRyOcZLgOc0vNtF7XRI,289
|
|
17
|
-
shared_auth/views.py,sha256=2hyLnYSWUscfq-jVcskt-ukzDt4vg6IXeKjRDRu9RXk,1519
|
|
18
|
-
maquinaweb_shared_auth-0.2.32.dist-info/METADATA,sha256=lIGuKuLhYkiFs9jDv-Kr6vlxW0N6tTciSdoZngCwRvA,26325
|
|
19
|
-
maquinaweb_shared_auth-0.2.32.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
20
|
-
maquinaweb_shared_auth-0.2.32.dist-info/top_level.txt,sha256=msyYRy02ZV7zz7GR1raUI5LXGFIFn2TIkgkeKZqKufE,12
|
|
21
|
-
maquinaweb_shared_auth-0.2.32.dist-info/RECORD,,
|
|
File without changes
|
{maquinaweb_shared_auth-0.2.32.dist-info → maquinaweb_shared_auth-0.2.34.dist-info}/top_level.txt
RENAMED
|
File without changes
|