diff --git a/src/BuildingBlocks/Security/Authorization/PolicyEvaluator.cs b/src/BuildingBlocks/Security/Authorization/PolicyEvaluator.cs new file mode 100644 index 0000000..2e5e792 --- /dev/null +++ b/src/BuildingBlocks/Security/Authorization/PolicyEvaluator.cs @@ -0,0 +1,43 @@ +namespace OpenHealthOS.Security.Authorization; + +using OpenHealthOS.Contracts.Authorization; + +/// +/// Evaluates authorization requests using an authorization policy set. +/// +public sealed class PolicyEvaluator : IAuthorizationEvaluator +{ + private readonly IAuthorizationPolicySet _policySet; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The authorization policy set used to evaluate requests. + /// + public PolicyEvaluator(IAuthorizationPolicySet policySet) + { + ArgumentNullException.ThrowIfNull(policySet); + + _policySet = policySet; + } + + /// + /// Evaluates whether the authorization context satisfies the policy set. + /// + /// + /// The authorization context to evaluate. + /// + /// + /// when all policies are + /// satisfied; otherwise . + /// + public AuthorizationDecision Evaluate(AuthorizationContextDto context) + { + ArgumentNullException.ThrowIfNull(context); + + return _policySet.IsSatisfied(context) + ? AuthorizationDecision.Allow + : AuthorizationDecision.Deny; + } +} diff --git a/tests/UnitTests/Authorization/PolicyEvaluatorTests.cs b/tests/UnitTests/Authorization/PolicyEvaluatorTests.cs new file mode 100644 index 0000000..e45f07f --- /dev/null +++ b/tests/UnitTests/Authorization/PolicyEvaluatorTests.cs @@ -0,0 +1,124 @@ +namespace OpenHealthOS.UnitTests.Authorization; + +using OpenHealthOS.Contracts.Authorization; +using OpenHealthOS.Contracts.Identity; +using OpenHealthOS.Security.Authorization; +using Xunit; + +public sealed class PolicyEvaluatorTests +{ + [Fact] + public void Evaluate_ShouldAllow_WhenPolicySetIsSatisfied() + { + var policySet = new AuthorizationPolicySet( + [ + new PermissionPolicy(new Permission("patient.read")), + ]); + + var evaluator = new PolicyEvaluator(policySet); + + var context = CreateContext("patient.read"); + + var result = evaluator.Evaluate(context); + + Assert.Equal( + AuthorizationDecision.Allow, + result); + } + + [Fact] + public void Evaluate_ShouldDeny_WhenPolicySetIsNotSatisfied() + { + var policySet = new AuthorizationPolicySet( + [ + new PermissionPolicy(new Permission("patient.write")), + ]); + + var evaluator = new PolicyEvaluator(policySet); + + var context = CreateContext("patient.read"); + + var result = evaluator.Evaluate(context); + + Assert.Equal( + AuthorizationDecision.Deny, + result); + } + + [Fact] + public void Evaluate_ShouldAllow_WhenAllPoliciesAreSatisfied() + { + var policySet = new AuthorizationPolicySet( + [ + new PermissionPolicy(new Permission("patient.read")), + new PermissionPolicy(new Permission("patient.read")), + ]); + + var evaluator = new PolicyEvaluator(policySet); + + var context = CreateContext("patient.read"); + + var result = evaluator.Evaluate(context); + + Assert.Equal( + AuthorizationDecision.Allow, + result); + } + + [Fact] + public void Evaluate_ShouldDeny_WhenAnyPolicyIsNotSatisfied() + { + var policySet = new AuthorizationPolicySet( + [ + new PermissionPolicy(new Permission("patient.read")), + new PermissionPolicy(new Permission("patient.write")), + ]); + + var evaluator = new PolicyEvaluator(policySet); + + var context = CreateContext("patient.read"); + + var result = evaluator.Evaluate(context); + + Assert.Equal( + AuthorizationDecision.Deny, + result); + } + + [Fact] + public void Constructor_ShouldThrow_WhenPolicySetIsNull() + { + Assert.Throws( + () => new PolicyEvaluator(null!)); + } + + [Fact] + public void Evaluate_ShouldThrow_WhenContextIsNull() + { + var policySet = new AuthorizationPolicySet( + [ + new PermissionPolicy(new Permission("patient.read")), + ]); + + var evaluator = new PolicyEvaluator(policySet); + + Assert.Throws( + () => evaluator.Evaluate(null!)); + } + + private static AuthorizationContextDto CreateContext( + string permission) + { + return new AuthorizationContextDto + { + Identity = new IdentityContextDto + { + SubjectId = "test-user", + PrincipalType = PrincipalType.User, + Scopes = [permission], + }, + Permission = new Permission(permission), + ResourceType = "Patient", + }; + } +}