Skip to content

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 / Roleowneradminmemberguestpending
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 member são aditivos — cada scope é toggle independente
  • guest tem pool limitado de scopes atribuíveis (default: apenas documents)
  • Pool de scopes atribuíveis a guest é configurável via filtro middag_guest_allowed_scopes

3. Regras de Imutabilidade

RegraDetalhe
Owner não pode ser excluídoAPI retorna 403 OWNER_IMMUTABLE
Owner não pode ter role alteradoAPI retorna 403 OWNER_IMMUTABLE
Owner não pode ter scopes alteradosScopes ignorados — owner sempre tem todos
Um owner por organizationExatamente 1 owner. Nunca 0, nunca 2+.
Transferência de ownershipOperação especial: owner atual → admin, novo membro → owner
Apenas owner pode transferirAdmin 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 momento

5. Limites Configuráveis

LimiteDefaultConfigurável ViaNotas
Collaborators com scope tickets5POLICY (por org ou global)Reflete termos: "5 por conta de suporte"
Collaborators total por orgSem limite defaultPode ser limitado via POLICY se necessário
Convites pendentes simultâneos20Setting globalPrevine spam de convites
TTL do token de convite7 diasSetting global
Guest scopes atribuíveisdocumentsFiltro middag_guest_allowed_scopesExpansível por código

6. Operações por Role

Operaçãoowneradminmemberguest
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

CampoTipoObrigatórioNotas
Nome (comercial)stringsimMin 2, max 255 caracteres
Razão socialstringnaoNome legal para documentos fiscais
Tax ID (CNPJ/EIN/VAT)stringnaoValidação por algoritmo (CNPJ: 14 dígitos + dígito verificador). Único entre orgs ativas
Tax ID secundário (IE/IM)stringnaoInscrição Estadual ou Municipal
Email principalstringsimRFC 5322 válido. Usado para notificações e comunicações
Email fiscalstringnaoEmail específico para documentos fiscais
TelefonestringnaoFormato internacional recomendado: +5511999998888
WebsitestringnaoURL do site da organização
Endereço (logradouro)stringnao
Número do endereçostringnaoSeparado do logradouro
Complementostringnao
Bairrostringnao
Cidadestringnao
EstadostringnaoSigla de 2 letras (BR) ou abreviação
PaísstringnaoISO 3166-1 alpha-2 recomendado
CEP/ZIPstringnaoSomente números. Brasil: 8 dígitos
Código IBGEstringnao7 dígitos. Usado em integrações fiscais e NF-e
stripe_customer_id_brstringnaoFormato: cus_XXXXXXXXXXXXXXX. Criado na primeira operação financeira BR
stripe_customer_id_globalstringnaoFormato: cus_XXXXXXXXXXXXXXX. Criado na primeira operação financeira GLOBAL
hubspot_company_id_brstringnaoID numérico. Sincronização bidirecional
hubspot_company_id_globalstringnaoID numérico. Sincronização bidirecional
Tipo da organizaçãoenumnaocompany (default), individual, holding
Status de verificaçãoenumnaopending (default), under_review, verified, rejected
Data de verificaçãodatetimenaoPreenchido quando status transiciona para verified
parent_organization_idnullablenaoFK para org pai. Null = independente
Company contextenumnaomiddag_br / middag_global — roteamento default
Notas internastextnaoObservações administrativas
Contato de suportestringnaoEmail/telefone do contato de suporte da org
Contato financeirostringnaoEmail/telefone do contato financeiro da org
Requer fatura para pagamentoboolnaoDefault: false. Organizações públicas/governamentais
Parâmetros fiscais (1-5)stringnaoCampos customizáveis para regime tributário

7.2 Status Operacional

Status lógicoDescrição
activeOperação normal, acesso completo ao portal
inactiveDesativada voluntariamente, sem acesso ao portal
suspendedSuspensa 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

CampoTipoObrigatórioNotas
User IDintsimReferência ao usuário WordPress
Organization IDintsimReferência à Organization. Combinação user+org é única
EmailstringsimDuplicado do user email para queries e convites
Roleenumsimowner, admin, member, guest, pending
Invite statusenumsimaccepted, pending, rejected
Invited atdatetimeautoData/hora do convite
Accepted atdatetimenaoPreenchido quando convite é aceito
Invite tokenstringnaoHash criptográfico para aceitação
Token expirationdatetimenaoTTL do token (default: 7 dias)
Deleted atdatetimenaoSoft delete

8.2 Scopes (campos booleanos)

Cada scope é armazenado como campo booleano individual com default false:

ScopeDescriçãoNovo?
organizationGerenciamento de dados e configurações da orgNão
financesFaturas, pagamentos, métodos de pagamento, extratosNão
ordersPedidos WooCommerce: listar, detalhes, statusNão
licensesLicenças: ativar, desativar, domínios, renovarNão
ticketsTickets de suporte: criar, responder, históricoNão
quotesOrçamentos: listar, aceitar, rejeitar, statusSim
contractsContratos: visualizar, baixar PDF, renovaçõesSim
documentsDocumentos fiscais, certificados, comprovantesSim
downloadsPlugins, temas, assets vinculados a licenças/pedidosSim

8.3 Scopes Default por Role

Scopes atribuídos automaticamente na criação do collaborator (customizáveis depois):

Scopeowner (implícito)adminmemberguest
organizationtruefalsefalsefalse
financestruetruefalsefalse
orderstruetruetruefalse
licensestruetruetruefalse
ticketstruetruetruetrue
quotestruetruefalsefalse
contractstruetruefalsefalse
documentstruetruetruefalse
downloadstruetruetruefalse

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] == true

Ordem de prioridade:

  1. Status da Organization (suspended/inactive → negar tudo)
  2. Role do collaborator (owner → tudo; pending → nada)
  3. Status do convite (deve ser accepted)
  4. Scope individual (campo booleano)

10. Role Transition Rules

DeParaGatilhoQuem Pode
pendingmemberConvite aceito (role definido)Sistema (auto)
pendingguestConvite aceito (role definido)Sistema (auto)
pendingadminConvite aceito (role definido)Sistema (auto)
memberadminPromoçãoowner
adminmemberRebaixamentoowner
guestmemberPromoçãoowner, admin
memberguestRebaixamentoowner, admin
adminownerTransferência de propriedadeowner (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
  1. JWT válido — token de autenticação verificado
  2. Organization válida — header X-Middag-Organization verificado: usuário é collaborator da org informada
  3. Role válido — collaborator tem role ativo (não pending)
  4. 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 BR
  • middag_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

EstadoDescrição
pendingOrg recém-criada, aguardando documentação
under_reviewDocumentação enviada, em análise pelo admin
verifiedAprovada — acesso completo a operações comerciais
rejectedDocumentação insuficiente, requer reenvio

12.2 Transições

DeParaGatilhoAtor
pendingunder_reviewCliente envia documentaçãoCliente (App)
under_reviewverifiedVerificação aprovadaAdmin / Auto
under_reviewrejectedVerificação reprovadaAdmin
rejectedunder_reviewCliente reenvia documentaçãoCliente (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:

  1. Validar que o Tax ID existe e está ativo
  2. Confirmar razão social
  3. Se todas as validações passarem → verified automaticamente
  4. Se falhar → permanece under_review para revisão manual

12.4 Restrições por Status de Verificação

Funcionalidadependingunder_reviewverifiedrejected
Acessar portalSimSimSimSim
Ver dados própriosSimSimSimSim
Realizar comprasNaoNaoSimNao
Aceitar quotesNaoNaoSimNao
Emitir NFNaoNaoSimNao
Criar ticketsSimSimSimSim
Baixar produtosSim*Sim*SimSim*

* 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

RegraDescrição
Profundidade máximaConfigurável (default: 3 níveis). Rejeitar se exceder.
Holdings não são subsidiáriasOrg tipo holding não pode ter parent
Sem auto-referênciaparent_organization_id não pode ser o próprio ID
Independência operacionalCada subsidiária tem seus próprios collaborators, pedidos, etc.
Visão consolidadaHolding pode ver dados agregados das subsidiárias

13.3 Herança de Permissões na Hierarquia

Configurável por organização holding:

ModoComportamento
independent (default)Cada org gerencia seus próprios collaborators. Collaborators da holding não têm acesso às subsidiárias.
inherit_readCollaborators da holding com scope organization ganham leitura nas subsidiárias.
full_inheritOwner/admin da holding são espelhados nas subsidiárias com role admin e mesmos scopes.

13.4 Hooks

HookParâmetrosQuando
middag_organization_verified$organizationOrg verificada (sync HubSpot, etc)
middag_subsidiary_linked$child, $parentSubsidiária vinculada a holding
middag_subsidiary_unlinked$child, $parentSubsidiária desvinculada

14. Invite System Enhancements

FeatureDescrição
Convites em loteConvidar múltiplos membros de uma vez com mesmo role e scopes
Templates de roleConjuntos predefinidos de scopes para roles comuns (ex: "Financeiro" = finances + orders + quotes)
Expiração de conviteConvites pendentes expiram após TTL configurável (default: 7 dias)
ReenvioPossibilidade de reenviar convites expirados ou não aceitos
CancelamentoAdmin 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)