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.

@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: maquinaweb-shared-auth
3
- Version: 0.2.32
3
+ Version: 0.2.34
4
4
  Summary: Models read-only para autenticação compartilhada entre projetos Django.
5
5
  Author-email: Seu Nome <seuemail@dominio.com>
6
6
  License: MIT
@@ -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)
@@ -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 .models import SharedToken, User
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
- model = SharedToken
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 = SharedToken.objects.get(key=key)
32
- except SharedToken.DoesNotExist:
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 .models import SharedOrganization, SharedToken, User
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 = SharedToken.objects.get(key=token)
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 (SharedToken.DoesNotExist, User.DoesNotExist):
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 = SharedOrganization.objects.get(pk=request.organization_id)
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 SharedOrganization.DoesNotExist:
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 .models import SharedOrganization
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 SharedOrganization.objects.filter(pk__in=org_ids)
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 .models import User
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 .models import SharedOrganization, User
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 SharedOrganization.objects.filter(pk__in=org_ids)
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 .models import SharedMember, SharedOrganization
191
+ from .utils import get_member_model, get_organization_model
187
192
 
188
193
  # Valida organização
189
- SharedOrganization.objects.get_or_fail(organization_id)
194
+ Organization = get_organization_model()
195
+ Organization.objects.get_or_fail(organization_id)
190
196
 
191
197
  # Valida usuário pertence à organização
192
- if not SharedMember.objects.filter(
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 .models import SharedMember, SharedOrganization, SharedToken, User
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 = SharedToken.objects.get(key=token)
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 (SharedToken.DoesNotExist, User.DoesNotExist):
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
- request.organization = SharedOrganization.objects.filter(
163
- pk=organization_id
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
- member = SharedMember.objects.filter(user_id=request.user.pk).first()
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
- return SharedMember.objects.filter(
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.models import SharedOrganization
47
+ from shared_auth.utils import get_organization_model
48
48
 
49
- self._cached_organization = SharedOrganization.objects.get_or_fail(
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.models import User
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.models import SharedMember
176
+ from shared_auth.utils import get_member_model
175
177
 
176
- return SharedMember.objects.filter(
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.models import SharedMember
188
+ from shared_auth.utils import get_member_model
186
189
 
187
- return SharedMember.objects.filter(
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
- from django.contrib.auth.models import AbstractUser
9
- from django.db import models
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 .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
9
+ from .abstract_models import (
10
+ AbstractSharedMember,
11
+ AbstractSharedOrganization,
12
+ AbstractSharedToken,
13
+ AbstractUser,
14
+ )
15
15
 
16
16
 
17
- class SharedToken(models.Model):
17
+ class SharedToken(AbstractSharedToken):
18
18
  """
19
- Model READ-ONLY da tabela authtoken_token
20
- Usado para validar tokens em outros sistemas
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
- key = models.CharField(max_length=40, primary_key=True)
24
- user_id = models.IntegerField()
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(models.Model):
28
+ class SharedOrganization(AbstractSharedOrganization):
59
29
  """
60
- Model READ-ONLY da tabela organization
61
- Usado para acessar dados de organizações em outros sistemas
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
- # Campos principais
65
- name = models.CharField(max_length=255)
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
- return organization
192
-
193
-
194
- class SharedMember(models.Model):
50
+ class SharedMember(AbstractSharedMember):
195
51
  """
196
- Model READ-ONLY da tabela organization_member
197
- Relacionamento entre User e Organization
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
- user_id = models.IntegerField()
201
- organization_id = models.IntegerField()
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
@@ -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
- from .models import SharedOrganization
39
+ Organization = get_organization_model()
39
40
 
40
41
  try:
41
- org = SharedOrganization.objects.get(pk=request.organization_id)
42
+ org = Organization.objects.get(pk=request.organization_id)
42
43
  return org.is_active()
43
- except SharedOrganization.DoesNotExist:
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:
@@ -13,7 +13,7 @@ Se quiser usar separadamente:
13
13
 
14
14
  from rest_framework import serializers
15
15
 
16
- from .models import SharedOrganization, User
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 = SharedOrganization
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: User = obj.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 = User
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,,