maquinaweb-shared-auth 0.2.60__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
shared_auth/app.py ADDED
@@ -0,0 +1,9 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class SharedAuthConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "shared_auth"
7
+
8
+ def ready(self):
9
+ from . import models, exceptions # noqa
@@ -0,0 +1,55 @@
1
+ """
2
+ Backend de autenticação usando tokens do banco compartilhado
3
+ """
4
+
5
+ from django.utils.translation import gettext_lazy as _
6
+ from rest_framework import exceptions
7
+ from rest_framework.authentication import TokenAuthentication
8
+
9
+ from .utils import get_token_model, get_user_model
10
+
11
+
12
+ class SharedTokenAuthentication(TokenAuthentication):
13
+ """
14
+ Autentica usando tokens do banco de dados compartilhado
15
+
16
+ Usa get_token_model() e get_user_model() para suportar models customizados.
17
+
18
+ Usage em settings.py:
19
+ REST_FRAMEWORK = {
20
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
21
+ 'shared_auth.authentication.SharedTokenAuthentication',
22
+ ]
23
+ }
24
+ """
25
+
26
+ @property
27
+ def model(self):
28
+ """Retorna o model de Token configurado"""
29
+ return get_token_model()
30
+
31
+ def authenticate_credentials(self, key):
32
+ """
33
+ Valida o token no banco de dados compartilhado
34
+ """
35
+ Token = get_token_model()
36
+ User = get_user_model()
37
+
38
+ try:
39
+ token = Token.objects.get(key=key)
40
+ except Token.DoesNotExist:
41
+ raise exceptions.AuthenticationFailed(_("Token inválido."))
42
+
43
+ # Buscar usuário completo
44
+ try:
45
+ user = User.objects.get(pk=token.user_id)
46
+ except User.DoesNotExist:
47
+ raise exceptions.AuthenticationFailed(_("Usuário não encontrado."))
48
+
49
+ if not user.is_active:
50
+ raise exceptions.AuthenticationFailed(_("Usuário inativo ou deletado."))
51
+
52
+ if user.deleted_at is not None:
53
+ raise exceptions.AuthenticationFailed(_("Usuário deletado."))
54
+
55
+ return (user, token)
shared_auth/conf.py ADDED
@@ -0,0 +1,33 @@
1
+ from django.conf import settings
2
+
3
+ def get_setting(name, default):
4
+ """Retorna valor configurado no settings ou o padrão"""
5
+ return getattr(settings, name, default)
6
+
7
+
8
+ # Auth tables
9
+ ORGANIZATION_TABLE = get_setting("SHARED_AUTH_ORGANIZATION_TABLE", "organization_organization")
10
+ ORGANIZATION_GROUP_TABLE = get_setting("SHARED_AUTH_ORGANIZATION_GROUP_TABLE", "organization_organizationgroup")
11
+ USER_TABLE = get_setting("SHARED_AUTH_USER_TABLE", "auth_user")
12
+ MEMBER_TABLE = get_setting("SHARED_AUTH_MEMBER_TABLE", "organization_member")
13
+ TOKEN_TABLE = get_setting("SHARED_AUTH_TOKEN_TABLE", "organization_multitoken")
14
+
15
+ # Permission system tables
16
+ SYSTEM_TABLE = get_setting("SHARED_AUTH_SYSTEM_TABLE", "plans_system")
17
+ PERMISSION_TABLE = get_setting("SHARED_AUTH_PERMISSION_TABLE", "organization_permissions")
18
+ PLAN_TABLE = get_setting("SHARED_AUTH_PLAN_TABLE", "plans_plan")
19
+ SUBSCRIPTION_TABLE = get_setting("SHARED_AUTH_SUBSCRIPTION_TABLE", "plans_subscription")
20
+ GROUP_PERMISSIONS_TABLE = get_setting("SHARED_AUTH_GROUP_PERMISSIONS_TABLE", "organization_grouppermissions")
21
+ GROUP_ORG_PERMISSIONS_TABLE = get_setting("SHARED_AUTH_GROUP_ORG_PERMISSIONS_TABLE", "organization_grouporganizationpermissions")
22
+ MEMBER_SYSTEM_GROUP_TABLE = get_setting("SHARED_AUTH_MEMBER_SYSTEM_GROUP_TABLE", "organization_membersystemgroup")
23
+
24
+ # и т.д.
25
+ # ManyToMany tables
26
+ PLAN_GROUP_PERMISSIONS_TABLE = get_setting("SHARED_AUTH_PLAN_GROUP_PERMISSIONS_TABLE", "plans_plan_group_permissions")
27
+ GROUP_PERMISSIONS_PERMISSIONS_TABLE = get_setting("SHARED_AUTH_GROUP_PERMISSIONS_PERMISSIONS_TABLE", "organization_grouppermissions_permissions")
28
+ GROUP_ORG_PERMISSIONS_PERMISSIONS_TABLE = get_setting("SHARED_AUTH_GROUP_ORG_PERMISSIONS_PERMISSIONS_TABLE", "organization_grouporganizationpermissions_permissions")
29
+
30
+ # Other settings
31
+ CLOUDFRONT_DOMAIN = get_setting("CLOUDFRONT_DOMAIN", "")
32
+ CUSTOM_DOMAIN_AUTH = get_setting("CUSTOM_DOMAIN_AUTH", CLOUDFRONT_DOMAIN)
33
+ SYSTEM_ID = get_setting("SYSTEM_ID", None)
@@ -0,0 +1,122 @@
1
+ """
2
+ Decorators para views funcionais
3
+ """
4
+
5
+ from functools import wraps
6
+
7
+ from django.http import JsonResponse
8
+
9
+ from .utils import get_organization_model, get_token_model, get_user_model
10
+
11
+
12
+ def require_auth(view_func):
13
+ """
14
+ Decorator que requer autenticação
15
+
16
+ Usage:
17
+ @require_auth
18
+ def my_view(request):
19
+ return JsonResponse({'user': request.user.email})
20
+ """
21
+
22
+ @wraps(view_func)
23
+ def wrapped_view(request, *args, **kwargs):
24
+ # Extrair token
25
+ token = _get_token_from_request(request)
26
+
27
+ if not token:
28
+ return JsonResponse({"error": "Token não fornecido"}, status=401)
29
+
30
+ # Validar token
31
+ Token = get_token_model()
32
+ User = get_user_model()
33
+
34
+ try:
35
+ token_obj = Token.objects.get(key=token)
36
+ user = User.objects.get(pk=token_obj.user_id)
37
+
38
+ if not user.is_active or user.deleted_at is not None:
39
+ return JsonResponse({"error": "Usuário inativo"}, status=401)
40
+
41
+ request.user = user
42
+ request.auth = token_obj
43
+
44
+ except (Token.DoesNotExist, User.DoesNotExist):
45
+ return JsonResponse({"error": "Token inválido"}, status=401)
46
+
47
+ return view_func(request, *args, **kwargs)
48
+
49
+ return wrapped_view
50
+
51
+
52
+ def require_organization(view_func):
53
+ """
54
+ Decorator que requer organização ativa
55
+ """
56
+
57
+ @wraps(view_func)
58
+ @require_auth
59
+ def wrapped_view(request, *args, **kwargs):
60
+ if not hasattr(request, "organization_id") or not request.organization_id:
61
+ return JsonResponse({"error": "Organização não definida"}, status=403)
62
+
63
+ # Buscar organização
64
+ Organization = get_organization_model()
65
+
66
+ try:
67
+ org = Organization.objects.get(pk=request.organization_id)
68
+
69
+ if not org.is_active():
70
+ return JsonResponse({"error": "Organização inativa"}, status=403)
71
+
72
+ request.organization = org
73
+
74
+ except Organization.DoesNotExist:
75
+ return JsonResponse({"error": "Organização não encontrada"}, status=404)
76
+
77
+ return view_func(request, *args, **kwargs)
78
+
79
+ return wrapped_view
80
+
81
+
82
+ def require_same_organization(view_func):
83
+ """
84
+ Decorator que verifica se objeto pertence à mesma organização
85
+
86
+ O objeto deve estar em kwargs['pk'] ou kwargs['id']
87
+ """
88
+
89
+ @wraps(view_func)
90
+ @require_organization
91
+ def wrapped_view(request, *args, **kwargs):
92
+ obj_id = kwargs.get("pk") or kwargs.get("id")
93
+
94
+ if not obj_id:
95
+ return view_func(request, *args, **kwargs)
96
+
97
+ # Aqui você precisa buscar o objeto e verificar
98
+ # Exemplo genérico - adapte conforme seu model
99
+ # Tentar identificar o model pelo path
100
+ # Esta é uma implementação básica
101
+ # Em produção, você pode passar o model como parâmetro
102
+
103
+ return view_func(request, *args, **kwargs)
104
+
105
+ return wrapped_view
106
+
107
+
108
+ def _get_token_from_request(request):
109
+ """Helper para extrair token"""
110
+ auth_header = request.META.get("HTTP_AUTHORIZATION", "")
111
+ if auth_header.startswith("Token "):
112
+ return auth_header.split(" ")[1]
113
+
114
+ token = request.META.get("HTTP_X_AUTH_TOKEN")
115
+ if token:
116
+ return token
117
+
118
+ token = request.COOKIES.get("auth_token")
119
+ if token:
120
+ return token
121
+
122
+ return None
@@ -0,0 +1,23 @@
1
+ from rest_framework.exceptions import APIException
2
+ """
3
+ Exceções customizadas
4
+ """
5
+ class SharedAuthError(APIException):
6
+ """Erro base da biblioteca"""
7
+ pass
8
+
9
+
10
+ class OrganizationNotFoundError(SharedAuthError):
11
+ status_code = 404
12
+ default_detail = 'Organização não encontrada.'
13
+ default_code = 'organization_not_found'
14
+
15
+ class UserNotFoundError(SharedAuthError):
16
+ status_code = 404
17
+ default_detail = 'Usuário não encontrado.'
18
+ default_code = 'user_not_found'
19
+
20
+ class DatabaseConnectionError(SharedAuthError):
21
+ status_code = 500
22
+ default_detail = 'Erro interno'
23
+ default_code = 'internal_error'
shared_auth/fields.py ADDED
@@ -0,0 +1,51 @@
1
+ from rest_framework import serializers
2
+ """
3
+ Fields customizados para facilitar ainda mais
4
+ """
5
+
6
+ """
7
+ Fields customizados para facilitar ainda mais
8
+ """
9
+
10
+
11
+ class OrganizationField(serializers.Field):
12
+ """
13
+ Field que retorna dados completos da organização
14
+
15
+ Usage:
16
+ class RascunhoSerializer(serializers.ModelSerializer):
17
+ organization = OrganizationField(source='*')
18
+ """
19
+
20
+ def to_representation(self, obj):
21
+ try:
22
+ org = obj.organization
23
+ return {
24
+ "id": org.pk,
25
+ "name": org.name,
26
+ "fantasy_name": org.fantasy_name,
27
+ "cnpj": org.cnpj,
28
+ "email": org.email,
29
+ "is_active": org.is_active(),
30
+ }
31
+ except Exception:
32
+ return None
33
+
34
+
35
+ class UserField(serializers.Field):
36
+ """
37
+ Field que retorna dados completos do usuário
38
+ """
39
+
40
+ def to_representation(self, obj):
41
+ try:
42
+ user = obj.user
43
+ return {
44
+ "id": user.pk,
45
+ "username": user.username,
46
+ "email": user.email,
47
+ "full_name": user.get_full_name(),
48
+ "is_active": user.is_active,
49
+ }
50
+ except Exception:
51
+ return None
File without changes
File without changes
@@ -0,0 +1,147 @@
1
+ from pathlib import Path
2
+
3
+ import yaml
4
+ from django.conf import settings
5
+ from django.core.management.base import BaseCommand, CommandError
6
+
7
+ from shared_auth.conf import get_setting
8
+
9
+ DEFAULT_ACTIONS = [
10
+ ("add", "Adicionar"),
11
+ ("view", "Visualizar"),
12
+ ("change", "Editar"),
13
+ ("delete", "Excluir"),
14
+ ]
15
+
16
+
17
+ class Command(BaseCommand):
18
+ help = "Generate permissions from permissions.yml"
19
+
20
+ def add_arguments(self, parser):
21
+ parser.add_argument(
22
+ "--file",
23
+ default="permissions.yml",
24
+ help="Path to permissions YAML file (default: permissions.yml)",
25
+ )
26
+ parser.add_argument(
27
+ "--dry-run",
28
+ action="store_true",
29
+ help="Show what would be done without making changes",
30
+ )
31
+
32
+ def handle(self, *args, **options):
33
+ from shared_auth.utils import get_permission_model, get_system_model
34
+
35
+ system_id = get_setting("SYSTEM_ID", None)
36
+ if not system_id:
37
+ raise CommandError("SYSTEM_ID not configured in settings.")
38
+
39
+ System = get_system_model()
40
+ try:
41
+ system = System.objects.using("auth_db").get(id=system_id)
42
+ except System.DoesNotExist:
43
+ raise CommandError(f"System with ID {system_id} not found.")
44
+
45
+ self.stdout.write(f"System: {system.name} (ID: {system_id})")
46
+
47
+ # Find and parse YAML
48
+ yaml_path = self._find_yaml(options["file"])
49
+ if not yaml_path:
50
+ raise CommandError(f"File not found: {options['file']}")
51
+
52
+ with open(yaml_path, encoding="utf-8") as f:
53
+ data = yaml.safe_load(f)
54
+
55
+ scopes = data.get("scopes", {})
56
+ if not scopes:
57
+ raise CommandError("No scopes found in YAML file.")
58
+
59
+ Permission = get_permission_model()
60
+ dry_run = options["dry_run"]
61
+ created, updated = 0, 0
62
+
63
+ for scope_key, scope_data in scopes.items():
64
+ scope_name = scope_data.get("name", scope_key)
65
+ models = scope_data.get("models", [])
66
+
67
+ self.stdout.write(f"\n[{scope_key}] {scope_name}")
68
+
69
+ for model_config in models:
70
+ model_name = model_config.get("model")
71
+ model_display_name = model_config.get("name", model_name)
72
+
73
+ if not model_name:
74
+ continue
75
+
76
+ # Create default CRUD permissions
77
+ for action, action_name in DEFAULT_ACTIONS:
78
+ codename = f"{action}_{model_name}"
79
+ name = f"{action_name} {model_display_name}"
80
+
81
+ c, u = self._create_permission(
82
+ Permission, codename, name, scope_key, system_id, dry_run
83
+ )
84
+ created += c
85
+ updated += u
86
+
87
+ # Create custom permissions
88
+ custom_permissions = model_config.get("custom_permissions", [])
89
+ for custom_perm in custom_permissions:
90
+ action = custom_perm.get("action")
91
+ perm_name = custom_perm.get("name")
92
+
93
+ if not action:
94
+ continue
95
+
96
+ codename = f"{action}_{model_name}"
97
+ name = perm_name or f"{action} {model_display_name}"
98
+
99
+ c, u = self._create_permission(
100
+ Permission, codename, name, scope_key, system_id, dry_run
101
+ )
102
+ created += c
103
+ updated += u
104
+
105
+ self.stdout.write(f"\nCreated: {created} | Updated: {updated}")
106
+
107
+ def _create_permission(
108
+ self, Permission, codename, name, scope, system_id, dry_run
109
+ ) -> tuple[int, int]:
110
+ """Create or update a permission. Returns (created_count, updated_count)."""
111
+ defaults = {"name": name, "scope": scope}
112
+
113
+ if dry_run:
114
+ exists = (
115
+ Permission.objects.using("auth_db")
116
+ .filter(codename=codename, system_id=system_id)
117
+ .exists()
118
+ )
119
+ status = "exists" if exists else "new"
120
+ self.stdout.write(f" [{status}] {codename}")
121
+ return (0, 0)
122
+
123
+ obj, is_new = Permission.objects.using("auth_db").update_or_create(
124
+ codename=codename,
125
+ system_id=system_id,
126
+ defaults=defaults,
127
+ )
128
+
129
+ if is_new:
130
+ self.stdout.write(self.style.SUCCESS(f" + {codename}"))
131
+ return (1, 0)
132
+
133
+ self.stdout.write(f" ~ {codename}")
134
+ return (0, 1)
135
+
136
+ def _find_yaml(self, file_path: str) -> Path | None:
137
+ paths = [
138
+ Path(file_path),
139
+ Path(settings.BASE_DIR) / file_path
140
+ if hasattr(settings, "BASE_DIR")
141
+ else None,
142
+ Path.cwd() / file_path,
143
+ ]
144
+ for p in paths:
145
+ if p and p.exists():
146
+ return p
147
+ return None