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,43 @@
namespace OpenHealthOS.Security.Authorization;

using OpenHealthOS.Contracts.Authorization;

/// <summary>
/// Provides application-facing authorization by delegating evaluation
/// to an authorization evaluator.
/// </summary>
public sealed class AuthorizationService : IAuthorizationService
{
private readonly IAuthorizationEvaluator _evaluator;

/// <summary>
/// Initializes a new instance of the
/// <see cref="AuthorizationService"/> class.
/// </summary>
/// <param name="evaluator">
/// The authorization evaluator used to evaluate requests.
/// </param>
public AuthorizationService(IAuthorizationEvaluator evaluator)
{
ArgumentNullException.ThrowIfNull(evaluator);

_evaluator = evaluator;
}

/// <summary>
/// Evaluates whether the specified authorization context is allowed.
/// </summary>
/// <param name="context">
/// The authorization context to evaluate.
/// </param>
/// <returns>
/// The authorization decision returned by the evaluator.
/// </returns>
public AuthorizationDecision Authorize(
AuthorizationContextDto context)
{
ArgumentNullException.ThrowIfNull(context);

return _evaluator.Evaluate(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace OpenHealthOS.Security.Authorization;

using OpenHealthOS.Contracts.Authorization;

/// <summary>
/// Provides the application-facing authorization service.
/// </summary>
public interface IAuthorizationService
{
/// <summary>
/// Evaluates whether the specified authorization context is allowed.
/// </summary>
/// <param name="context">
/// The authorization context to evaluate.
/// </param>
/// <returns>
/// The authorization decision.
/// </returns>
AuthorizationDecision Authorize(AuthorizationContextDto context);
}
110 changes: 110 additions & 0 deletions tests/UnitTests/Authorization/AuthorizationServiceTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
namespace OpenHealthOS.UnitTests.Authorization;

using OpenHealthOS.Contracts.Authorization;
using OpenHealthOS.Contracts.Identity;
using OpenHealthOS.Security.Authorization;
using Xunit;

public sealed class AuthorizationServiceTests
{
[Fact]
public void Authorize_ShouldReturnAllow_WhenEvaluatorAllows()
{
var evaluator = new StubAuthorizationEvaluator(
AuthorizationDecision.Allow);

var service = new AuthorizationService(evaluator);

var result = service.Authorize(CreateContext());

Assert.Equal(
AuthorizationDecision.Allow,
result);
}

[Fact]
public void Authorize_ShouldReturnDeny_WhenEvaluatorDenies()
{
var evaluator = new StubAuthorizationEvaluator(
AuthorizationDecision.Deny);

var service = new AuthorizationService(evaluator);

var result = service.Authorize(CreateContext());

Assert.Equal(
AuthorizationDecision.Deny,
result);
}

[Fact]
public void Constructor_ShouldThrow_WhenEvaluatorIsNull()
{
Assert.Throws<ArgumentNullException>(
() => new AuthorizationService(null!));
}

[Fact]
public void Authorize_ShouldThrow_WhenContextIsNull()
{
var evaluator = new StubAuthorizationEvaluator(
AuthorizationDecision.Allow);

var service = new AuthorizationService(evaluator);

Assert.Throws<ArgumentNullException>(
() => service.Authorize(null!));
}

[Fact]
public void Authorize_ShouldDelegateToEvaluator()
{
var evaluator = new StubAuthorizationEvaluator(
AuthorizationDecision.Allow);

var service = new AuthorizationService(evaluator);

var context = CreateContext();

service.Authorize(context);

Assert.Same(context, evaluator.LastContext);
}

private static AuthorizationContextDto CreateContext()
{
return new AuthorizationContextDto
{
Identity = new IdentityContextDto
{
SubjectId = "test-user",
PrincipalType = PrincipalType.User,
Scopes = ["patient.read"],
},
Permission = new Permission("patient.read"),
ResourceType = "Patient",
};
}

private sealed class StubAuthorizationEvaluator
: IAuthorizationEvaluator
{
private readonly AuthorizationDecision _decision;

public StubAuthorizationEvaluator(
AuthorizationDecision decision)
{
_decision = decision;
}

public AuthorizationContextDto? LastContext { get; private set; }

public AuthorizationDecision Evaluate(
AuthorizationContextDto context)
{
LastContext = context;

return _decision;
}
}
}
Loading