maquinaweb-shared-auth 0.1.5__tar.gz → 0.1.8__tar.gz
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.1.5 → maquinaweb_shared_auth-0.1.8}/PKG-INFO +1 -1
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/maquinaweb_shared_auth.egg-info/PKG-INFO +1 -1
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/pyproject.toml +5 -11
- maquinaweb_shared_auth-0.1.8/shared_auth/__init__.py +6 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/app.py +4 -1
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/decorators.py +5 -8
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/managers.py +2 -1
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/middleware.py +32 -18
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/models.py +5 -56
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/permissions.py +15 -9
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/router.py +1 -1
- maquinaweb_shared_auth-0.1.5/shared_auth/__init__.py +0 -21
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/README.md +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/maquinaweb_shared_auth.egg-info/SOURCES.txt +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/maquinaweb_shared_auth.egg-info/dependency_links.txt +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/maquinaweb_shared_auth.egg-info/requires.txt +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/maquinaweb_shared_auth.egg-info/top_level.txt +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/setup.cfg +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/setup.py +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/authentication.py +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/conf.py +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/exceptions.py +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/fields.py +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/mixins.py +0 -0
- {maquinaweb_shared_auth-0.1.5 → maquinaweb_shared_auth-0.1.8}/shared_auth/serializers.py +0 -0
|
@@ -4,14 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "maquinaweb-shared-auth"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.8"
|
|
8
8
|
description = "Models read-only para autenticação compartilhada entre projetos Django."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.13"
|
|
11
|
-
license = {text = "MIT"}
|
|
12
|
-
authors = [
|
|
13
|
-
{ name="Seu Nome", email="seuemail@dominio.com" },
|
|
14
|
-
]
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Seu Nome", email = "seuemail@dominio.com" }]
|
|
15
13
|
keywords = ["django", "auth", "models", "shared"]
|
|
16
14
|
classifiers = [
|
|
17
15
|
"Framework :: Django",
|
|
@@ -20,11 +18,7 @@ classifiers = [
|
|
|
20
18
|
"Operating System :: OS Independent",
|
|
21
19
|
]
|
|
22
20
|
|
|
23
|
-
dependencies = [
|
|
24
|
-
"django>=5",
|
|
25
|
-
"djangorestframework>=3",
|
|
26
|
-
"setuptools>=70",
|
|
27
|
-
]
|
|
21
|
+
dependencies = ["django>=5", "djangorestframework>=3", "setuptools>=70"]
|
|
28
22
|
|
|
29
23
|
[project.urls]
|
|
30
24
|
Homepage = "https://github.com/maquinaweb/maquinaweb-shared-auth"
|
|
@@ -33,4 +27,4 @@ Issues = "https://github.com/maquinaweb/maquinaweb-shared-auth/issues"
|
|
|
33
27
|
|
|
34
28
|
[pypi]
|
|
35
29
|
username = "_token_"
|
|
36
|
-
password = "pypi-
|
|
30
|
+
password = "pypi-AgEIcHlwaS5vcmcCJGE5ZGRjMjJmLTgyMTMtNGExMC1iNzNhLTMwODZlOGNkMzRmMQACHlsxLFsibWFxdWluYXdlYi1zaGFyZWQtYXV0aCJdXQACLFsyLFsiZGEzMmMyMGUtYzQwMy00NTdmLThkMGItNjBhMmUyM2FmZTM1Il1dAAAGINvA-Vz3VzZFOSJ7XlOeJFMCSP7uv4dbA4HUCaxZaeoW"
|
|
@@ -3,8 +3,10 @@ Decorators para views funcionais
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from functools import wraps
|
|
6
|
+
|
|
6
7
|
from django.http import JsonResponse
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
from .models import SharedOrganization, SharedToken, SharedUser
|
|
8
10
|
|
|
9
11
|
|
|
10
12
|
def require_auth(view_func):
|
|
@@ -52,16 +54,13 @@ def require_organization(view_func):
|
|
|
52
54
|
@wraps(view_func)
|
|
53
55
|
@require_auth
|
|
54
56
|
def wrapped_view(request, *args, **kwargs):
|
|
55
|
-
if (
|
|
56
|
-
not hasattr(request.user, "logged_organization_id")
|
|
57
|
-
or not request.user.logged_organization_id
|
|
58
|
-
):
|
|
57
|
+
if not hasattr(request, "organization_id") or not request.organization_id:
|
|
59
58
|
return JsonResponse({"error": "Organização não definida"}, status=403)
|
|
60
59
|
|
|
61
60
|
# Buscar organização
|
|
62
61
|
try:
|
|
63
62
|
org = SharedOrganization.objects.using("auth_db").get(
|
|
64
|
-
pk=request.
|
|
63
|
+
pk=request.organization_id
|
|
65
64
|
)
|
|
66
65
|
|
|
67
66
|
if not org.is_active():
|
|
@@ -94,8 +93,6 @@ def require_same_organization(view_func):
|
|
|
94
93
|
|
|
95
94
|
# Aqui você precisa buscar o objeto e verificar
|
|
96
95
|
# Exemplo genérico - adapte conforme seu model
|
|
97
|
-
from django.apps import apps
|
|
98
|
-
|
|
99
96
|
# Tentar identificar o model pelo path
|
|
100
97
|
# Esta é uma implementação básica
|
|
101
98
|
# Em produção, você pode passar o model como parâmetro
|
|
@@ -3,6 +3,7 @@ Managers customizados para os models compartilhados
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
from django.db import models
|
|
6
|
+
from django.contrib.auth.models import UserManager
|
|
6
7
|
from .exceptions import OrganizationNotFoundError, UserNotFoundError
|
|
7
8
|
|
|
8
9
|
|
|
@@ -43,7 +44,7 @@ class SharedOrganizationManager(models.Manager):
|
|
|
43
44
|
return self.filter(cnpj__contains=clean_cnpj).first()
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
class SharedUserManager(
|
|
47
|
+
class SharedUserManager(UserManager):
|
|
47
48
|
"""Manager para SharedUser"""
|
|
48
49
|
|
|
49
50
|
def get_or_fail(self, user_id):
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
Middlewares para autenticação compartilhada
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from django.utils.deprecation import MiddlewareMixin
|
|
6
5
|
from django.http import JsonResponse
|
|
6
|
+
from django.utils.deprecation import MiddlewareMixin
|
|
7
7
|
|
|
8
|
-
from . import SharedMember
|
|
9
8
|
from .authentication import SharedTokenAuthentication
|
|
10
|
-
from .models import SharedToken, SharedUser
|
|
9
|
+
from .models import SharedMember, SharedOrganization, SharedToken, SharedUser
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class SharedAuthMiddleware(MiddlewareMixin):
|
|
@@ -46,7 +45,7 @@ class SharedAuthMiddleware(MiddlewareMixin):
|
|
|
46
45
|
token = self._get_token_from_request(request)
|
|
47
46
|
|
|
48
47
|
if not token:
|
|
49
|
-
request.user = None
|
|
48
|
+
# request.user = None
|
|
50
49
|
request.auth = None
|
|
51
50
|
return None
|
|
52
51
|
|
|
@@ -56,16 +55,16 @@ class SharedAuthMiddleware(MiddlewareMixin):
|
|
|
56
55
|
user = SharedUser.objects.using("auth_db").get(pk=token_obj.user_id)
|
|
57
56
|
|
|
58
57
|
if not user.is_active or user.deleted_at is not None:
|
|
59
|
-
request.user = None
|
|
58
|
+
# request.user = None
|
|
60
59
|
request.auth = None
|
|
61
60
|
return None
|
|
62
61
|
|
|
63
62
|
# Adicionar ao request
|
|
64
|
-
request.user = user
|
|
63
|
+
# request.user = user
|
|
65
64
|
request.auth = token_obj
|
|
66
65
|
|
|
67
66
|
except (SharedToken.DoesNotExist, SharedUser.DoesNotExist):
|
|
68
|
-
request.user = None
|
|
67
|
+
# request.user = None
|
|
69
68
|
request.auth = None
|
|
70
69
|
|
|
71
70
|
return None
|
|
@@ -149,6 +148,9 @@ class OrganizationMiddleware(MiddlewareMixin):
|
|
|
149
148
|
organization_id = self._determine_organization_id(request)
|
|
150
149
|
user = self._authenticate_user(request)
|
|
151
150
|
|
|
151
|
+
if not organization_id and not user:
|
|
152
|
+
return
|
|
153
|
+
|
|
152
154
|
if organization_id and user:
|
|
153
155
|
organization_id = self._validate_organization_membership(
|
|
154
156
|
user, organization_id
|
|
@@ -157,7 +159,11 @@ class OrganizationMiddleware(MiddlewareMixin):
|
|
|
157
159
|
return
|
|
158
160
|
|
|
159
161
|
request.organization_id = organization_id
|
|
160
|
-
request.organization =
|
|
162
|
+
request.organization = (
|
|
163
|
+
SharedOrganization.objects.using("auth_db")
|
|
164
|
+
.filter(pk=organization_id)
|
|
165
|
+
.first()
|
|
166
|
+
)
|
|
161
167
|
|
|
162
168
|
@staticmethod
|
|
163
169
|
def _authenticate_user(request):
|
|
@@ -171,6 +177,7 @@ class OrganizationMiddleware(MiddlewareMixin):
|
|
|
171
177
|
return org_id
|
|
172
178
|
|
|
173
179
|
return self._get_organization_from_user(request)
|
|
180
|
+
|
|
174
181
|
@staticmethod
|
|
175
182
|
def _get_organization_from_header(request):
|
|
176
183
|
if header_value := request.headers.get("X-Organization"):
|
|
@@ -179,22 +186,26 @@ class OrganizationMiddleware(MiddlewareMixin):
|
|
|
179
186
|
except (ValueError, TypeError):
|
|
180
187
|
pass
|
|
181
188
|
return None
|
|
189
|
+
|
|
182
190
|
@staticmethod
|
|
183
191
|
def _get_organization_from_user(request):
|
|
192
|
+
"""
|
|
193
|
+
Retorna a primeira organização do usuário autenticado
|
|
194
|
+
"""
|
|
184
195
|
if not request.user.is_authenticated:
|
|
185
196
|
return None
|
|
186
197
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
198
|
+
# Buscar a primeira organização que o usuário pertence
|
|
199
|
+
member = (
|
|
200
|
+
SharedMember.objects.using("auth_db")
|
|
201
|
+
.filter(user_id=request.user.pk)
|
|
202
|
+
.first()
|
|
203
|
+
)
|
|
204
|
+
|
|
205
|
+
return member.organization_id if member else None
|
|
192
206
|
|
|
193
|
-
return None
|
|
194
207
|
@staticmethod
|
|
195
|
-
def _validate_organization_membership(
|
|
196
|
-
user, organization_id
|
|
197
|
-
):
|
|
208
|
+
def _validate_organization_membership(user, organization_id):
|
|
198
209
|
try:
|
|
199
210
|
member = get_member(user, organization_id)
|
|
200
211
|
if not member and not user.is_superuser:
|
|
@@ -203,5 +214,8 @@ class OrganizationMiddleware(MiddlewareMixin):
|
|
|
203
214
|
except Exception:
|
|
204
215
|
return None
|
|
205
216
|
|
|
217
|
+
|
|
206
218
|
def get_member(user, organization_id):
|
|
207
|
-
return SharedMember.objects.filter(
|
|
219
|
+
return SharedMember.objects.filter(
|
|
220
|
+
user_id=user.pk, organization_id=organization_id
|
|
221
|
+
).first()
|
|
@@ -3,12 +3,14 @@ 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
4
|
"""
|
|
5
5
|
|
|
6
|
+
from django.contrib.auth.models import AbstractUser
|
|
6
7
|
from django.db import models
|
|
7
|
-
|
|
8
|
+
|
|
9
|
+
from .conf import MEMBER_TABLE, ORGANIZATION_TABLE, USER_TABLE
|
|
8
10
|
from .managers import (
|
|
11
|
+
SharedMemberManager,
|
|
9
12
|
SharedOrganizationManager,
|
|
10
13
|
SharedUserManager,
|
|
11
|
-
SharedMemberManager,
|
|
12
14
|
)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -132,23 +134,15 @@ class SharedOrganization(models.Model):
|
|
|
132
134
|
return self.deleted_at is None
|
|
133
135
|
|
|
134
136
|
|
|
135
|
-
class SharedUser(
|
|
137
|
+
class SharedUser(AbstractUser):
|
|
136
138
|
"""
|
|
137
139
|
Model READ-ONLY da tabela auth_user
|
|
138
140
|
"""
|
|
139
141
|
|
|
140
|
-
username = models.CharField(max_length=150)
|
|
141
|
-
first_name = models.CharField(max_length=150, blank=True)
|
|
142
|
-
last_name = models.CharField(max_length=150, blank=True)
|
|
143
|
-
email = models.EmailField()
|
|
144
|
-
is_staff = models.BooleanField(default=False)
|
|
145
|
-
is_active = models.BooleanField(default=True)
|
|
146
|
-
is_superuser = models.BooleanField(default=False)
|
|
147
142
|
date_joined = models.DateTimeField()
|
|
148
143
|
last_login = models.DateTimeField(null=True, blank=True)
|
|
149
144
|
|
|
150
145
|
# Campos customizados
|
|
151
|
-
logged_organization_id = models.IntegerField(null=True, blank=True)
|
|
152
146
|
createdat = models.DateTimeField()
|
|
153
147
|
updatedat = models.DateTimeField()
|
|
154
148
|
deleted_at = models.DateTimeField(null=True, blank=True)
|
|
@@ -160,51 +154,6 @@ class SharedUser(models.Model):
|
|
|
160
154
|
db_table = USER_TABLE
|
|
161
155
|
app_label = "shared_auth"
|
|
162
156
|
|
|
163
|
-
def __str__(self):
|
|
164
|
-
return self.get_full_name() or self.username
|
|
165
|
-
|
|
166
|
-
def get_full_name(self):
|
|
167
|
-
"""Retorna nome completo"""
|
|
168
|
-
return f"{self.first_name} {self.last_name}".strip()
|
|
169
|
-
|
|
170
|
-
@property
|
|
171
|
-
def logged_organization(self):
|
|
172
|
-
"""
|
|
173
|
-
Acessa organização logada (lazy loading)
|
|
174
|
-
|
|
175
|
-
Usage:
|
|
176
|
-
user = SharedUser.objects.get(pk=1)
|
|
177
|
-
org = user.logged_organization
|
|
178
|
-
print(org.name)
|
|
179
|
-
"""
|
|
180
|
-
if self.logged_organization_id:
|
|
181
|
-
return SharedOrganization.objects.get_or_fail(self.logged_organization_id)
|
|
182
|
-
return None
|
|
183
|
-
|
|
184
|
-
@property
|
|
185
|
-
def organizations(self):
|
|
186
|
-
"""
|
|
187
|
-
Retorna todas as organizações do usuário
|
|
188
|
-
|
|
189
|
-
Usage:
|
|
190
|
-
orgs = user.organizations
|
|
191
|
-
"""
|
|
192
|
-
member_orgs = SharedMember.objects.for_user(self.pk)
|
|
193
|
-
org_ids = member_orgs.values_list("organization_id", flat=True)
|
|
194
|
-
return SharedOrganization.objects.filter(pk__in=org_ids)
|
|
195
|
-
|
|
196
|
-
@property
|
|
197
|
-
def memberships(self):
|
|
198
|
-
"""
|
|
199
|
-
Retorna memberships do usuário
|
|
200
|
-
|
|
201
|
-
Usage:
|
|
202
|
-
memberships = user.memberships
|
|
203
|
-
for m in memberships:
|
|
204
|
-
print(m.organization.name)
|
|
205
|
-
"""
|
|
206
|
-
return SharedMember.objects.for_user(self.pk)
|
|
207
|
-
|
|
208
157
|
|
|
209
158
|
class SharedMember(models.Model):
|
|
210
159
|
"""
|
|
@@ -4,6 +4,8 @@ Permissões customizadas para DRF
|
|
|
4
4
|
|
|
5
5
|
from rest_framework import permissions
|
|
6
6
|
|
|
7
|
+
from shared_auth.middleware import get_member
|
|
8
|
+
|
|
7
9
|
|
|
8
10
|
class IsAuthenticated(permissions.BasePermission):
|
|
9
11
|
"""
|
|
@@ -26,10 +28,10 @@ class HasActiveOrganization(permissions.BasePermission):
|
|
|
26
28
|
message = "Organização ativa necessária."
|
|
27
29
|
|
|
28
30
|
def has_permission(self, request, view):
|
|
29
|
-
if not request.user or not hasattr(request
|
|
31
|
+
if not request.user or not hasattr(request, "organization_id"):
|
|
30
32
|
return False
|
|
31
33
|
|
|
32
|
-
if not request.
|
|
34
|
+
if not request.organization_id:
|
|
33
35
|
return False
|
|
34
36
|
|
|
35
37
|
# Verificar se organização está ativa
|
|
@@ -37,7 +39,7 @@ class HasActiveOrganization(permissions.BasePermission):
|
|
|
37
39
|
|
|
38
40
|
try:
|
|
39
41
|
org = SharedOrganization.objects.using("auth_db").get(
|
|
40
|
-
pk=request.
|
|
42
|
+
pk=request.organization_id
|
|
41
43
|
)
|
|
42
44
|
return org.is_active()
|
|
43
45
|
except SharedOrganization.DoesNotExist:
|
|
@@ -54,13 +56,17 @@ class IsSameOrganization(permissions.BasePermission):
|
|
|
54
56
|
message = "Você não tem permissão para acessar este recurso."
|
|
55
57
|
|
|
56
58
|
def has_object_permission(self, request, view, obj):
|
|
57
|
-
if not hasattr(request
|
|
59
|
+
if not hasattr(request, "organization_id"):
|
|
58
60
|
return False
|
|
59
61
|
|
|
60
62
|
if not hasattr(obj, "organization_id"):
|
|
61
63
|
return True # Se objeto não tem org, permite
|
|
62
64
|
|
|
63
|
-
|
|
65
|
+
# Verifica se o usuário é membro da organização do objeto
|
|
66
|
+
if not get_member(request.user, obj.organization_id):
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
return obj.organization_id == request.organization_id
|
|
64
70
|
|
|
65
71
|
|
|
66
72
|
class IsOwnerOrSameOrganization(permissions.BasePermission):
|
|
@@ -78,9 +84,9 @@ class IsOwnerOrSameOrganization(permissions.BasePermission):
|
|
|
78
84
|
return True
|
|
79
85
|
|
|
80
86
|
# Verificar se é da mesma organização
|
|
81
|
-
if hasattr(obj, "organization_id") and hasattr(
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
if hasattr(obj, "organization_id") and hasattr(request, "organization_id"):
|
|
88
|
+
# Verifica se o usuário é membro da organização do objeto
|
|
89
|
+
if get_member(request.user, obj.organization_id):
|
|
90
|
+
return obj.organization_id == request.organization_id
|
|
85
91
|
|
|
86
92
|
return False
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Biblioteca compartilhada para acesso aos models de autenticação
|
|
3
|
-
"""
|
|
4
|
-
from .models import (
|
|
5
|
-
SharedOrganization,
|
|
6
|
-
SharedUser,
|
|
7
|
-
SharedMember,
|
|
8
|
-
)
|
|
9
|
-
from .exceptions import (
|
|
10
|
-
OrganizationNotFoundError,
|
|
11
|
-
UserNotFoundError,
|
|
12
|
-
)
|
|
13
|
-
|
|
14
|
-
__version__ = '1.0.0'
|
|
15
|
-
__all__ = [
|
|
16
|
-
'SharedOrganization',
|
|
17
|
-
'SharedUser',
|
|
18
|
-
'SharedMember',
|
|
19
|
-
'OrganizationNotFoundError',
|
|
20
|
-
'UserNotFoundError',
|
|
21
|
-
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|