public abstract class SaasPermissionAuthorizationHandlerBase()

in src/Saas.Lib/Saas.Identity/Authorization/Handler/SaasPermissionAuthorizationHandlerBase.cs [12:139]


public abstract class SaasPermissionAuthorizationHandlerBase<TSaasRequirement, TSaasPermissionKind>(
    IHttpContextAccessor httpContextAccessor,
    IOptions<SaasAuthorizationOptions> saasAuthorizationOptions) : AuthorizationHandler<TSaasRequirement>
    where TSaasRequirement : ISaasRequirement
    where TSaasPermissionKind : struct, Enum
{
    protected readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;
    protected readonly Guid _globalEntity = saasAuthorizationOptions?.Value.Global
            ?? throw new InvalidOperationException($"Global entity guid in '{nameof(saasAuthorizationOptions)}' cannot be null and must be defined.");

    protected virtual HashSet<int> GetGrantedPermissionValues(AuthorizationHandlerContext context, TSaasRequirement requirement)
    {
        HashSet<int> permissionsGranted;

        if (requirement.Policy.RoutingKeyName is null)
        {
            permissionsGranted = context.User.Claims
                .Where(claim => claim.Type == SaasPermissionClaim<TSaasPermissionKind>.PermissionClaimsIdentifier)
                .Select(claim => new SaasPermissionClaim<TSaasPermissionKind>(claim.Value, requirement.Policy.GroupName))
                .Where(permission => permission.IsValid)
                .Where(permission => IsValidPermission(permission, context, requirement))
                .Select(permission => permission.ToInt())
                .ToHashSet();

            return permissionsGranted;
        }

        if (_httpContextAccessor?.HttpContext?.GetRouteValue(requirement.Policy.RoutingKeyName) is not string routeValue
            || !Guid.TryParse(routeValue, out Guid requestedEntity))
        {
            requestedEntity = default;
        }

        permissionsGranted = context.User.Claims
            .Where(claim => claim.Type == SaasPermissionClaim<TSaasPermissionKind>.PermissionClaimsIdentifier)
            .Select(claim => new SaasPermissionClaim<TSaasPermissionKind>(claim.Value, requirement.Policy.GroupName))
            .Where(permission => permission.IsValid)
            .Where(permission => IsValidPermission(permission, context, requirement, requestedEntity))
            .Select(permission => permission.ToInt())
            .ToHashSet();

        return permissionsGranted;
    }

    protected virtual SaasPermissionClaim<TSaasPermissionKind> CreateSaasPermission(string value, string permissionName) => new(value, permissionName);

    protected virtual bool IsValidPermission(
        SaasPermissionClaim<TSaasPermissionKind> permission, 
        AuthorizationHandlerContext context, 
        TSaasRequirement requirement) => true;

    protected virtual bool IsValidPermission(
        SaasPermissionClaim<TSaasPermissionKind> permission,
        AuthorizationHandlerContext context,
        TSaasRequirement requirement,
        Guid routingEntity) => permission.Entity == routingEntity;

    protected override async Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        TSaasRequirement requirement)
    {
        if (requirement.Policy.Value is null)
        {
            throw new InvalidOperationException($"No permission specified for ${nameof(requirement)}.");
        }

        if (IsGlobalAdmin(context))
        {
            context.Succeed(requirement);
            return;
        }

        HashSet<int> permissionsGranted = GetGrantedPermissionValues(context, requirement);

        if (!permissionsGranted.Any())
        {
            return;
        }

        if (requirement.PermissionValue() == 0b0 // special case where the permissions needed are "None".
            || HasRequiredPermissions(requirement, permissionsGranted))
        {
            context.Succeed(requirement);
        }

        await Task.CompletedTask;
    }

    protected virtual bool IsGlobalAdmin(AuthorizationHandlerContext context)
    {
        var userId = context.User.Claims.Where(c => c.Type == ClaimTypes.NameIdentifier).FirstOrDefault()?.Value;

        if (Guid.TryParse(userId, out var userIdGuid))
        {
            userIdGuid = default;
        }

        return userIdGuid == _globalEntity;
    }

    protected virtual bool HasRequiredPermissions(TSaasRequirement requirement, HashSet<int> permissionsGranted)
    {
        BitArray requiredBits = new(requirement.PermissionValue());
        BitArray grantedBits = new(AccumulatePermissionValues(permissionsGranted));

        for (int i = 0; i < requiredBits.Count; i++)
        {
            if (requiredBits[i] == true && grantedBits[i] == false)
            {
                return false;
            }
        }

        return true;
    }

    protected virtual int AccumulatePermissionValues(HashSet<int> permissionValuesGranted)
    {
        int permissionsCombined = 0;

        foreach (var permissionValue in permissionValuesGranted)
        {
            permissionsCombined |= (int)permissionValue;
        }

        return permissionsCombined;
    }
}