Políticas de autorização — API
CRUD de políticas de autorização por tenant e uma rota para avaliar, sob demanda, o que uma
política decidiria para um ator específico. Uma política é a camada ABAC (controle por atributo)
que fica em cima do RBAC: ela nunca concede uma permissão que o RBAC já não tenha concedido —
só pode confirmar (allow) ou derrubar (deny) uma concessão RBAC existente, com base em
condições sobre atributos da requisição.
Funcionamento
- Criar (
POST /tenants/:tenantId/policies): a política é amarrada a umapermission_namedo catálogo atribuível, umeffect(allow/deny), umapriority(0 a 10.000, maior primeiro), até 50conditionse até 100target_role_ids(papéis aos quais a política se aplica; vazio = qualquer papel). Nome único por tenant. - Listar/buscar/atualizar/retirar: mesmo padrão de concorrência otimista de
Papéis —
PATCHexigeexpected_version;DELETEtambém (no corpo, diferente de papéis que usamIf-Match). Atualizar reaplica toda validação de criação sobre o resultado final (tenant e papéis-alvo continuam ativos, nome continua único). - Avaliar (
POST /tenants/:tenantId/policies/:policyId/evaluations): roda a política contra umactor_user_ide um escopo (tenant, unidade, ou recurso específico) sem de fato autorizar nada — é uma simulação síncrona, útil para debug/suporte ("por que este usuário foi barrado?"). - Como a decisão é calculada (
EvaluateIdentityAuthorizationPoliciesService, compartilhado entre esta avaliação pontual, oAuthorizationGuarde a Inspeção):- Entre as políticas ativas que citam a permissão em questão, mantém só as que se aplicam a
algum papel do ator (
target_role_idsvazio conta como "se aplica a todos"). - Ordena por
prioritydecrescente; em empate de prioridade,denyvenceallow; em empate de prioridade e efeito, desempata por nome. - A primeira política cujas todas as condições combinam com os atributos do contexto
(
tenantId,unitId,resourceType,resourceIdetc.) decide o resultado. - Se nenhuma política combina, o resultado é
allowcomreason_code: no-applicable-policy— silêncio de política nunca nega.
- Entre as políticas ativas que citam a permissão em questão, mantém só as que se aplicam a
algum papel do ator (
- Condições: comparam um atributo por caminho (
a.b.c) contra um valor literal ou contra outro atributo (valuecomeçando com$, ex.:$resource.tenantId). Operadores:equals,not_equals,in,not_in,contains,exists. Segmentos__proto__/constructor/prototypesão bloqueados explicitamente na resolução do caminho (proteção contra poluição de protótipo).
Endpoints
| Método | Rota | Descrição |
|---|---|---|
| POST | /v1/tenants/:tenantId/policies | Cria uma política |
| GET | /v1/tenants/:tenantId/policies | Lista políticas do tenant |
| GET | /v1/tenants/:tenantId/policies/:policyId | Lê uma política |
| PATCH | /v1/tenants/:tenantId/policies/:policyId | Atualiza uma política |
| DELETE | /v1/tenants/:tenantId/policies/:policyId | Retira uma política |
| POST | /v1/tenants/:tenantId/policies/:policyId/evaluations | Avalia a política contra um ator/escopo, sem autorizar |
Versão: v1
Swagger: Identity — Authorization policies · Rota (Dev): http://localhost:3000/v1/tenants/{tenantId}/policies
Permissões
| Rota | Guards | Permissão exigida (escopo tenant) |
|---|---|---|
POST /policies, PATCH /:policyId | IdentityOAuthAccessTokenGuard, AuthorizationGuard | policy:write |
DELETE /:policyId | idem | policy:delete |
GET /policies, GET /:policyId, POST /:policyId/evaluations | idem | policy:read |
Headers
| Header | Obrigatório | Descrição |
|---|---|---|
Authorization | Sim | Bearer <access_token> |
Path parameters
| Nome | Tipo | Obrigatório | Descrição |
|---|---|---|---|
tenantId | uuid | Sim | Tenant dono da política |
policyId | uuid | Sim, nas rotas de item | Política alvo |
Query parameters
GET /policies — IdentityAuthorizationPolicyListQueryRequest:
| Nome | Tipo | Obrigatório | Default | Descrição |
|---|---|---|---|---|
page | integer | Não | 1 | Página |
limit | integer | Não | 20 | Itens por página (1–100) |
search | string | Não | — | Busca textual (até 128 caracteres) |
effect | allow | deny | Não | — | Filtra pelo efeito |
is_active | boolean | Não | — | Filtra por ativa/inativa |
permission_name | string | Não | — | Filtra pela permissão-alvo (até 128 caracteres) |
resource | string | Não | — | Filtra por recurso; deve ser um resource existente no catálogo atribuível, senão IDENTITY_AUTHORIZATION_POLICY_RESOURCE_INVALID |
Body
POST /policies — CreateIdentityAuthorizationPolicyRequest:
json{"name": "deny-read-only-export","permission_name": "user:export","effect": "deny","priority": 100,"target_role_ids": ["3f2a...uuid"],"conditions": [ { "attribute": "resource.tenantId", "operator": "not_equals", "value": "$actor.tenantId" } ],"is_active": true}
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
name | string | Sim | 1 a 128 caracteres |
description | string | Não | até 500 caracteres |
permission_name | string | Sim | deve existir e ser atribuível no catálogo, senão PERMISSION_NOT_ASSIGNABLE |
effect | allow | deny | Sim | — |
conditions[] | object | Não | até 50 itens |
conditions[].attribute | string | Sim | até 128 caracteres, sem segmento vazio |
conditions[].operator | enum | Sim | equals, not_equals, in, not_in, contains, exists |
conditions[].value | string | string[] | Sim | string até 256 caracteres, ou array de até 100 strings ≤ 256 caracteres cada; in/not_in exigem array |
priority | integer | Não (default 0) | 0 a 10.000 |
target_role_ids | uuid[] | Não | até 100, sem duplicatas; cada um deve ser um papel ativo visível no tenant |
is_active | boolean | Não (default true) | — |
PATCH /:policyId: mesmos campos, todos opcionais, mais expected_version (integer ≥ 1,
obrigatório).
POST /:policyId/evaluations — EvaluateIdentityAuthorizationPolicyRequest:
json{ "actor_user_id": "77aa...uuid", "unit_id": "9b3c...uuid" }
| Campo | Tipo | Obrigatório | Validação |
|---|---|---|---|
actor_user_id | uuid | Sim | — |
unit_id | uuid | Não | se informado, precisa pertencer e estar ativo no tenant da política |
resource_id | uuid | Não | — |
resource_type | string | Não | até 128 caracteres |
Response
2xx — IdentityAuthorizationPolicyResponse:
json{"data": {"id": "5e1c...uuid","tenant_id": "1f2a...uuid","name": "deny-read-only-export","description": null,"permission_name": "user:export","effect": "deny","conditions": [ { "attribute": "resource.tenantId", "operator": "not_equals", "value": "$actor.tenantId" } ],"priority": 100,"target_role_ids": ["3f2a...uuid"],"is_active": true,"version": 1,"created_at": "2026-09-01T12:00:00.000Z","updated_at": "2026-09-01T12:00:00.000Z"}}
POST /:policyId/evaluations — 200 (IdentityAuthorizationPolicyDecisionResponse):
json{ "data": { "effect": "deny", "matched_policy_id": "5e1c...uuid", "reason_code": "policy-deny" } }
Mapa de campos
| Campo | Tipo | Valores possíveis | Default | Descrição |
|---|---|---|---|---|
effect | string | allow, deny | — | Efeito da política |
priority | integer | 0–10.000 | 0 | Maior prioridade é avaliada primeiro; empate resolve para deny |
target_role_ids | uuid[] | — | [] | Vazio = aplica-se a qualquer papel do ator |
reason_code (avaliação) | string | policy-allow, policy-deny, no-applicable-policy | — | Motivo da decisão simulada |
matched_policy_id | uuid | null | — | null quando no-applicable-policy | Política que decidiu, se houver |
Erros
| Classe de erro | errorCode | Status | Quando ocorre |
|---|---|---|---|
| (validação de payload) | BAD_REQUEST / IDENTITY_AUTHORIZATION_POLICY_CONDITION_INVALID / IDENTITY_AUTHORIZATION_POLICY_INVALID | 400 | payload inválido, nome/prioridade/condições fora dos limites, operador incompatível com o valor |
| — | IDENTITY_AUTHORIZATION_POLICY_RESOURCE_INVALID | 400 | filtro resource de GET /policies não existe no catálogo |
| — | IDENTITY_AUTHORIZATION_POLICY_EXPECTED_VERSION_INVALID | 400 | expected_version ausente/não positivo em PATCH/DELETE |
| — | PERMISSION_NOT_ASSIGNABLE | 400 | permission_name não existe ou está retired no catálogo |
UnauthenticatedError | IDENTITY_INVALID_OAUTH_ACCESS_TOKEN | 401 | access token ausente/inválido |
ForbiddenAction (guard) | — | 403 | ator sem policy:read/policy:write/policy:delete no tenant |
IdentityAuthorizationPolicyNotFoundError | IDENTITY_AUTHORIZATION_POLICY_NOT_FOUND | 404 | política inexistente/de outro tenant; tenant inativo; algum target_role_id inválido/de outro tenant; unidade de avaliação fora do tenant |
IdentityAuthorizationPolicyAlreadyExistsError | IDENTITY_AUTHORIZATION_POLICY_ALREADY_EXISTS | 409 | nome já usado por outra política ativa do tenant |
IdentityAuthorizationPolicyVersionConflictError | IDENTITY_AUTHORIZATION_POLICY_VERSION_CONFLICT | 409 | expected_version não bate com a versão atual |
Regras de negócio
| ID | Regra | Comportamento esperado |
|---|---|---|
| RN-01 | Uma política nunca amplia o que o RBAC concedeu | só pode confirmar (allow) ou negar (deny) uma permissão que o bitmask do papel já concede |
| RN-02 | Maior priority decide primeiro; empate favorece deny | ordenação determinística confirmada em teste (gives deny precedence when allow and deny have equal priority) |
| RN-03 | Política inativa nunca participa da decisão | is_active: false é ignorada mesmo que combine com tudo o mais |
| RN-04 | Sem política aplicável, o resultado é allow | silêncio de política nunca bloqueia; reason_code: no-applicable-policy |
| RN-05 | target_role_ids restringe a quais papéis a política se aplica | vazio = todos; um papel-alvo atribuído só em outro tenant não ativa a política (confirmado em teste) |
| RN-06 | Nome de política é único por tenant, entre as ativas | criar/atualizar com nome em conflito → 409 |
| RN-07 | Atualização e retirada exigem concorrência otimista | expected_version deve casar com a versão persistida |
| RN-08 | Caminho de atributo de condição bloqueia poluição de protótipo | segmentos __proto__, constructor, prototype sempre resolvem para undefined |
Compliance
| Órgão / norma | Exigência | Como a rota atende |
|---|---|---|
| LGPD / HIPAA | Controle de acesso refinado por atributo, auditável | toda escrita grava outbox (identity.authorization_policy.created/updated/retired) com antes/depois |
| HIPAA / ANVISA | Trilha imutável de mudança de controle de acesso | idem — inclui effect e permission_name nos metadados do evento |
Variáveis de ambiente
Nenhuma variável de ambiente é consumida diretamente por estas rotas.
Tempo médio de resposta
A confirmar — responsável: time de Identity; data: 24/09/2026. Sem medição publicada.
Requisitos não funcionais
| Requisito | Definição |
|---|---|
| Idempotência | Não para POST (cada chamada cria uma nova política); PATCH/DELETE protegidas por versão |
| Paginação | Sim, em GET /policies (1–100, default 20) |
| Rate limit | Não identificado |
| Cache | Não identificado (a avaliação é síncrona a cada chamada) |
| Auditoria | Sim — outbox em toda criação/atualização/retirada |
Relacionado
- 📂 Módulo: Authorization
- 🔁 Consumida por:
AuthorizationGuard(toda rota protegida do backend) e Inspeção de autorização - 🔁 Depende de: Papéis (papéis-alvo) e Catálogo de permissões (permissão-alvo)