You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Framework/Connected.Security/Authorization/AuthorizationService.cs

144 lines
4.9 KiB

using Connected.Middleware;
using Connected.Security.Authorization.Middleware;
using Connected.Security.Permissions;
using Connected.Threading;
using System.Collections.Immutable;
namespace Connected.Security.Authorization;
internal class AuthorizationService : IAuthorizationService
{
public AuthorizationService(IMiddlewareService middleware, IPermissionService permissions)
{
Middleware = new AsyncLazy<ImmutableList<IAuthorizationMiddleware>>(middleware.Query<IAuthorizationMiddleware>());
Permissions = permissions;
}
private IPermissionService Permissions { get; }
private AsyncLazy<ImmutableList<IAuthorizationMiddleware>> Middleware { get; }
public async Task<IAuthorizationResult> Authorize(AuthorizationArgs args)
{
if (args is null)
throw new ArgumentException(null, nameof(args));
/*
* Claim is required otherwise we don't know what to authorize.
*/
if (string.IsNullOrWhiteSpace(args.Claim))
return AuthorizationResult.Fail(AuthorizationResultReason.NoClaim);
/*
* Query permissions for the specified arguments.
*/
var permissions = await Permissions.Query(new PermissionSearchArgs
{
Entity = args.Entity,
PrimaryKey = args.PrimaryKey?.ToString(),
Claim = args.Claim
});
/*
* We'll use the transition state between calls for better performance.
*/
var state = new Dictionary<string, object>();
/*
* The first state is to find out if we need to perform the actual authorization.
* If at least one of the providers returns success from this stage, the authorization
* will immediately return. For example, if the user has Full Control role there is no
* need to perform full authorization since the user has access to any resource.
*/
foreach (var i in await Middleware.Value)
{
if (await i.PreAuthorize(args, state) == AuthorizationProviderResult.Success)
return AuthorizationResult.OK();
}
/*
* If there are not permissions found we'll look into the arguments configuration. Optimistic scenarios
* will succeed authorization, pessimistic not.
*/
if (permissions is null || !permissions.Any())
{
return args.Schema.EmptyPolicy switch
{
EmptyPolicyBehavior.Deny => AuthorizationResult.Fail(AuthorizationResultReason.Empty),
EmptyPolicyBehavior.Alow => AuthorizationResult.OK(),
_ => throw new NotSupportedException(),
};
}
/*
* We have permissions, let's do the authorization
*/
var denyFound = false;
var allowFound = false;
/*
* We must authorize permission by permission.
*/
foreach (var permission in permissions)
{
/*
* We'll ask every provider to tell us what he thinks about each
* permission and then, based on its result, we're gonna decide
* what to do.
*/
foreach (var provider in await Middleware.Value)
{
/*
* Perform the authorization by the provider.
*/
var result = await provider.Authorize(permission, args, state);
/*
* Mark its result for the decision.
*/
switch (result)
{
case AuthorizationProviderResult.Success:
allowFound = true;
break;
case AuthorizationProviderResult.Fail:
denyFound = true;
break;
}
/*
* The first scenarios is that authorization succedded. If the strategy is also optimistic
* it means we were looking for the first successfull authorization. If found, the authorization
* completes successfully.
*
* The second scenarios is oposite. The authorization failed and the strategy is pessimistic which means
* we were looking for the first unsuccessful authorization. If found, the authorization completes unsuccessfully.
*/
if (result == AuthorizationProviderResult.Success && args.Schema.Strategy == AuthorizationStrategy.Optimistic)
return AuthorizationResult.OK();
else if (result == AuthorizationProviderResult.Fail && args.Schema.Strategy == AuthorizationStrategy.Pessimistic)
return AuthorizationResult.Fail(AuthorizationResultReason.Other);
}
}
/*
* No authorization check directly decided the result so the last stage is to determine the overall result.
*/
switch (args.Schema.Strategy)
{
case AuthorizationStrategy.Pessimistic:
/*
* Pessimistic scenario where not unsuccessful authorization occured we need at least one allow. If there are
* no allow permissions the authorization fail.
*/
if (allowFound)
return AuthorizationResult.OK();
else
return AuthorizationResult.Fail(AuthorizationResultReason.NoAllowFound);
case AuthorizationStrategy.Optimistic:
/*
* Optimistic scenario, where no successful authorizations occured we are looking for the explicit
* deny authorizations. If found, the authorization fails.
*/
if (denyFound)
return AuthorizationResult.Fail(AuthorizationResultReason.DenyFound);
else
return AuthorizationResult.OK();
default:
throw new NotSupportedException();
}
}
}