REF-201-01: RBAC Roles & Scopes
ADR: ADR-201 — Identity: Organization & CollaboratorEscopo: Matriz completa roles × scopes, herança de permissões, regras de imutabilidade, fluxo de convite, limites configuráveis
1. Matriz Roles × Scopes
| Scope / Role | owner | admin | member | guest | pending |
|---|---|---|---|---|---|
organization | ✅ * | ✅ | ⚙️ | ❌ | ❌ |
finances | ✅ * | ✅ | ⚙️ | ❌ | ❌ |
orders | ✅ * | ✅ | ⚙️ | ❌ | ❌ |
licenses | ✅ * | ✅ | ⚙️ | ❌ | ❌ |
tickets | ✅ * | ✅ | ⚙️ | ❌ | ❌ |
quotes | ✅ * | ✅ | ⚙️ | ❌ | ❌ |
contracts | ✅ * | ✅ | ⚙️ | ❌ | ❌ |
documents | ✅ * | ✅ | ⚙️ | ⚙️ | ❌ |
downloads | ✅ * | ✅ | ⚙️ | ❌ | ❌ |
| Gerenciar equipe | ✅ | ✅ | ❌ | ❌ | ❌ |
Legenda:
- ✅ * = Acesso implícito (owner tem todos os scopes, não precisa de atribuição)
- ✅ = Acesso garantido pelo role
- ⚙️ = Atribuível individualmente (pode ter ou não)
- ❌ = Sem acesso
2. Herança de Permissões
owner → TODOS os scopes implícitos + gerenciar equipe
admin → TODOS os scopes + gerenciar equipe (convidar, remover, alterar roles/scopes)
member → APENAS scopes atribuídos explicitamente
guest → APENAS `documents` (se atribuído explicitamente)
pending → ZERO acesso (aguardando aceitação de convite)Regras de herança:
- Role superior herda tudo do inferior (owner > admin > member > guest > pending)
- Scopes de
membersão aditivos — cada scope é toggle independente guesttem pool limitado de scopes atribuíveis (default: apenasdocuments)- Pool de scopes atribuíveis a
guesté configurável via filtromiddag_guest_allowed_scopes
3. Regras de Imutabilidade
| Regra | Detalhe |
|---|---|
| Owner não pode ser excluído | API retorna 403 OWNER_IMMUTABLE |
| Owner não pode ter role alterado | API retorna 403 OWNER_IMMUTABLE |
| Owner não pode ter scopes alterados | Scopes ignorados — owner sempre tem todos |
| Um owner por organization | Exatamente 1 owner. Nunca 0, nunca 2+. |
| Transferência de ownership | Operação especial: owner atual → admin, novo membro → owner |
| Apenas owner pode transferir | Admin não pode se autopromover a owner |
4. Fluxo de Convite
1. Admin (ou owner) cria convite
├── Dados: email, role (member/guest), scopes selecionados
└── Validação: email único por org, role ≤ role do invitador
2. Sistema gera token
├── Token: hash criptográfico, TTL configurável (default: 7 dias)
├── Collaborator criado com role `pending`
└── Email de convite enviado (hook: `middag_collaborator_invited`)
3. Destinatário clica no link
├── Token validado (existência + TTL + não-usado)
├── Se usuário WP existe: vincula
└── Se não existe: formulário de criação de conta (nome + senha)
4. Aceitação
├── Role muda de `pending` para role definido no convite
├── Scopes atribuídos conforme convite
└── Hook: `middag_collaborator_accepted`
5. Expiração (se não aceito)
├── Token expira após TTL
├── Collaborator `pending` pode ser re-convidado (novo token)
└── Admin pode cancelar convite a qualquer momento5. Limites Configuráveis
| Limite | Default | Configurável Via | Notas |
|---|---|---|---|
Collaborators com scope tickets | 5 | POLICY (por org ou global) | Reflete termos: "5 por conta de suporte" |
| Collaborators total por org | ∞ | Sem limite default | Pode ser limitado via POLICY se necessário |
| Convites pendentes simultâneos | 20 | Setting global | Previne spam de convites |
| TTL do token de convite | 7 dias | Setting global | — |
| Guest scopes atribuíveis | documents | Filtro middag_guest_allowed_scopes | Expansível por código |
6. Operações por Role
| Operação | owner | admin | member | guest |
|---|---|---|---|---|
| Ver dados da org | ✅ | ✅ | ⚙️ * | ❌ |
| Editar dados da org | ✅ | ✅ | ❌ | ❌ |
| Convidar collaborator | ✅ | ✅ | ❌ | ❌ |
| Remover collaborator | ✅ | ✅ | ❌ | ❌ |
| Alterar role de collaborator | ✅ | ✅ ** | ❌ | ❌ |
| Alterar scopes de collaborator | ✅ | ✅ | ❌ | ❌ |
| Transferir ownership | ✅ | ❌ | ❌ | ❌ |
| Aceitar quote | ✅ | ⚙️ | ⚙️ | ❌ |
| Criar service request | ✅ | ⚙️ | ⚙️ | ❌ |
* Com scope organization ** Admin pode promover member→admin e rebaixar admin→member, mas nunca promover a owner
7. Organization Schema
7.1 Dados Estruturais
| Campo | Tipo | Obrigatório | Notas |
|---|---|---|---|
| Nome (comercial) | string | sim | Min 2, max 255 caracteres |
| Razão social | string | nao | Nome legal para documentos fiscais |
| Tax ID (CNPJ/EIN/VAT) | string | nao | Validação por algoritmo (CNPJ: 14 dígitos + dígito verificador). Único entre orgs ativas |
| Tax ID secundário (IE/IM) | string | nao | Inscrição Estadual ou Municipal |
| Email principal | string | sim | RFC 5322 válido. Usado para notificações e comunicações |
| Email fiscal | string | nao | Email específico para documentos fiscais |
| Telefone | string | nao | Formato internacional recomendado: +5511999998888 |
| Website | string | nao | URL do site da organização |
| Endereço (logradouro) | string | nao | — |
| Número do endereço | string | nao | Separado do logradouro |
| Complemento | string | nao | — |
| Bairro | string | nao | — |
| Cidade | string | nao | — |
| Estado | string | nao | Sigla de 2 letras (BR) ou abreviação |
| País | string | nao | ISO 3166-1 alpha-2 recomendado |
| CEP/ZIP | string | nao | Somente números. Brasil: 8 dígitos |
| Código IBGE | string | nao | 7 dígitos. Usado em integrações fiscais e NF-e |
stripe_customer_id_br | string | nao | Formato: cus_XXXXXXXXXXXXXXX. Criado na primeira operação financeira BR |
stripe_customer_id_global | string | nao | Formato: cus_XXXXXXXXXXXXXXX. Criado na primeira operação financeira GLOBAL |
hubspot_company_id_br | string | nao | ID numérico. Sincronização bidirecional |
hubspot_company_id_global | string | nao | ID numérico. Sincronização bidirecional |
| Tipo da organização | enum | nao | company (default), individual, holding |
| Status de verificação | enum | nao | pending (default), under_review, verified, rejected |
| Data de verificação | datetime | nao | Preenchido quando status transiciona para verified |
parent_organization_id | nullable | nao | FK para org pai. Null = independente |
| Company context | enum | nao | middag_br / middag_global — roteamento default |
| Notas internas | text | nao | Observações administrativas |
| Contato de suporte | string | nao | Email/telefone do contato de suporte da org |
| Contato financeiro | string | nao | Email/telefone do contato financeiro da org |
| Requer fatura para pagamento | bool | nao | Default: false. Organizações públicas/governamentais |
| Parâmetros fiscais (1-5) | string | nao | Campos customizáveis para regime tributário |
7.2 Status Operacional
| Status lógico | Descrição |
|---|---|
active | Operação normal, acesso completo ao portal |
inactive | Desativada voluntariamente, sem acesso ao portal |
suspended | Suspensa por inadimplência ou violação, bloqueada |
Transições válidas: active → inactive, active → suspended, inactive → active, suspended → active. Não é possível ir de suspended para inactive sem passar por active.
8. Collaborator Schema
8.1 Campos Estruturais
| Campo | Tipo | Obrigatório | Notas |
|---|---|---|---|
| User ID | int | sim | Referência ao usuário WordPress |
| Organization ID | int | sim | Referência à Organization. Combinação user+org é única |
| string | sim | Duplicado do user email para queries e convites | |
| Role | enum | sim | owner, admin, member, guest, pending |
| Invite status | enum | sim | accepted, pending, rejected |
| Invited at | datetime | auto | Data/hora do convite |
| Accepted at | datetime | nao | Preenchido quando convite é aceito |
| Invite token | string | nao | Hash criptográfico para aceitação |
| Token expiration | datetime | nao | TTL do token (default: 7 dias) |
| Deleted at | datetime | nao | Soft delete |
8.2 Scopes (campos booleanos)
Cada scope é armazenado como campo booleano individual com default false:
| Scope | Descrição | Novo? |
|---|---|---|
organization | Gerenciamento de dados e configurações da org | Não |
finances | Faturas, pagamentos, métodos de pagamento, extratos | Não |
orders | Pedidos WooCommerce: listar, detalhes, status | Não |
licenses | Licenças: ativar, desativar, domínios, renovar | Não |
tickets | Tickets de suporte: criar, responder, histórico | Não |
quotes | Orçamentos: listar, aceitar, rejeitar, status | Sim |
contracts | Contratos: visualizar, baixar PDF, renovações | Sim |
documents | Documentos fiscais, certificados, comprovantes | Sim |
downloads | Plugins, temas, assets vinculados a licenças/pedidos | Sim |
8.3 Scopes Default por Role
Scopes atribuídos automaticamente na criação do collaborator (customizáveis depois):
| Scope | owner (implícito) | admin | member | guest |
|---|---|---|---|---|
| organization | true | false | false | false |
| finances | true | true | false | false |
| orders | true | true | true | false |
| licenses | true | true | true | false |
| tickets | true | true | true | true |
| quotes | true | true | false | false |
| contracts | true | true | false | false |
| documents | true | true | true | false |
| downloads | true | true | true | false |
9. Lógica de Verificação de Scope
Pseudocódigo da verificação de acesso:
function hasScope(collaborator, scope):
IF org.status == suspended OR org.status == inactive:
RETURN false // org bloqueada = sem acesso
IF collaborator.role == "owner":
RETURN true // owner tem todos os scopes implicitamente
IF collaborator.role == "pending":
RETURN false // convite pendente = sem acesso
IF collaborator.inviteStatus != "accepted":
RETURN false // convite não aceito = sem acesso
RETURN collaborator.scopes[scope] == trueOrdem de prioridade:
- Status da Organization (suspended/inactive → negar tudo)
- Role do collaborator (owner → tudo; pending → nada)
- Status do convite (deve ser accepted)
- Scope individual (campo booleano)
10. Role Transition Rules
| De | Para | Gatilho | Quem Pode |
|---|---|---|---|
| pending | member | Convite aceito (role definido) | Sistema (auto) |
| pending | guest | Convite aceito (role definido) | Sistema (auto) |
| pending | admin | Convite aceito (role definido) | Sistema (auto) |
| member | admin | Promoção | owner |
| admin | member | Rebaixamento | owner |
| guest | member | Promoção | owner, admin |
| member | guest | Rebaixamento | owner, admin |
| admin | owner | Transferência de propriedade | owner (atual) |
Qualquer role (exceto owner) pode ser removido (delete do registro). Owner requer transferência de propriedade antes da remoção.
11. Middleware Enforcement (RBAC)
11.1 Cadeia de Verificação
Requisições à API REST passam por validação em camadas antes de atingir o controller:
Request → JWT válido → Organization válida → Role válido → Scope permitido → Controller- JWT válido — token de autenticação verificado
- Organization válida — header
X-Middag-Organizationverificado: usuário é collaborator da org informada - Role válido — collaborator tem role ativo (não
pending) - Scope permitido — rota declara scopes necessários; middleware verifica contra scopes do collaborator
11.2 Declaração de Scopes por Rota
Cada rota da API declara os scopes necessários na sua definição. Rotas sem declaração de scope são bloqueadas (fail-closed).
11.3 Company Context
O header X-Middag-Company (middag_br ou middag_global) funciona como filtro de dados, não como scope:
middag_br→ conta Stripe BR, conta HubSpot BR, serviços BRmiddag_global→ conta Stripe GLOBAL, conta HubSpot GLOBAL, plugins/SaaS
Um collaborator com scope orders no contexto middag_br vê apenas pedidos da conta Stripe BR.
12. Verification Workflow
12.1 Estados de Verificação
| Estado | Descrição |
|---|---|
pending | Org recém-criada, aguardando documentação |
under_review | Documentação enviada, em análise pelo admin |
verified | Aprovada — acesso completo a operações comerciais |
rejected | Documentação insuficiente, requer reenvio |
12.2 Transições
| De | Para | Gatilho | Ator |
|---|---|---|---|
pending | under_review | Cliente envia documentação | Cliente (App) |
under_review | verified | Verificação aprovada | Admin / Auto |
under_review | rejected | Verificação reprovada | Admin |
rejected | under_review | Cliente reenvia documentação | Cliente (App) |
12.3 Verificação Automática por Tax ID
Quando disponível (ex: CNPJ no Brasil), o sistema pode verificar automaticamente via API fiscal:
- Validar que o Tax ID existe e está ativo
- Confirmar razão social
- Se todas as validações passarem →
verifiedautomaticamente - Se falhar → permanece
under_reviewpara revisão manual
12.4 Restrições por Status de Verificação
| Funcionalidade | pending | under_review | verified | rejected |
|---|---|---|---|---|
| Acessar portal | Sim | Sim | Sim | Sim |
| Ver dados próprios | Sim | Sim | Sim | Sim |
| Realizar compras | Nao | Nao | Sim | Nao |
| Aceitar quotes | Nao | Nao | Sim | Nao |
| Emitir NF | Nao | Nao | Sim | Nao |
| Criar tickets | Sim | Sim | Sim | Sim |
| Baixar produtos | Sim* | Sim* | Sim | Sim* |
* Produtos já adquiridos antes da mudança de status permanecem acessíveis.
13. Hierarchy Rules
13.1 Estrutura
Hierarquia limitada. Cada nível deve ser entidade faturável (Tax ID próprio). Não recursiva — profundidade máxima fixa e configurável por admin.
Holding Corp (tipo: holding, parent: null)
├── Subsidiária A (parent: Holding.ID)
├── Subsidiária B (parent: Holding.ID)
└── Subsidiária C (parent: Holding.ID)13.2 Regras de Validação
| Regra | Descrição |
|---|---|
| Profundidade máxima | Configurável (default: 3 níveis). Rejeitar se exceder. |
| Holdings não são subsidiárias | Org tipo holding não pode ter parent |
| Sem auto-referência | parent_organization_id não pode ser o próprio ID |
| Independência operacional | Cada subsidiária tem seus próprios collaborators, pedidos, etc. |
| Visão consolidada | Holding pode ver dados agregados das subsidiárias |
13.3 Herança de Permissões na Hierarquia
Configurável por organização holding:
| Modo | Comportamento |
|---|---|
independent (default) | Cada org gerencia seus próprios collaborators. Collaborators da holding não têm acesso às subsidiárias. |
inherit_read | Collaborators da holding com scope organization ganham leitura nas subsidiárias. |
full_inherit | Owner/admin da holding são espelhados nas subsidiárias com role admin e mesmos scopes. |
13.4 Hooks
| Hook | Parâmetros | Quando |
|---|---|---|
middag_organization_verified | $organization | Org verificada (sync HubSpot, etc) |
middag_subsidiary_linked | $child, $parent | Subsidiária vinculada a holding |
middag_subsidiary_unlinked | $child, $parent | Subsidiária desvinculada |
14. Invite System Enhancements
| Feature | Descrição |
|---|---|
| Convites em lote | Convidar múltiplos membros de uma vez com mesmo role e scopes |
| Templates de role | Conjuntos predefinidos de scopes para roles comuns (ex: "Financeiro" = finances + orders + quotes) |
| Expiração de convite | Convites pendentes expiram após TTL configurável (default: 7 dias) |
| Reenvio | Possibilidade de reenviar convites expirados ou não aceitos |
| Cancelamento | Admin pode cancelar convite pendente a qualquer momento |
Templates de role são configuráveis e extensíveis. Não substituem a atribuição individual — são atalhos para configurações comuns.
15. OrganizationService — Responsabilidades
O OrganizationService centraliza todas as operações do domínio Organization:
- CRUD de organizações (criação, leitura, atualização, exclusão lógica)
- Workflow de verificação (transições de estado: pending → under_review → verified/rejected)
- Sincronização Stripe (criação e atualização de customers em ambas as contas BR + GLOBAL)
- Sincronização HubSpot (criação e atualização de companies em ambas as contas BR + GLOBAL)
- Gestão de collaborators (convites, aceitação, alteração de roles/scopes)
- Hierarquia organizacional (vinculação/desvinculação de subsidiárias, propagação de permissões)