Permissão
Quando usar: toda rota tem uma. A permissão é o que decide se uma chamada passa — o serviço não decide por papel, por claim de e-mail, nem por lista de administradores.
Uma permissão é local ao produto (id curto, no iam.yaml) e vale qualificada
(<produto>.<local>) no resto do sistema: no api.yaml da rota, no token expandido pelo gateway e
no código.
1. Declare a permissão no iam.yaml
nnp/iam.yaml:
apiVersion: console.nnumbers.io/v1alpha3
kind: IamDescriptor
product: sample
permissions:
- {id: context.read, description: Ver o contexto verificado pelo serviço (whoami)}
- {id: item.read, description: Ver itens e o estado das varreduras}
- {id: item.create, description: Criar item}
- {id: item.delete, description: Apagar item}
- {id: item.scan, description: Iniciar a varredura assíncrona de um item}
roles:
viewer: {description: Leitura, permissions: [context.read, item.read]}
operator: {description: "Leitura, criação e varredura", permissions: [context.read, item.read, item.create, item.scan]}
admin: {description: Tudo do produto, permissions: ["*"]}
2. Amarre a rota à permissão
nnp/api.yaml:
routes:
- {id: whoami, method: GET, path: /whoami, permission: sample.context.read}
- {id: items.get, method: GET, path: "/items/{item_id}", permission: sample.item.read}
- {id: items.delete, method: DELETE, path: "/items/{item_id}", permission: sample.item.delete, idempotency: optional, max_body_bytes: 4096}
3. Confira dentro do handler quando o motivo for outro
A permissão da rota é conferida pelo runtime antes do handler (o serviço refaz a conferência do
lado dele: o gateway expande, o serviço não confia). Dentro do handler você chama
ServiceContext só quando a operação precisa de uma permissão a mais — um caminho de escrita
dentro de uma rota de leitura, um campo administrativo:
@NnsRoute("whoami")
Views.WhoamiView whoami(ServiceContext sc, Introspection who) {
// A permissão da rota (`sample.context.read`) já passou. Aqui a conferência é EXPLÍCITA: o
// `whoami` também mostra o que a chamada NÃO tem, e isso não autoriza nada.
return Views.WhoamiView.of(who, sc, service.module.iam(), service.roleText(), SampleModule.MARKER, ScanEngine.ENGINE);
}
// A permissão QUALIFICADA, como o gateway a expande e o serviço a recebe.
if (sc.can("sample.item.delete")) {
// apaga de verdade
} else {
// soft delete
}
// Ou exigindo: sem ela, é 403 `permission_denied/permission_required`, com a permissão em details.
sc.require("sample.item.delete");
O whoami responde, para cada permissão do produto, o que esta chamada tem:
Map<String, Boolean> verified = new TreeMap<>();
for (Permission p : iam.permissions()) {
String q = p.id();
if (!Descriptor.productOf(q).equals(iam.product())) {
q = iam.product() + "." + q;
}
verified.put(q, sc.can(q));
}
Como testar
O teste que importa é o negativo: com o token sem a permissão, a resposta é 403 — e não 500, não
200 e não "só esconde no front". Ele está em java/src/test/java/.../app/HttpAuthorizationTest.java:
@Test
void routeWithoutThePermissionIs403WithTheReason() {
// O token é válido e o usuário existe: o que falta é a PERMISSÃO da rota.
String token = ctxToken(ctx("whoami", "sample.item.read"), "GET", "/v1/sample/whoami", null);
HttpResponse<String> r = call("GET", "/v1/sample/whoami", token, null);
assertThat(r.statusCode()).isEqualTo(403);
JsonNode error = body(r).get("error");
assertThat(error.get("code").asText()).isEqualTo("permission_denied");
assertThat(error.get("reason").asText()).isEqualTo("permission_required");
assertThat(error.get("details").get("permission").asText()).isEqualTo("sample.context.read");
}
@Test
void whoamiShowsTheVerifiedContextOfTheToken() {
String token = ctxToken(ctx("whoami", "sample.context.read", "sample.item.read"), "GET", "/v1/sample/whoami", null);
HttpResponse<String> r = call("GET", "/v1/sample/whoami", token, null);
assertThat(r.statusCode()).isEqualTo(200);
JsonNode b = body(r);
assertThat(b.get("verified").get("sample.context.read").asBoolean()).isTrue();
assertThat(b.get("verified").get("sample.item.scan").asBoolean()).isFalse();
}
E, do lado do descritor, um teste que confere que as permissões declaradas são exatamente as esperadas (assim, trocar o id de uma permissão quebra o build em vez de quebrar a autorização em produção):
@Test
void rolesOnlyUsePermissionsOfTheProduct() {
Iam iam = MODULE.iam();
assertThat(iam.permissions()).extracting(p -> p.id())
.containsExactly("sample.context.read", "sample.item.read", "sample.item.create",
"sample.item.delete", "sample.item.scan");
assertThat(iam.roles().get("viewer").permissions())
.containsExactly("sample.context.read", "sample.item.read");
}
Erros comuns
- Usar o id LOCAL no código —
sc.can("item.read")nunca é verdade: o contexto traz a forma qualificada (sample.item.read). - Inventar permissão fora do
iam.yaml— odescriptors validaterecusa os dois lados: a permissão da rota que não existe no descritor e o papel que agrupa permissão inexistente. - Decidir por papel —
sc.roles()é informativo. Quem autoriza é a permissão. - Conferir depois de mexer —
sc.require(...)é a primeira linha da operação, antes de qualquer escrita e antes de qualquer leitura de dado sensível. - Duplicar a conferência da rota — o runtime já fez; repetir a mesma checagem no handler só polui. Confira o que é a mais.
- Achar que
impliesdá permissão no serviço —impliesexpande o escopo do papel no gateway; no serviço chega a lista já expandida. - Esquecer que
admincom"*"vale só as permissõestenantdo produto — poder de plataforma não se herda por wildcard.
Was this page helpful?
Report a problem on this pageDo not send passwords, keys, tokens, or customer data.