diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 57c322aab03..395dcd66ae4 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -39,7 +39,6 @@
-
@@ -59,6 +58,7 @@
+
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Extensions/HotChocolateAuthorizeRequestExecutorBuilder.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Extensions/HotChocolateAuthorizeRequestExecutorBuilder.cs
index a802bb5e6a2..7bdd2ecfdc4 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Extensions/HotChocolateAuthorizeRequestExecutorBuilder.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Extensions/HotChocolateAuthorizeRequestExecutorBuilder.cs
@@ -1,8 +1,8 @@
using HotChocolate.AspNetCore.Authorization;
using HotChocolate.Execution.Configuration;
-using Microsoft.Extensions.Configuration;
using System.Text.Json;
using System.Text.Json.Serialization;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Options;
namespace Microsoft.Extensions.DependencyInjection;
@@ -42,7 +42,7 @@ public static IRequestExecutorBuilder AddOpaAuthorization(
var jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
jsonOptions.Converters.Add(
new JsonStringEnumConverter(
@@ -55,6 +55,15 @@ public static IRequestExecutorBuilder AddOpaAuthorization(
return builder;
}
+ ///
+ /// Adds result handler to the OPA options.
+ ///
+ /// Instance of .
+ /// The path to the policy.
+ /// The PDP decision result.
+ ///
+ /// Returns the for chaining in more configurations.
+ ///
public static IRequestExecutorBuilder AddOpaResultHandler(
this IRequestExecutorBuilder builder,
string policyPath,
@@ -66,4 +75,27 @@ public static IRequestExecutorBuilder AddOpaResultHandler(
(o, _) => o.PolicyResultHandlers.Add(policyPath, parseResult));
return builder;
}
+
+ ///
+ /// Adds OPA query request extensions handler to the OPA options.
+ ///
+ /// Instance of .
+ /// The path to the policy.
+ /// The handler for the extensions associated with the Policy.
+ ///
+ ///
+ /// Returns the for chaining in more configurations.
+ ///
+ public static IRequestExecutorBuilder AddOpaQueryRequestExtensionsHandler(
+ this IRequestExecutorBuilder builder,
+ string policyPath,
+ OpaQueryRequestExtensionsHandler opaQueryRequestExtensionsHandler)
+ {
+ builder.Services
+ .AddOptions()
+ .Configure(
+ (o, _) => o.OpaQueryRequestExtensionsHandlers.Add(policyPath, opaQueryRequestExtensionsHandler));
+ return builder;
+ }
+
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/IOpaService.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/IOpaService.cs
index 4218692373b..9840c7af544 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/IOpaService.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/IOpaService.cs
@@ -1,7 +1,17 @@
namespace HotChocolate.AspNetCore.Authorization;
+///
+/// The OPA service interface communicating with OPA server.
+///
public interface IOpaService
{
+ ///
+ /// The method used to query OPA PDP decision based on the request input.
+ ///
+ /// The string parameter representing path of the evaluating policy.
+ /// The instance .
+ /// Cancellation token.
+ ///
Task QueryAsync(
string policyPath,
OpaQueryRequest request,
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaAuthorizationHandler.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaAuthorizationHandler.cs
index d2b7c17c779..6ca37a71ed4 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaAuthorizationHandler.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaAuthorizationHandler.cs
@@ -19,43 +19,36 @@ public OpaAuthorizationHandler(
IOpaQueryRequestFactory requestFactory,
IOptions options)
{
- if (options is null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ ArgumentNullException.ThrowIfNull(options);
_opaService = opaService ?? throw new ArgumentNullException(nameof(opaService));
_requestFactory = requestFactory ?? throw new ArgumentNullException(nameof(requestFactory));
_options = options.Value;
}
- ///
- /// Authorize current directive using OPA (Open Policy Agent).
- ///
- /// The current middleware context.
- /// The authorization directive.
- /// The cancellation token.
- ///
- /// Returns a value indicating if the current session is authorized to
- /// access the resolver data.
- ///
+ ///
public async ValueTask AuthorizeAsync(
IMiddlewareContext context,
AuthorizeDirective directive,
- CancellationToken ct)
+ CancellationToken cancellationToken = default)
{
- var authorizationContext = new AuthorizationContext(
- context.Schema,
- context.Services,
- context.ContextData,
- context.Operation.Document,
- context.Operation.Id);
- return await AuthorizeAsync(authorizationContext, directive, ct).ConfigureAwait(false);
+ return await AuthorizeAsync(
+ new OpaAuthorizationHandlerContext(context), [directive], cancellationToken).ConfigureAwait(false);
}
+ ///
public async ValueTask AuthorizeAsync(
AuthorizationContext context,
IReadOnlyList directives,
+ CancellationToken cancellationToken = default)
+ {
+ return await AuthorizeAsync(
+ new OpaAuthorizationHandlerContext(context), directives, cancellationToken).ConfigureAwait(false);
+ }
+
+ private async ValueTask AuthorizeAsync(
+ OpaAuthorizationHandlerContext context,
+ IReadOnlyList directives,
CancellationToken ct)
{
if (directives.Count == 1)
@@ -89,12 +82,12 @@ public async ValueTask AuthorizeAsync(
return AuthorizeResult.Allowed;
static async Task ExecuteAsync(
- AuthorizationContext context,
+ OpaAuthorizationHandlerContext context,
IEnumerator partition,
Authorize authorize,
CancellationToken ct)
{
- while (partition.MoveNext() && partition.Current is not null)
+ while (partition.MoveNext())
{
var directive = partition.Current;
var result = await authorize(context, directive, ct).ConfigureAwait(false);
@@ -110,7 +103,7 @@ static async Task ExecuteAsync(
}
private async ValueTask AuthorizeAsync(
- AuthorizationContext context,
+ OpaAuthorizationHandlerContext context,
AuthorizeDirective directive,
CancellationToken ct)
{
@@ -122,7 +115,7 @@ private async ValueTask AuthorizeAsync(
}
private delegate ValueTask Authorize(
- AuthorizationContext context,
+ OpaAuthorizationHandlerContext context,
AuthorizeDirective directive,
CancellationToken ct);
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaAuthorizationHandlerContext.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaAuthorizationHandlerContext.cs
new file mode 100644
index 00000000000..59811e92efb
--- /dev/null
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaAuthorizationHandlerContext.cs
@@ -0,0 +1,25 @@
+namespace HotChocolate.AspNetCore.Authorization;
+
+///
+/// The OPA authorization handler context.
+///
+public class OpaAuthorizationHandlerContext
+{
+ ///
+ /// The constructor.
+ ///
+ /// Either IMiddlewareContext or AuthorizationContext depending on the phase of
+ /// a rule execution.
+ ///
+ public OpaAuthorizationHandlerContext(object resource)
+ {
+ ArgumentNullException.ThrowIfNull(resource);
+
+ Resource = resource;
+ }
+
+ ///
+ /// The object representing instance of either IMiddlewareContext or AuthorizationContext.
+ ///
+ public object Resource { get; }
+}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaOptions.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaOptions.cs
index 955487008f6..a0b8af88034 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaOptions.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaOptions.cs
@@ -5,6 +5,9 @@
namespace HotChocolate.AspNetCore.Authorization;
+///
+/// The class representing OPA configuration options.
+///
public sealed class OpaOptions
{
private readonly ConcurrentDictionary _handlerKeysRegexes = new();
@@ -17,14 +20,34 @@ public sealed class OpaOptions
public Dictionary PolicyResultHandlers { get; } = new();
+ public Dictionary OpaQueryRequestExtensionsHandlers { get; } = new();
+
+ public OpaQueryRequestExtensionsHandler? GetOpaQueryRequestExtensionsHandler(string policyPath)
+ {
+ if (OpaQueryRequestExtensionsHandlers.Count == 0)
+ {
+ return null;
+ }
+ return OpaQueryRequestExtensionsHandlers.TryGetValue(policyPath, out var handler)
+ ? handler :
+ FindHandler(policyPath, OpaQueryRequestExtensionsHandlers);
+ }
+
public ParseResult GetPolicyResultParser(string policyPath)
{
if (PolicyResultHandlers.TryGetValue(policyPath, out var handler))
{
return handler;
}
+ handler = FindHandler(policyPath, PolicyResultHandlers);
+ return handler ??
+ throw new InvalidOperationException(
+ $"No result handler found for policy: {policyPath}");
+ }
- var maybeHandler = PolicyResultHandlers.SingleOrDefault(
+ private THandler? FindHandler(string policyPath, Dictionary handlers)
+ {
+ var maybeHandler = handlers.SingleOrDefault(
k =>
{
var regex = _handlerKeysRegexes.GetOrAdd(
@@ -33,14 +56,15 @@ public ParseResult GetPolicyResultParser(string policyPath)
k.Key,
RegexOptions.Compiled |
RegexOptions.Singleline |
- RegexOptions.CultureInvariant));
+ RegexOptions.CultureInvariant,
+ TimeSpan.FromMilliseconds(500)));
return regex.IsMatch(policyPath);
});
- return maybeHandler.Value ??
- throw new InvalidOperationException(
- $"No result handler found for policy: {policyPath}");
+ return maybeHandler.Value;
}
}
public delegate AuthorizeResult ParseResult(OpaQueryResponse response);
+
+public delegate object? OpaQueryRequestExtensionsHandler(OpaAuthorizationHandlerContext context);
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaService.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaService.cs
index 578763b186d..7576f3e1c67 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaService.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/OpaService.cs
@@ -11,36 +11,29 @@ internal sealed class OpaService : IOpaService
public OpaService(HttpClient httpClient, IOptions options)
{
- if (options is null)
- {
- throw new ArgumentNullException(nameof(options));
- }
+ ArgumentNullException.ThrowIfNull(options);
- _client = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
+ ArgumentNullException.ThrowIfNull(httpClient);
+
+ _client = httpClient;
_options = options.Value;
}
public async Task QueryAsync(
string policyPath,
OpaQueryRequest request,
- CancellationToken ct)
+ CancellationToken cancellationToken = default)
{
- if (policyPath is null)
- {
- throw new ArgumentNullException(nameof(policyPath));
- }
+ ArgumentNullException.ThrowIfNull(policyPath);
- if (request is null)
- {
- throw new ArgumentNullException(nameof(request));
- }
+ ArgumentNullException.ThrowIfNull(request);
using var body = JsonContent.Create(request, options: _options.JsonSerializerOptions);
- using var response = await _client.PostAsync(policyPath, body, ct).ConfigureAwait(false);
+ using var response = await _client.PostAsync(policyPath, body, cancellationToken).ConfigureAwait(false);
response.EnsureSuccessStatusCode();
- await using var stream = await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false);
- var document = await JsonDocument.ParseAsync(stream, default, ct);
+ await using var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
+ var document = await JsonDocument.ParseAsync(stream, default, cancellationToken);
return new OpaQueryResponse(document);
}
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/QueryResponse.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/QueryResponse.cs
index 18f2a00e917..2c770dbf897 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/QueryResponse.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/QueryResponse.cs
@@ -2,24 +2,20 @@
namespace HotChocolate.AspNetCore.Authorization;
-public sealed class OpaQueryResponse : IDisposable
+///
+/// The class representing OPA query response.
+///
+public sealed class OpaQueryResponse(JsonDocument document) : IDisposable
{
- private readonly JsonDocument _document;
- private readonly JsonElement _root;
-
- public OpaQueryResponse(JsonDocument document)
- {
- _document = document;
- _root = document.RootElement;
- }
+ private readonly JsonElement _root = document.RootElement;
public Guid? DecisionId
- => _root.TryGetProperty("decisionId", out var value)
+ => _root.TryGetProperty("decision_id", out var value)
? value.GetGuid()
: null;
public T? GetResult()
- => _root.TryGetProperty("decisionId", out var value)
+ => _root.TryGetProperty("result", out var value)
? value.Deserialize()
: default;
@@ -28,5 +24,5 @@ public bool IsEmpty
_root.EnumerateObject().Any();
public void Dispose()
- => _document.Dispose();
+ => document.Dispose();
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/DefaultQueryRequestFactory.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/DefaultQueryRequestFactory.cs
index 77e6b349659..1decfec9c1f 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/DefaultQueryRequestFactory.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/DefaultQueryRequestFactory.cs
@@ -1,23 +1,39 @@
using HotChocolate.Authorization;
+using HotChocolate.Resolvers;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Options;
namespace HotChocolate.AspNetCore.Authorization;
-public sealed class DefaultQueryRequestFactory : IOpaQueryRequestFactory
+///
+/// Default implementation of .
+///
+internal sealed class DefaultQueryRequestFactory : IOpaQueryRequestFactory
{
- public OpaQueryRequest CreateRequest(AuthorizationContext context, AuthorizeDirective directive)
+ private readonly OpaOptions _options;
+
+ public DefaultQueryRequestFactory(IOptions options)
{
- if (context is null)
- {
- throw new ArgumentNullException(nameof(context));
- }
+ ArgumentNullException.ThrowIfNull(options);
- if (directive is null)
- {
- throw new ArgumentNullException(nameof(directive));
- }
+ _options = options.Value;
+ }
- var httpContext = (HttpContext)context.ContextData[nameof(HttpContext)]!;
+ ///
+ public OpaQueryRequest CreateRequest(OpaAuthorizationHandlerContext context, AuthorizeDirective directive)
+ {
+ ArgumentNullException.ThrowIfNull(context);
+ ArgumentNullException.ThrowIfNull(directive);
+
+ var httpContext =
+ context.Resource switch
+ {
+ IMiddlewareContext middlewareContext =>
+ (HttpContext)middlewareContext.ContextData[nameof(HttpContext)]!,
+ AuthorizationContext authorizationContext =>
+ (HttpContext)authorizationContext.ContextData[nameof(HttpContext)]!,
+ _ => throw new ArgumentException("Invalid context data.")
+ };
var connection = httpContext.Connection;
var policy = new Policy(
@@ -40,6 +56,13 @@ public OpaQueryRequest CreateRequest(AuthorizationContext context, AuthorizeDire
connection.LocalIpAddress!.ToString(),
connection.LocalPort);
- return new OpaQueryRequest(policy, originalRequest, source, destination);
+ object? extensions = null;
+ if (directive.Policy is not null &&
+ _options.GetOpaQueryRequestExtensionsHandler(directive.Policy) is { } extensionsHandler)
+ {
+ extensions = extensionsHandler(context);
+ }
+
+ return new OpaQueryRequest(policy, originalRequest, source, destination, extensions);
}
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/IOpaQueryRequestFactory.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/IOpaQueryRequestFactory.cs
index 92a35f2c95c..0ed248eb250 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/IOpaQueryRequestFactory.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/IOpaQueryRequestFactory.cs
@@ -2,9 +2,20 @@
namespace HotChocolate.AspNetCore.Authorization;
+///
+/// The OPA query request factory interface.
+///
public interface IOpaQueryRequestFactory
{
+ ///
+ /// Creates .
+ ///
+ /// The OPA authorization handler context.
+ /// Depending on the query execution phase the context's Resource is different
+ /// see for details.
+ ///
+ /// The OPA authorization directive. See .
OpaQueryRequest CreateRequest(
- AuthorizationContext context,
+ OpaAuthorizationHandlerContext context,
AuthorizeDirective directive);
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/IPAndPort.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/IPAndPort.cs
index 55cfd1f9611..d19ea2c1f04 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/IPAndPort.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/IPAndPort.cs
@@ -1,21 +1,34 @@
namespace HotChocolate.AspNetCore.Authorization;
// ReSharper disable once InconsistentNaming
+///
+/// A structure to store information about IP address and port in OPA query request input.
+///
public sealed class IPAndPort
{
+ ///
+ /// Public constructor.
+ ///
+ /// IP address.
+ /// Port number.
+ /// Thrown if port values is out of range: [0:65535].
public IPAndPort(string ipAddress, int port = 0)
{
- if (port <= 0)
- {
- throw new ArgumentOutOfRangeException(nameof(port));
- }
+ ArgumentOutOfRangeException.ThrowIfNegativeOrZero(port);
+ ArgumentOutOfRangeException.ThrowIfGreaterThan(port, (1 << 16) - 1);
IPAddress = ipAddress ?? throw new ArgumentNullException(nameof(ipAddress));
Port = port;
}
// ReSharper disable once InconsistentNaming
+ ///
+ /// IP address string.
+ ///
public string IPAddress { get; }
+ ///
+ /// Port value.
+ ///
public int Port { get; }
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/OpaQueryRequest.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/OpaQueryRequest.cs
index 579af63a72a..2108e2920d9 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/OpaQueryRequest.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/OpaQueryRequest.cs
@@ -1,7 +1,22 @@
namespace HotChocolate.AspNetCore.Authorization;
+///
+/// A structure representing OPA query request input.
+///
public sealed class OpaQueryRequest
{
+ ///
+ /// The constructor.
+ ///
+ /// The instance of .
+ /// The instance of .
+ /// Stores information about the original GraphQl request.
+ /// The instance .
+ /// Stores information about the source address of the original request
+ /// The instance .
+ /// Stores information about the destination address of the original request.
+ /// The instance of object that provides extended information for the OPA query request.
+ /// Usually is represented as a dictionary.
public OpaQueryRequest(
Policy policy,
OriginalRequest request,
@@ -9,55 +24,63 @@ public OpaQueryRequest(
IPAndPort destination,
object? extensions = null)
{
- if (policy is null)
- {
- throw new ArgumentNullException(nameof(policy));
- }
-
- if (request is null)
- {
- throw new ArgumentNullException(nameof(request));
- }
-
- if (source is null)
- {
- throw new ArgumentNullException(nameof(source));
- }
-
- if (destination is null)
- {
- throw new ArgumentNullException(nameof(destination));
- }
+ ArgumentNullException.ThrowIfNull(policy);
+ ArgumentNullException.ThrowIfNull(request);
+ ArgumentNullException.ThrowIfNull(source);
+ ArgumentNullException.ThrowIfNull(destination);
Input = new OpaQueryRequestInput(policy, request, source, destination, extensions);
}
+ ///
+ /// The property to get the instance of .
+ ///
public OpaQueryRequestInput Input { get; }
- public sealed class OpaQueryRequestInput
+ ///
+ /// The class representing an input that will be sent to the OPA server.
+ ///
+ /// The instance of .
+ /// The instance of .
+ /// Stores information about the original GraphQl request.
+ /// The instance .
+ /// Stores information about the source address of the original request
+ /// The instance .
+ /// Stores information about the destination address of the original request.
+ /// The instance of object the provides extended information for the OPA query request.
+ /// Usually is represented as a dictionary.
+ public sealed class OpaQueryRequestInput(
+ Policy policy,
+ OriginalRequest request,
+ IPAndPort source,
+ IPAndPort destination,
+ object? extensions)
{
- public OpaQueryRequestInput(
- Policy policy,
- OriginalRequest request,
- IPAndPort source,
- IPAndPort destination,
- object? extensions)
- {
- Policy = policy;
- Request = request;
- Source = source;
- Destination = destination;
- Extensions = extensions;
- }
-
- public Policy Policy { get; }
+ ///
+ /// The property to get instance of .
+ ///
+ public Policy Policy { get; } = policy;
- public OriginalRequest Request { get; }
+ ///
+ /// The property to get instance of .
+ ///
+ public OriginalRequest Request { get; } = request;
- public IPAndPort Source { get; }
+ ///
+ /// The property to get instance of representing
+ /// the original request source IP address and port.
+ ///
+ public IPAndPort Source { get; } = source;
- public IPAndPort Destination { get; }
+ ///
+ /// The property to get instance of representing
+ /// the original request destination IP address and port.
+ ///
+ public IPAndPort Destination { get; } = destination;
- public object? Extensions { get; }
+ ///
+ /// The property to get instance of object representing OPA query input extension data.
+ ///
+ public object? Extensions { get; } = extensions;
}
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/OriginalRequest.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/OriginalRequest.cs
index 5d31d0c67e4..f86f96ebf0f 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/OriginalRequest.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/OriginalRequest.cs
@@ -3,33 +3,44 @@
namespace HotChocolate.AspNetCore.Authorization;
-public sealed class OriginalRequest
+///
+/// The class representing the information about the original GraphQl request.
+///
+public sealed class OriginalRequest(
+ IHeaderDictionary headers,
+ string host,
+ string method,
+ string path,
+ IEnumerable>? query,
+ string scheme)
{
- public OriginalRequest(
- IHeaderDictionary headers,
- string host,
- string method,
- string path,
- IEnumerable>? query,
- string scheme)
- {
- Headers = headers ?? throw new ArgumentNullException(nameof(headers));
- Host = host ?? throw new ArgumentNullException(nameof(host));
- Method = method ?? throw new ArgumentNullException(nameof(method));
- Path = path ?? throw new ArgumentNullException(nameof(path));
- Query = query;
- Scheme = scheme ?? throw new ArgumentNullException(nameof(scheme));
- }
-
- public IHeaderDictionary Headers { get; }
-
- public string Host { get; }
-
- public string Method { get; }
-
- public string Path { get; }
-
- public IEnumerable>? Query { get; }
-
- public string Scheme { get; }
+ ///
+ /// Original request headers.
+ ///
+ public IHeaderDictionary Headers { get; } = headers ?? throw new ArgumentNullException(nameof(headers));
+
+ ///
+ /// Information about the host sent request.
+ ///
+ public string Host { get; } = host ?? throw new ArgumentNullException(nameof(host));
+
+ ///
+ /// The HTTP request method.
+ ///
+ public string Method { get; } = method ?? throw new ArgumentNullException(nameof(method));
+
+ ///
+ /// Path of the request.
+ ///
+ public string Path { get; } = path ?? throw new ArgumentNullException(nameof(path));
+
+ ///
+ /// The query of the request.
+ ///
+ public IEnumerable>? Query { get; } = query;
+
+ ///
+ /// GraphQl schema of the request.
+ ///
+ public string Scheme { get; } = scheme ?? throw new ArgumentNullException(nameof(scheme));
}
diff --git a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/Policy.cs b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/Policy.cs
index 3f88bd1fc85..0c7e689252d 100644
--- a/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/Policy.cs
+++ b/src/HotChocolate/AspNetCore/src/AspNetCore.Authorization.Opa/Request/Policy.cs
@@ -1,14 +1,17 @@
namespace HotChocolate.AspNetCore.Authorization;
-public sealed class Policy
+///
+/// The structure representing information about an OPA policy to be evaluated by the OPA server.
+///
+public sealed class Policy(string path, IReadOnlyList roles)
{
- public Policy(string path, IReadOnlyList roles)
- {
- Path = path ?? throw new ArgumentNullException(nameof(path));
- Roles = roles ?? throw new ArgumentNullException(nameof(roles));
- }
+ ///
+ /// Path of the policy. Contains the path string appended to the OPA base address.
+ ///
+ public string Path { get; } = path ?? throw new ArgumentNullException(nameof(path));
- public string Path { get; }
-
- public IReadOnlyList Roles { get; }
+ ///
+ /// Roles associated with the user to evaluate by the policy rule.
+ ///
+ public IReadOnlyList Roles { get; } = roles ?? throw new ArgumentNullException(nameof(roles));
}
diff --git a/src/HotChocolate/AspNetCore/test/AspNetCore.Authorization.Opa.Tests/AuthorizationAttributeTestData.cs b/src/HotChocolate/AspNetCore/test/AspNetCore.Authorization.Opa.Tests/AuthorizationAttributeTestData.cs
index 9f9317f971e..d621181de44 100644
--- a/src/HotChocolate/AspNetCore/test/AspNetCore.Authorization.Opa.Tests/AuthorizationAttributeTestData.cs
+++ b/src/HotChocolate/AspNetCore/test/AspNetCore.Authorization.Opa.Tests/AuthorizationAttributeTestData.cs
@@ -13,7 +13,7 @@ public class Query
public string GetDefault() => "foo";
[Authorize(Policy = Policies.HasDefinedAge)]
- public string GetAge() => "foo";
+ public string? GetAge() => "foo";
[Authorize(Roles = ["a",])]
public string GetRoles() => "foo";
@@ -40,11 +40,13 @@ private Action CreateSchema() =>
})
.AddOpaResultHandler(
Policies.HasDefinedAge,
- response => response.GetResult() switch
- {
- { Allow: true, } => AuthorizeResult.Allowed,
- _ => AuthorizeResult.NotAllowed,
- });
+ response => response.DecisionId is null
+ ? AuthorizeResult.NotAllowed
+ : response.GetResult() switch
+ {
+ { Allow: true, } => AuthorizeResult.Allowed,
+ _ => AuthorizeResult.NotAllowed,
+ });
public IEnumerator