🔐 Maquinaweb Shared Auth
Biblioteca Django para autenticação compartilhada entre múltiplos sistemas usando um único banco de dados centralizado.
📋 Índice
- Visão Geral
- Características
- Arquitetura
- Instalação
- Configuração
- Uso Básico
- Guias Avançados
- API Reference
🎯 Visão Geral
A Maquinaweb Shared Auth permite que múltiplos sistemas Django compartilhem dados de autenticação, usuários e organizações através de um banco de dados centralizado, sem necessidade de requisições HTTP.
Problema Resolvido
Ao invés de:
- ❌ Duplicar dados de usuários em cada sistema
- ❌ Fazer requisições HTTP entre sistemas
- ❌ Manter múltiplos bancos de autenticação sincronizados
Você pode:
- ✅ Acessar dados de autenticação diretamente do banco central
- ✅ Usar a interface familiar do Django ORM
- ✅ Garantir consistência de dados entre sistemas
- ✅ Trabalhar com models read-only seguros
✨ Características
Core Features
- 🔐 Autenticação Centralizada: Token-based authentication compartilhado
- 🏢 Multi-Tenancy: Suporte completo a organizações e filiais
- 👥 Gestão de Membros: Relacionamento usuários ↔ organizações
- 🔒 Read-Only Safety: Proteção contra modificações acidentais
- ⚡ Performance: Managers otimizados com prefetch automático
- 🎨 DRF Ready: Mixins para serializers com dados aninhados
Componentes Principais
| Componente | Descrição |
|---|---|
| Models | SharedOrganization, User, SharedMember, SharedToken |
| Mixins | OrganizationMixin, UserMixin, OrganizationUserMixin |
| Serializers | OrganizationSerializerMixin, UserSerializerMixin |
| Authentication | SharedTokenAuthentication |
| Middleware | SharedAuthMiddleware, OrganizationMiddleware |
| Permissions | IsAuthenticated, HasActiveOrganization, IsSameOrganization |
| Managers | Métodos otimizados com prefetch e validações |
🏗️ Arquitetura
┌─────────────────────────────────────┐
│ Sistema de Autenticação Central │
│ │
│ ┌──────────────┐ ┌────────────┐ │
│ │Organization │ │ User │ │
│ └──────┬───────┘ └─────┬──────┘ │
│ │ │ │
│ └────────┬───────┘ │
│ │ │
│ ┌──────▼──────┐ │
│ │ Member │ │
│ │ Token │ │
│ └─────────────┘ │
└──────────────────┬──────────────────┘
│
┌────────────┴────────────┐
│ PostgreSQL/MySQL │
│ (auth_db) │
└────────────┬────────────┘
│
┌────────────┴────────────┐
│ │
┌─────▼─────┐ ┌─────▼─────┐
│ Sistema A │ │ Sistema B │
│ │ │ │
│ Pedidos │ │ Estoque │
│ ├─ org │ │ ├─ org │
│ └─ user │ │ └─ user │
└───────────┘ └───────────┘
Fluxo de Autenticação:
- Cliente envia request com token no header
- Middleware valida token no banco
auth_db - Dados do usuário e organização são anexados ao
request - Sistema cliente acessa dados via ORM (read-only)
📦 Instalação
1. Instalar a Biblioteca
# Via pip (quando publicado)
pip install maquinaweb-shared-auth
# Ou modo desenvolvimento
pip install -e /path/to/maquinaweb-shared-auth
2. Adicionar ao requirements.txt
Django>=4.2
djangorestframework>=3.14
maquinaweb-shared-auth>=0.2.25
⚙️ Configuração
1. Settings do Django
# settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'rest_framework',
# Adicionar shared_auth
'shared_auth',
# Suas apps
'myapp',
]
2. Configurar Banco de Dados
# settings.py
DATABASES = {
'default': {
# Banco do sistema atual
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'meu_sistema_db',
'USER': 'meu_user',
'PASSWORD': 'senha',
'HOST': 'localhost',
'PORT': '5432',
},
'auth_db': {
# Banco centralizado de autenticação (READ-ONLY)
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'sistema_auth_db',
'USER': 'readonly_user',
'PASSWORD': 'senha_readonly',
'HOST': 'auth-server.example.com',
'PORT': '5432',
}
}
# Router para direcionar queries
DATABASE_ROUTERS = ['shared_auth.router.SharedAuthRouter']
3. Configurar Autenticação (DRF)
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'shared_auth.authentication.SharedTokenAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
'shared_auth.permissions.IsAuthenticated',
],
}
4. Configurar Middleware (Opcional)
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# Middlewares da shared_auth
'shared_auth.middleware.SharedAuthMiddleware',
'shared_auth.middleware.OrganizationMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
]
5. Configurar Tabelas (Opcional)
# settings.py
# Customizar nomes das tabelas (se necessário)
SHARED_AUTH_ORGANIZATION_TABLE = 'organization_organization'
SHARED_AUTH_USER_TABLE = 'auth_user'
SHARED_AUTH_MEMBER_TABLE = 'organization_member'
SHARED_AUTH_TOKEN_TABLE = 'authtoken_token'
6. Criar Usuário Read-Only no PostgreSQL
-- No servidor de autenticação
CREATE USER readonly_user WITH PASSWORD 'senha_segura_aqui';
-- Conceder permissões
GRANT CONNECT ON DATABASE sistema_auth_db TO readonly_user;
GRANT USAGE ON SCHEMA public TO readonly_user;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly_user;
-- Para tabelas futuras
ALTER DEFAULT PRIVILEGES IN SCHEMA public
GRANT SELECT ON TABLES TO readonly_user;
-- Garantir read-only
ALTER USER readonly_user SET default_transaction_read_only = on;
🚀 Uso Básico
1. Models com Mixins
# myapp/models.py
from django.db import models
from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
from shared_auth.managers import BaseAuthManager
class Pedido(OrganizationUserMixin, TimestampedMixin):
"""Model que pertence a organização e usuário"""
numero = models.CharField(max_length=20, unique=True)
valor_total = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=20, default='pending')
objects = BaseAuthManager()
def __str__(self):
return f"Pedido {self.numero}"
O que você ganha automaticamente:
- ✅ Campos:
organization_id,user_id,created_at,updated_at - ✅ Properties:
organization,user,organization_name,user_email - ✅ Métodos:
validate_user_belongs_to_organization(),user_can_access()
2. Serializers com Dados Aninhados
# myapp/serializers.py
from rest_framework import serializers
from shared_auth.serializers import OrganizationUserSerializerMixin
from .models import Pedido
class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
class Meta:
model = Pedido
fields = [
'id', 'numero', 'valor_total', 'status',
'organization', # Objeto completo
'user', # Objeto completo
'created_at',
]
read_only_fields = ['organization', 'user', 'created_at']
Response JSON:
{
"id": 1,
"numero": "PED-001",
"valor_total": "1500.00",
"status": "pending",
"organization": {
"id": 123,
"name": "Empresa XYZ Ltda",
"fantasy_name": "XYZ",
"cnpj": "12.345.678/0001-90",
"email": "contato@xyz.com",
"is_active": true
},
"user": {
"id": 456,
"username": "joao.silva",
"email": "joao@xyz.com",
"full_name": "João Silva",
"is_active": true
},
"created_at": "2025-10-01T10:00:00Z"
}
3. ViewSets com Organização
# myapp/views.py
from rest_framework import viewsets
from shared_auth.mixins import LoggedOrganizationMixin
from shared_auth.permissions import HasActiveOrganization, IsSameOrganization
from .models import Pedido
from .serializers import PedidoSerializer
class PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
"""
ViewSet que filtra automaticamente por organização logada
"""
serializer_class = PedidoSerializer
permission_classes = [HasActiveOrganization, IsSameOrganization]
# get_queryset() já filtra por organization_id automaticamente
# perform_create() já adiciona organization_id automaticamente
4. Acessar Dados Compartilhados
# Em qualquer lugar do código
from shared_auth.models import SharedOrganization, User, SharedMember
# Buscar organização
org = SharedOrganization.objects.get_or_fail(123)
print(org.name) # "Empresa XYZ"
print(org.members) # QuerySet de membros
# Buscar usuário
user = User.objects.get_or_fail(456)
print(user.email) # "joao@xyz.com"
print(user.organizations) # Organizações do usuário
# Verificar membership
member = SharedMember.objects.filter(
user_id=456,
organization_id=123
).first()
if member:
print(f"{member.user.email} é membro de {member.organization.name}")
📚 Guias Avançados
Mixins para Models
1. OrganizationMixin
Para models que pertencem apenas a uma organização.
from shared_auth.mixins import OrganizationMixin
class EmpresaConfig(OrganizationMixin):
tema_cor = models.CharField(max_length=7, default='#3490dc')
logo = models.ImageField(upload_to='logos/')
# Uso
config = EmpresaConfig.objects.create(organization_id=123, tema_cor='#ff0000')
print(config.organization.name) # Acesso automático
print(config.organization_members) # Membros da organização
2. UserMixin
Para models que pertencem apenas a um usuário.
from shared_auth.mixins import UserMixin
class UserPreferences(UserMixin):
theme = models.CharField(max_length=20, default='light')
notifications_enabled = models.BooleanField(default=True)
# Uso
prefs = UserPreferences.objects.create(user_id=456, theme='dark')
print(prefs.user.email)
print(prefs.user_full_name)
3. OrganizationUserMixin
Para models que pertencem a organização E usuário (mais comum).
from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
class Tarefa(OrganizationUserMixin, TimestampedMixin):
titulo = models.CharField(max_length=200)
descricao = models.TextField()
status = models.CharField(max_length=20, default='pending')
# Uso
tarefa = Tarefa.objects.create(
organization_id=123,
user_id=456,
titulo='Implementar feature X'
)
# Validações
if tarefa.validate_user_belongs_to_organization():
print("✓ Usuário pertence à organização")
if tarefa.user_can_access(outro_user_id):
print("✓ Outro usuário pode acessar")
Managers Otimizados
from shared_auth.managers import BaseAuthManager
class Pedido(OrganizationUserMixin):
# ...
objects = BaseAuthManager()
# Filtrar por organização
pedidos = Pedido.objects.for_organization(123)
# Filtrar por usuário
meus_pedidos = Pedido.objects.for_user(456)
# Prefetch automático (evita N+1)
pedidos = Pedido.objects.with_auth_data()
for pedido in pedidos:
print(pedido.organization.name) # Sem query adicional
print(pedido.user.email) # Sem query adicional
Serializers - Variações
Versão Completa (Detail)
from shared_auth.serializers import OrganizationUserSerializerMixin
class PedidoDetailSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
class Meta:
model = Pedido
fields = ['id', 'numero', 'organization', 'user', 'created_at']
Versão Simplificada (List)
from shared_auth.serializers import (
OrganizationSimpleSerializerMixin,
UserSimpleSerializerMixin
)
class PedidoListSerializer(
OrganizationSimpleSerializerMixin,
UserSimpleSerializerMixin,
serializers.ModelSerializer
):
class Meta:
model = Pedido
fields = ['id', 'numero', 'organization', 'user']
# Response com dados reduzidos
{
"id": 1,
"numero": "PED-001",
"organization": {
"id": 123,
"name": "Empresa XYZ",
"cnpj": "12.345.678/0001-90"
},
"user": {
"id": 456,
"email": "joao@xyz.com",
"full_name": "João Silva"
}
}
Customização Avançada
class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
def get_organization(self, obj):
"""Override para adicionar campos customizados"""
org_data = super().get_organization(obj)
if org_data:
# Adicionar dados extras
org_data['logo_url'] = f"/logos/{obj.organization_id}.png"
org_data['member_count'] = obj.organization.members.count()
return org_data
Middleware
SharedAuthMiddleware
Autentica usuário baseado no token.
# settings.py
MIDDLEWARE = [
# ...
'shared_auth.middleware.SharedAuthMiddleware',
]
Busca token em:
- Header:
Authorization: Token <token> - Header:
X-Auth-Token: <token> - Cookie:
auth_token
Adiciona ao request:
request.user- Objeto User autenticadorequest.auth- Token object
OrganizationMiddleware
Adiciona organização logada ao request.
# settings.py
MIDDLEWARE = [
'shared_auth.middleware.SharedAuthMiddleware',
'shared_auth.middleware.OrganizationMiddleware', # Depois do Auth
]
Busca organização:
- Header
X-Organization: <org_id> - Primeira organização do usuário autenticado
Adiciona ao request:
request.organization_id- ID da organizaçãorequest.organization- Objeto SharedOrganization
Uso em views:
def my_view(request):
org_id = request.organization_id
org = request.organization
if org:
print(f"Organização logada: {org.name}")
Permissions
from shared_auth.permissions import (
IsAuthenticated,
HasActiveOrganization,
IsSameOrganization,
IsOwnerOrSameOrganization,
)
class PedidoViewSet(viewsets.ModelViewSet):
permission_classes = [
IsAuthenticated, # Requer autenticação
HasActiveOrganization, # Requer organização ativa
IsSameOrganization, # Objeto da mesma org
]
# Ou combinações
class TarefaViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwnerOrSameOrganization]
# Permite se for dono OU da mesma organização
Authentication
# Em qualquer view/viewset DRF
from shared_auth.authentication import SharedTokenAuthentication
class MyAPIView(APIView):
authentication_classes = [SharedTokenAuthentication]
def get(self, request):
user = request.user # User autenticado
token = request.auth # SharedToken object
return Response({
'user': user.email,
'token_created': token.created
})
🔍 API Reference
Models
SharedOrganization
from shared_auth.models import SharedOrganization
# Campos
org.id
org.name
org.fantasy_name
org.cnpj
org.email
org.image_organization
org.is_branch
org.main_organization_id
org.created_at
org.updated_at
org.deleted_at
# Properties
org.main_organization # SharedOrganization | None
org.branches # QuerySet[SharedOrganization]
org.members # QuerySet[SharedMember]
org.users # QuerySet[User]
# Métodos
org.is_active() # bool
User
from shared_auth.models import User
# Campos (AbstractUser + custom)
user.id
user.username
user.email
user.first_name
user.last_name
user.is_active
user.is_staff
user.is_superuser
user.date_joined
user.last_login
user.avatar
user.createdat
user.updatedat
user.deleted_at
# Properties
user.organizations # QuerySet[SharedOrganization]
# Métodos
user.get_full_name() # str
user.get_org(organization_id) # SharedOrganization | raise
SharedMember
from shared_auth.models import SharedMember
# Campos
member.id
member.user_id
member.organization_id
member.metadata # JSONField
# Properties
member.user # User
member.organization # SharedOrganization
SharedToken
from shared_auth.models import SharedToken
# Campos
token.key # Primary Key
token.user_id
token.created
# Properties
token.user # User
# Métodos
token.is_valid() # bool
Managers
SharedOrganizationManager
from shared_auth.models import SharedOrganization
SharedOrganization.objects.get_or_fail(123) # Org | raise OrganizationNotFoundError
SharedOrganization.objects.active() # QuerySet (deleted_at is null)
SharedOrganization.objects.branches() # QuerySet (is_branch=True)
SharedOrganization.objects.main_organizations() # QuerySet (is_branch=False)
SharedOrganization.objects.by_cnpj('12.345.678/0001-90') # Org | None
UserManager
from shared_auth.models import User
User.objects.get_or_fail(456) # User | raise UserNotFoundError
User.objects.active() # QuerySet (is_active=True, deleted_at is null)
User.objects.by_email('user@example.com') # User | None
SharedMemberManager
from shared_auth.models import SharedMember
SharedMember.objects.for_user(456) # QuerySet
SharedMember.objects.for_organization(123) # QuerySet
BaseAuthManager (para seus models)
# Quando usa OrganizationMixin
Model.objects.for_organization(123) # QuerySet
Model.objects.for_organizations([123, 456]) # QuerySet
Model.objects.with_organization_data() # List com prefetch
# Quando usa UserMixin
Model.objects.for_user(456) # QuerySet
Model.objects.for_users([456, 789]) # QuerySet
Model.objects.with_user_data() # List com prefetch
# Quando usa OrganizationUserMixin
Model.objects.with_auth_data() # List com prefetch de org e user
Model.objects.create_with_validation(
organization_id=123,
user_id=456,
**kwargs
) # Valida membership antes de criar
Exceptions
from shared_auth.exceptions import (
SharedAuthError,
OrganizationNotFoundError,
UserNotFoundError,
DatabaseConnectionError,
)
try:
org = SharedOrganization.objects.get_or_fail(999)
except OrganizationNotFoundError as e:
print(e) # "Organização com ID 999 não encontrada"
🎯 Casos de Uso Reais
Sistema de Pedidos Multi-Tenant
# models.py
from shared_auth.mixins import OrganizationUserMixin, TimestampedMixin
class Pedido(OrganizationUserMixin, TimestampedMixin):
numero = models.CharField(max_length=20, unique=True)
valor_total = models.DecimalField(max_digits=10, decimal_places=2)
status = models.CharField(max_length=20)
objects = BaseAuthManager()
class ItemPedido(models.Model):
pedido = models.ForeignKey(Pedido, related_name='itens')
produto = models.CharField(max_length=200)
quantidade = models.IntegerField()
valor_unitario = models.DecimalField(max_digits=10, decimal_places=2)
# serializers.py
from shared_auth.serializers import OrganizationUserSerializerMixin
class ItemPedidoSerializer(serializers.ModelSerializer):
class Meta:
model = ItemPedido
fields = ['id', 'produto', 'quantidade', 'valor_unitario']
class PedidoSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
itens = ItemPedidoSerializer(many=True, read_only=True)
class Meta:
model = Pedido
fields = [
'id', 'numero', 'valor_total', 'status',
'organization', 'user', 'itens', 'created_at'
]
# views.py
from shared_auth.mixins import LoggedOrganizationMixin
from shared_auth.permissions import HasActiveOrganization
class PedidoViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
serializer_class = PedidoSerializer
permission_classes = [HasActiveOrganization]
def get_queryset(self):
# Já filtra por organization_id automaticamente
return super().get_queryset().with_auth_data()
Sistema de Tarefas com Responsáveis
# models.py
class Tarefa(OrganizationUserMixin, TimestampedMixin):
"""
user_id = criador
responsavel_id = quem vai executar
"""
titulo = models.CharField(max_length=200)
descricao = models.TextField()
responsavel_id = models.IntegerField()
status = models.CharField(max_length=20, default='pending')
objects = BaseAuthManager()
@property
def responsavel(self):
"""Acessa usuário responsável"""
if not hasattr(self, '_cached_responsavel'):
from shared_auth.models import User
self._cached_responsavel = User.objects.get_or_fail(self.responsavel_id)
return self._cached_responsavel
# serializers.py
class TarefaSerializer(OrganizationUserSerializerMixin, serializers.ModelSerializer):
responsavel = serializers.SerializerMethodField()
def get_responsavel(self, obj):
try:
resp = obj.responsavel
return {
'id': resp.pk,
'email': resp.email,
'full_name': resp.get_full_name(),
}
except:
return None
class Meta:
model = Tarefa
fields = [
'id', 'titulo', 'descricao', 'status',
'organization', # Organização dona
'user', # Criador
'responsavel', # Executor
'created_at'
]
🔧 Troubleshooting
Problema: Queries lentas (N+1)
Solução: Use os managers com prefetch
# ❌ Ruim - Causa N+1
pedidos = Pedido.objects.all()
for pedido in pedidos:
print(pedido.organization.name) # Query por item!
# ✅ Bom - 3 queries total
pedidos = Pedido.objects.with_auth_data()
for pedido in pedidos:
print(pedido.organization.name) # Sem query adicional
Problema: OrganizationNotFoundError
Causa: ID de organização inválido ou deletada
Solução:
# Usar try/except
try:
org = SharedOrganization.objects.get_or_fail(org_id)
except OrganizationNotFoundError:
# Tratar erro
return Response({'error': 'Organização não encontrada'}, status=404)
# Ou usar filter
org = SharedOrganization.objects.filter(pk=org_id).first()
if not org:
# Tratar
Problema: Erro de conexão com auth_db
Solução: Verificar configuração do database router e permissões
# Testar conexão
from django.db import connections
connection = connections['auth_db']
with connection.cursor() as cursor:
cursor.execute("SELECT 1")
print("✓ Conexão OK")
📝 Changelog
v0.2.25
- ✨ Adicionado suporte a imagens (avatar, logo)
- ✨ StorageBackend para arquivos compartilhados
- 🐛 Correções nos serializers
- 📚 Documentação melhorada
v0.2.0
- ✨ Middlewares: SharedAuthMiddleware, OrganizationMiddleware
- ✨ Permissions customizadas
- ✨ Managers otimizados com prefetch
- ✨ Serializer mixins com dados aninhados
v0.1.0
- 🎉 Versão inicial
- ✨ Models compartilhados
- ✨ Mixins básicos
- ✨ Autenticação via token
📄 Licença
MIT License - veja LICENSE para detalhes.
🤝 Contribuindo
Contribuições são bem-vindas! Por favor, abra uma issue ou pull request.
📧 Suporte
Para suporte, abra uma issue no GitHub ou entre em contato com a equipe Maquinaweb.
Desenvolvido com ❤️ por Maquinaweb
Multi-organização optativa (0.3.0)
X-Organization continua identificando a organização logada. X-Mult-Organization
contém IDs positivos separados por vírgulas, por exemplo 1,2. IDs repetidos são
normalizados; um header vazio ou malformado retorna 400. Sem o header múltiplo,
permissões e escopo mantêm o fluxo anterior.
from rest_framework import viewsets
from shared_auth.mixins import LoggedOrganizationMixin
class ReceiptsViewSet(LoggedOrganizationMixin, viewsets.ModelViewSet):
queryset = Receipts.objects.all()
base_permission = "receipts"
allow_multi_organization = True
Antes da execução da ação, a biblioteca valida vínculo ativo, disponibilidade da
organização, permissões do plano e permissões do usuário em todas as
organizações selecionadas. Uma falha recusa toda a requisição com 403, sem resultados
parciais ou exceção automática para superusuários. Ações padrão reutilizam
base_permission e translate_action_to_perm. Ações customizadas precisam declarar
required_permission ou required_permissions antes de super().check_permissions();
esta última mantém a semântica de pelo menos uma permissão em cada organização.
Não existe um segundo mapa obrigatório de permissões multi-organização.
O contexto diferencia vínculos de escopo autorizado:
request.organization_id: organização logada, sem alteração pela seleção.request.organization_idseget_organization_ids(): vínculos do usuário, não representam autorização para consultar todos esses dados.request.selected_organization_ids: seleção normalizada da requisição aceita.request.authorized_organization_ids: conjunto validado para a ação atual.get_query_organization_ids(): conjunto autorizado ou apenas a organização logada se o header múltiplo estiver ausente.
LoggedOrganizationMixin.get_queryset() usa esse escopo. Consultas manuais,
serviços, exportações e tarefas precisam recebê-lo explicitamente. Endpoints que
não habilitam a seleção recusam o header com 400 e código
multi_organization_not_supported; ações sem política declarada também são recusadas.
IsSameOrganization aceita objetos do conjunto autorizado, após validar o vínculo;
as permissões da ação continuam sendo verificadas pelo mixin.
A criação padrão com o header múltiplo valida a permissão de criação em todo o
conjunto e também na organização logada, mesmo quando ela não faz parte da seleção.
O consumidor deve continuar atribuindo todos os novos cadastros à organização logada
com OrganizationCreateSerializerMixin. Nos updates, o consumidor deve preservar a
organização original, rejeitar transferências e validar relacionamentos na
organização do registro. A biblioteca não converte logged_organization em uma lista
nem altera relações de domínio automaticamente.
Para ações em lote, MultiOrganizationBatchMixin é exportado por shared_auth.mixins
e shared_auth.batch. Ele valida todos os IDs presentes em request.data["ids"]
antes da ação e rejeita registros fora do queryset autorizado. Corpos com outro
campo de IDs devem chamar validate_organization_batch(ids) explicitamente antes
de executar alterações. Ações bulk_delete, mark_as_paid e
deactivate_recurrents reutilizam a permissão da ação correspondente. Outras ações
precisam declarar sua permissão. O mixin deve preceder LoggedOrganizationMixin
na lista de bases da view.
As recusas de autorização retornam code="multi_organization_denied", detail
e impediments. Cada impedimento contém organization_id, cause e detail.
As causas são inactive_or_missing_membership, organization_unavailable ou
insufficient_permission. Nomes de organizações e permissões faltantes só são
incluídos quando o vínculo ativo permite identificar a organização. A mensagem
orienta o usuário a solicitar acesso ou permissão ao gestor ou ajustar a seleção.
O frontend deve manter a seleção editável e esconder resultados antigos após uma
recusa, sem substituir essa mensagem por uma página genérica de acesso negado.
Nenhuma alteração de schema no mw-auth é necessária. Publique e valide esta versão
compatível antes de ativar os consumidores. Atualize a dependência e o lockfile
com uv add maquinaweb-shared-auth==0.3.0 após a publicação; não dependa de caminhos
locais ou de uma instalação editável para deploy.
Metadata
Release files for maquinaweb-shared-auth 0.3.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| maquinaweb_shared_auth-0.3.3.tar.gz | 53.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| maquinaweb_shared_auth-0.3.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 101.1 kB