Skip to content

Instantly share code, notes, and snippets.

@saulin18
Last active May 7, 2026 20:33
Show Gist options
  • Select an option

  • Save saulin18/0f1707f6dcc689b2cd7e9985f7ffcccd to your computer and use it in GitHub Desktop.

Select an option

Save saulin18/0f1707f6dcc689b2cd7e9985f7ffcccd to your computer and use it in GitHub Desktop.
Permission Authorization Handler with wildcards
using Infrastructure.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.DependencyInjection;
using System.Text.RegularExpressions;
namespace Infrastructure.Authorization;
internal sealed class PermissionAuthorizationHandler(IServiceScopeFactory serviceScopeFactory)
: AuthorizationHandler<PermissionRequirement>
{
// Wildcards para permisos flexibles
private static readonly Dictionary<string, Regex> Wildcards = new()
{
{ "all", new Regex("^.+$") },
{ "read", new Regex("^(get|getbyid|list)$", RegexOptions.IgnoreCase) },
{ "write", new Regex("^(create|post|update|put|delete)$", RegexOptions.IgnoreCase) }
};
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
PermissionRequirement requirement)
{
if (context.User is not { Identity.IsAuthenticated: true })
{
return;
}
using IServiceScope scope = serviceScopeFactory.CreateScope();
var permissionProvider = scope.ServiceProvider.GetRequiredService<PermissionProvider>();
Guid userId = context.User.GetUserId();
HashSet<string> userPermissions = await permissionProvider.GetForUserIdAsync(userId);
HttpContext? httpContext = context.Resource as HttpContext;
if (httpContext is null) return;
Endpoint? endpoint = httpContext.GetEndpoint();
string? endpointName = endpoint?.Metadata.GetMetadata<EndpointNameMetadata>()?.EndpointName;
if (string.IsNullOrEmpty(endpointName)) return;
// Extraer resource y action del endpoint
var parts = endpointName.Split('.');
if (parts.Length < 2) return;
string resource = parts[0].ToLower();
string action = parts[1].ToLower();
// Mapear action a permiso estándar
string mappedAction = MapActionName(action);
// Construir permiso requerido con placeholders
string requiredPermission = requirement.Permission
.Replace("{resource}", resource, StringComparison.OrdinalIgnoreCase)
.Replace("{action}", mappedAction, StringComparison.OrdinalIgnoreCase)
.Replace("{owner}", $"owner:{userId}", StringComparison.OrdinalIgnoreCase);
// Verificar si el usuario tiene el permiso
foreach (string userPermission in userPermissions)
{
if (Match(requiredPermission, userPermission))
{
context.Succeed(requirement);
return;
}
}
}
// Verificar si el permiso del usuario coincide con el requerido
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1310:Specify StringComparison for correctness", Justification = "<Pending>")]
private static bool Match(string requiredPermission, string userPermission)
{
var requiredParts = requiredPermission.Split('.');
var userParts = userPermission.Split('.');
if (requiredParts.Length != userParts.Length) return false;
for (int i = 0; i < requiredParts.Length; i++)
{
string requiredPart = requiredParts[i];
string userPart = userParts[i];
// Verificar wildcards
if (Wildcards.TryGetValue(userPart, out var regex) && regex.IsMatch(requiredPart))
{
continue;
}
// Verificar coincidencia exacta
if (string.Equals(requiredPart, userPart, StringComparison.OrdinalIgnoreCase))
{
continue;
}
// Verificar ownership
if (requiredPart.StartsWith("owner:") && userPart.StartsWith("owner:"))
{
continue;
}
return false;
}
return true;
}
// Mapear nombres de acciones a permisos estándar
[System.Diagnostics.CodeAnalysis.SuppressMessage("Globalization", "CA1310:Specify StringComparison for correctness", Justification = "<Pending>")]
private static string MapActionName(string actionName)
{
return actionName.ToLower() switch
{
var action when action.StartsWith("get") || action.StartsWith("list") => "read",
var action when action.StartsWith("create") || action.StartsWith("post") => "write",
var action when action.StartsWith("update") || action.StartsWith("put") => "write",
var action when action.StartsWith("delete") => "write",
_ => actionName.ToLower()
};
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment