Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
namespace OpenHealthOS.Contracts.Authorization;

/// <summary>
/// Represents a named authorization policy.
/// </summary>
public sealed record AuthorizationPolicy
{
/// <summary>
/// Initializes a new instance of the
/// <see cref="AuthorizationPolicy"/> class.
/// </summary>
/// <param name="value">The stable policy identifier.</param>
public AuthorizationPolicy(string value)
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException(
"Authorization policy value cannot be null or whitespace.",
nameof(value));
}

Value = value;
}

/// <summary>
/// Gets the stable policy identifier.
/// </summary>
public string Value { get; }

/// <summary>
/// Returns the policy identifier.
/// </summary>
/// <returns>The policy identifier string.</returns>
public override string ToString() => Value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace OpenHealthOS.Security.Authorization;

using OpenHealthOS.Contracts.Authorization;

/// <summary>
/// Represents a policy used to determine whether an authorization
/// request satisfies a defined authorization rule.
/// </summary>
public interface IAuthorizationPolicy
{
/// <summary>
/// Determines whether the specified authorization context satisfies
/// this policy.
/// </summary>
/// <param name="context">
/// The authorization context to evaluate.
/// </param>
/// <returns>
/// <see langword="true"/> when the context satisfies the policy;
/// otherwise <see langword="false"/>.
/// </returns>
bool IsSatisfied(AuthorizationContextDto context);
}
44 changes: 44 additions & 0 deletions src/BuildingBlocks/Security/Authorization/PermissionPolicy.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
namespace OpenHealthOS.Security.Authorization;

using OpenHealthOS.Contracts.Authorization;

/// <summary>
/// Authorization policy that requires a specific permission.
/// </summary>
public sealed class PermissionPolicy : IAuthorizationPolicy
{
private readonly Permission _permission;

/// <summary>
/// Initializes a new instance of the <see cref="PermissionPolicy"/> class.
/// </summary>
/// <param name="permission">
/// The permission required by this policy.
/// </param>
public PermissionPolicy(Permission permission)
{
ArgumentNullException.ThrowIfNull(permission);

_permission = permission;
}

/// <summary>
/// Determines whether the authorization context satisfies the policy.
/// </summary>
/// <param name="context">
/// The authorization context to evaluate.
/// </param>
/// <returns>
/// <see langword="true"/> when the requested permission matches the
/// policy permission; otherwise <see langword="false"/>.
/// </returns>
public bool IsSatisfied(AuthorizationContextDto context)
{
ArgumentNullException.ThrowIfNull(context);

return string.Equals(
context.Permission.Value,
_permission.Value,
StringComparison.OrdinalIgnoreCase);
}
}
63 changes: 63 additions & 0 deletions tests/UnitTests/Authorization/PermissionPolicyTests.cs
Original file line number Diff line number Diff line change
@@ -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",
};
}
}
Loading