Last active
May 7, 2026 20:33
-
-
Save saulin18/0f1707f6dcc689b2cd7e9985f7ffcccd to your computer and use it in GitHub Desktop.
Permission Authorization Handler with wildcards
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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