From 37e4b128b187eb441c833f90fff1c8dd62a1feed Mon Sep 17 00:00:00 2001 From: Ponchanon Datta Rone Date: Wed, 12 Aug 2026 10:14:40 -0500 Subject: [PATCH] feat(authorization): add permission policy --- .../Authorization/AuthorizationPolicy.cs | 35 +++++++++++ .../Authorization/IAuthorizationPolicy.cs | 23 +++++++ .../Authorization/PermissionPolicy.cs | 44 +++++++++++++ .../Authorization/PermissionPolicyTests.cs | 63 +++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 src/BuildingBlocks/Contracts/Authorization/AuthorizationPolicy.cs create mode 100644 src/BuildingBlocks/Security/Authorization/IAuthorizationPolicy.cs create mode 100644 src/BuildingBlocks/Security/Authorization/PermissionPolicy.cs create mode 100644 tests/UnitTests/Authorization/PermissionPolicyTests.cs diff --git a/src/BuildingBlocks/Contracts/Authorization/AuthorizationPolicy.cs b/src/BuildingBlocks/Contracts/Authorization/AuthorizationPolicy.cs new file mode 100644 index 0000000..c730eef --- /dev/null +++ b/src/BuildingBlocks/Contracts/Authorization/AuthorizationPolicy.cs @@ -0,0 +1,35 @@ +namespace OpenHealthOS.Contracts.Authorization; + +/// +/// Represents a named authorization policy. +/// +public sealed record AuthorizationPolicy +{ + /// + /// Initializes a new instance of the + /// class. + /// + /// The stable policy identifier. + public AuthorizationPolicy(string value) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException( + "Authorization policy value cannot be null or whitespace.", + nameof(value)); + } + + Value = value; + } + + /// + /// Gets the stable policy identifier. + /// + public string Value { get; } + + /// + /// Returns the policy identifier. + /// + /// The policy identifier string. + public override string ToString() => Value; +} diff --git a/src/BuildingBlocks/Security/Authorization/IAuthorizationPolicy.cs b/src/BuildingBlocks/Security/Authorization/IAuthorizationPolicy.cs new file mode 100644 index 0000000..0edb365 --- /dev/null +++ b/src/BuildingBlocks/Security/Authorization/IAuthorizationPolicy.cs @@ -0,0 +1,23 @@ +namespace OpenHealthOS.Security.Authorization; + +using OpenHealthOS.Contracts.Authorization; + +/// +/// Represents a policy used to determine whether an authorization +/// request satisfies a defined authorization rule. +/// +public interface IAuthorizationPolicy +{ + /// + /// Determines whether the specified authorization context satisfies + /// this policy. + /// + /// + /// The authorization context to evaluate. + /// + /// + /// when the context satisfies the policy; + /// otherwise . + /// + bool IsSatisfied(AuthorizationContextDto context); +} diff --git a/src/BuildingBlocks/Security/Authorization/PermissionPolicy.cs b/src/BuildingBlocks/Security/Authorization/PermissionPolicy.cs new file mode 100644 index 0000000..00adf07 --- /dev/null +++ b/src/BuildingBlocks/Security/Authorization/PermissionPolicy.cs @@ -0,0 +1,44 @@ +namespace OpenHealthOS.Security.Authorization; + +using OpenHealthOS.Contracts.Authorization; + +/// +/// Authorization policy that requires a specific permission. +/// +public sealed class PermissionPolicy : IAuthorizationPolicy +{ + private readonly Permission _permission; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The permission required by this policy. + /// + public PermissionPolicy(Permission permission) + { + ArgumentNullException.ThrowIfNull(permission); + + _permission = permission; + } + + /// + /// Determines whether the authorization context satisfies the policy. + /// + /// + /// The authorization context to evaluate. + /// + /// + /// when the requested permission matches the + /// policy permission; otherwise . + /// + public bool IsSatisfied(AuthorizationContextDto context) + { + ArgumentNullException.ThrowIfNull(context); + + return string.Equals( + context.Permission.Value, + _permission.Value, + StringComparison.OrdinalIgnoreCase); + } +} diff --git a/tests/UnitTests/Authorization/PermissionPolicyTests.cs b/tests/UnitTests/Authorization/PermissionPolicyTests.cs new file mode 100644 index 0000000..aa632e6 --- /dev/null +++ b/tests/UnitTests/Authorization/PermissionPolicyTests.cs @@ -0,0 +1,63 @@ +namespace OpenHealthOS.UnitTests.Authorization; + +using OpenHealthOS.Contracts.Authorization; +using OpenHealthOS.Contracts.Identity; +using OpenHealthOS.Security.Authorization; +using Xunit; + +public sealed class PermissionPolicyTests +{ + [Fact] + public void IsSatisfied_ShouldReturnTrue_WhenPermissionMatches() + { + var policy = new PermissionPolicy( + new Permission("patient.read")); + + var context = CreateContext("patient.read"); + + var result = policy.IsSatisfied(context); + + Assert.True(result); + } + + [Fact] + public void IsSatisfied_ShouldReturnTrue_WhenPermissionMatchesIgnoringCase() + { + var policy = new PermissionPolicy( + new Permission("patient.read")); + + var context = CreateContext("PATIENT.READ"); + + var result = policy.IsSatisfied(context); + + Assert.True(result); + } + + [Fact] + public void IsSatisfied_ShouldReturnFalse_WhenPermissionDoesNotMatch() + { + var policy = new PermissionPolicy( + new Permission("patient.read")); + + var context = CreateContext("patient.write"); + + var result = policy.IsSatisfied(context); + + Assert.False(result); + } + + private static AuthorizationContextDto CreateContext( + string permission) + { + return new AuthorizationContextDto + { + Identity = new IdentityContextDto + { + SubjectId = "test-user", + PrincipalType = PrincipalType.User, + }, + Permission = new Permission(permission), + ResourceType = "Patient", + }; + } +}