Pular para o conteúdo principal
Versão: 10.4.1

ITLab.Template.DevPack.Authorization

Policy-based permission system. Provides the infrastructure to declare permissions on endpoints (RequireAuthorization("Pedidos.Create")) and on commands/queries ([RequiresPermission("Pedidos.Edit")]), with a single avaliação delegate at runtime via IPermissionEvaluator.

The DevPack is agnostic about how permissions are sourced. JWT claims, DB lookup with cache, hierarchical inheritance — all of those are decisions for the consuming product. The DevPack provides the contract and the wiring.

Componentes

TipoNomeResponsabilidade
InterfaceIPermissionEvaluatorContract for resolving whether the current context holds a permission
InterfaceIRolePermissionResolverContract for resolving permission strings from a role-name set
ClassRoleBasedPermissionEvaluatorDefault IPermissionEvaluator combining ICurrentUser.UserRoles + IRolePermissionResolver, with per-instance cache
ClassPermissionAuthorizationRequirementIAuthorizationRequirement carrying the permission identifier
ClassPermissionAuthorizationHandlerAuthorizationHandler<...> that delegates to IPermissionEvaluator
ClassPermissionAuthorizationPolicyProviderTreats unknown policy names as permission requirements at endpoint level
AttributeRequiresPermissionAttributeMarks Commands/Queries with the permissions they need; multiple form an AND
ClassAuthorizationBehavior<TRequest, TResponse>Mediator pipeline behavior that enforces [RequiresPermission] before the handler
Static ClassDevPackAuthorizationExtensionsDI registration helpers (AddDevPackAuthorization, AddDevPackRoleBasedPermissionEvaluator<TResolver>)

Como usar

1. Defina a classe estática hierárquica de permissões no projeto consumidor

Permissions catalogue location

Convenção: Application/Common/Authorization/{ProjectName}Permissions.cs, uma única classe estática com classes estáticas aninhadas por feature group:

namespace MyApp.Application.Common.Authorization;

public static class MyAppPermissions
{
public static class Pedidos
{
public const string Create = "Pedidos.Create";
public const string Edit = "Pedidos.Edit";
public const string Read = "Pedidos.Read";
public const string Delete = "Pedidos.Delete";
}

public static class Clientes
{
public const string Create = "Clientes.Create";
public const string Deactivate = "Clientes.Deactivate";
}
}

Use as constantes em dois lugares:

  • RequireAuthorization(MyAppPermissions.X.Y) em endpoints Minimal API.
  • [RequiresPermission(MyAppPermissions.X.Y)] em commands/queries somente quando a permissão for contextual (depende do payload do command) — ver § Permission convention: endpoint vs command.

PermissionsGenerator (em Helpers/) extrai todas as strings:

var allPermissions = PermissionsGenerator.GenerateJson(typeof(MyAppPermissions));

2. Escolha a estratégia de resolução de permissões

Opção A — Role-based (default sugerido na 10.2.0)

Implemente IRolePermissionResolver no projeto consumidor — tipicamente uma classe que consulta a tabela RolePermission com cache em memória ou Redis:

public sealed class DbRolePermissionResolver(MyDbContext db, IMemoryCache cache)
: IRolePermissionResolver
{
public async Task<IReadOnlyCollection<string>> ResolvePermissionsAsync(
IEnumerable<string> roles, CancellationToken ct = default)
{
var key = $"perm:{string.Join('|', roles.OrderBy(r => r))}";
return await cache.GetOrCreateAsync(key, async _ =>
{
var rolesArray = roles.ToArray();
var permissions = await db.RolePermissions
.Where(rp => rolesArray.Contains(rp.RoleName))
.Select(rp => rp.Permission)
.Distinct()
.ToListAsync(ct);
return (IReadOnlyCollection<string>)permissions;
}) ?? [];
}
}

Opção B — Implementação custom

Implemente IPermissionEvaluator direto se as regras forem mais complexas (hierarquia, herança por organização, regras temporais).

3. Registre no Program.cs

services.AddAuthorization();

// Opção A — Role-based:
services.AddDevPackRoleBasedPermissionEvaluator<DbRolePermissionResolver>();

// Opção B — Implementação custom:
// services.AddScoped<IPermissionEvaluator, MyCustomEvaluator>();

services.AddDevPackAuthorization();

4. Aplique no endpoint (Camada 1 — early)

group.MapPost("/", CriarPedido)
.RequireAuthorization(MyAppPermissions.Pedidos.Create);

5. (Opcional) Reforce no Command/Query (Camada 2 — contextual)

[RequiresPermission(MyAppPermissions.Pedidos.Edit)]
public sealed record EditarPedidoCommand(Guid PedidoId, string Novo) : ICommand<EditarPedidoResponse>;

Permission convention: endpoint vs command

  • Default (genérica): declare a permissão somente no endpoint via RequireAuthorization(...). Suficiente para ~90% dos casos.
  • Contextual: adicione [RequiresPermission(...)] no command/query apenas quando a permissão depender do payload (ex.: tenant scoping, ownership de recurso) e precisar ser re-checada em entry-points não-HTTP (background jobs, integration tests bypassando endpoints, mensageria interna).

Declarar a mesma permissão genérica nas duas camadas é redundante (duas chamadas a IPermissionEvaluator por request, sem ganho de segurança).

Em resumo:

  • Endpoint (RequireAuthorization("...")) — avalia early, antes do request body ser deserializado completamente. Default para "pode invocar esta rota?".
  • Pipeline behavior ([RequiresPermission("...")]) — opt-in apenas quando a permissão for contextual ao payload, ou quando o handler é invocado fora de HTTP.

Convenção de naming

Hierárquico com pontos: {Feature}.{Operação} (ex.: Pedidos.Create, Reports.Financeiro.Export).

O que não fazer

  • Não verificar permissões manualmente dentro do corpo do handler — use o sistema declarativo.
  • Não hardcode strings literais de permissões espalhadas — sempre via classe estática {Projeto}Permissions.
  • Não criar sistema paralelo de roles ad-hoc (if (user.Role == "Admin")). Roles vivem no Identity; autorização funcional via permissões.
  • Não substituir PermissionAuthorizationPolicyProvider por uma versão que só atende permissões — o decorator deve ainda fazer fallback ao DefaultAuthorizationPolicyProvider para policies registradas explicitamente.

Extensão pelo produto

  • Produtos podem implementar IPermissionEvaluator com cache (IMemoryCache), banco (com cache TTL) ou claims do JWT.
  • Produtos podem adicionar suas próprias IAuthorizationRequirement para regras avançadas (ex.: ownership, tenant scoping); o DevPack não opina sobre essas extensões.
  • Produtos podem combinar [RequiresPermission] com outros atributos próprios em comandos para implementar políticas multi-fator.