From cef705a01a4b255498a18fad9d96d93cbb132f23 Mon Sep 17 00:00:00 2001 From: Knut Haug Date: Mon, 16 Sep 2024 15:44:21 +0200 Subject: [PATCH 01/32] --wip-- [skip-ci] --- .../Common/ICompactJwsGenerator.cs | 89 +++++++++++++++--- .../AuthorizationOptionsSetup.cs | 94 ++++++++++++++++++- .../HotChocolate/DefinitionNodeExtensions.cs | 48 ++++++++++ .../EndUser/DialogById/Subscriptions.cs | 2 +- .../ServiceCollectionExtensions.cs | 9 +- .../appsettings.Development.json | 15 +-- 6 files changed, 234 insertions(+), 23 deletions(-) create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/DefinitionNodeExtensions.cs diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs index a4f002bfc..6e21b44c7 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs @@ -1,7 +1,9 @@ using System.Buffers.Text; +using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; using NSec.Cryptography; namespace Digdir.Domain.Dialogporten.Application.Common; @@ -10,6 +12,8 @@ public interface ICompactJwsGenerator { string GetCompactJws(Dictionary claims); bool VerifyCompactJws(string compactJws); + bool VerifyCompactJwsTimestamp(string compactJwt); + bool TryGetClaimValue(string compactJws, string claim, [NotNullWhen(true)] out string? value); } public class Ed25519Generator : ICompactJwsGenerator @@ -22,12 +26,11 @@ public class Ed25519Generator : ICompactJwsGenerator public Ed25519Generator(IOptions applicationSettings) { _applicationSettings = applicationSettings.Value; + InitSigningKey(); } public string GetCompactJws(Dictionary claims) { - InitSigningKey(); - var header = JsonSerializer.SerializeToUtf8Bytes(new { alg = "EdDSA", @@ -62,23 +65,80 @@ public string GetCompactJws(Dictionary claims) public bool VerifyCompactJws(string compactJws) { - var parts = compactJws.Split('.'); - if (parts.Length != 3) return false; + try + { + var parts = compactJws.Split('.'); + if (parts.Length != 3) return false; - var header = Base64Url.Decode(parts[0]); + var header = Base64Url.Decode(parts[0]); - var headerJson = JsonSerializer.Deserialize(header); - if (headerJson.TryGetProperty("kid", out var kid)) + var headerJson = JsonSerializer.Deserialize(header); + if (headerJson.TryGetProperty("kid", out var kid)) + { + if (kid.GetString() != _kid) return false; + } + else + { + return false; + } + + var signature = Base64Url.Decode(parts[2]); + return SignatureAlgorithm.Ed25519.Verify(_publicKey!, Encoding.UTF8.GetBytes(parts[0] + '.' + parts[1]), signature); + } + catch (Exception) { - if (kid.GetString() != _kid) return false; + // Log? + return false; + } + } + + public bool VerifyCompactJwsTimestamp(string compactJwt) + { + try + { + var parts = compactJwt.Split('.'); + if (parts.Length != 3) return false; + + var payload = Base64Url.Decode(parts[1]); + var payloadJson = JsonSerializer.Deserialize(payload); + if (!payloadJson.TryGetProperty(DialogTokenClaimTypes.Expires, out var exp)) return false; + + var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); + return exp.GetInt64() > now; } - else + catch (Exception) { + // Log? return false; } + } + + public bool TryGetClaimValue(string compactJws, string claim, [NotNullWhen(true)] out string? value) + { + value = null; + try + { + var parts = compactJws.Split('.'); + if (parts.Length != 3) + { + return false; + } + + var payload = Base64Url.Decode(parts[1]); + var payloadJson = JsonSerializer.Deserialize(payload); + if (!payloadJson.TryGetProperty(claim, out var claimValue)) + { + return false; + } - var signature = Base64Url.Decode(parts[2]); - return SignatureAlgorithm.Ed25519.Verify(_publicKey!, Encoding.UTF8.GetBytes(parts[0] + '.' + parts[1]), signature); + value = claimValue.GetString(); + return value != null; + } + catch (Exception) + { + // Log? + return false; + } } private void InitSigningKey() @@ -104,6 +164,13 @@ public LocalDevelopmentCompactJwsGeneratorDecorator(ICompactJwsGenerator _) public string GetCompactJws(Dictionary claims) => "local-development-jws"; public bool VerifyCompactJws(string compactJws) => true; + public bool VerifyCompactJwsTimestamp(string compactJwt) => true; + + public bool TryGetClaimValue(string compactJws, string claim, [NotNullWhen(true)] out string? value) + { + value = "local-development-claim"; + return true; + } } public static class Base64Url diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 3af13c46e..520135c9c 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -1,15 +1,24 @@ -using Microsoft.AspNetCore.Authorization; +using System.Security.Claims; +using Digdir.Domain.Dialogporten.Application.Common; +using Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; +using HotChocolate.Authorization; +using HotChocolate.Resolvers; +using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; +using AuthorizationOptions = Microsoft.AspNetCore.Authorization.AuthorizationOptions; namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; internal sealed class AuthorizationOptionsSetup : IConfigureOptions { private readonly GraphQlSettings _options; + private readonly ICompactJwsGenerator _compactJwsGenerator; + private const string DialogTokenHeader = "DigDir-Dialog-Token"; - public AuthorizationOptionsSetup(IOptions options) + public AuthorizationOptionsSetup(IOptions options, ICompactJwsGenerator compactJwsGenerator) { - _options = options.Value; + _compactJwsGenerator = compactJwsGenerator ?? throw new ArgumentNullException(nameof(compactJwsGenerator)); + _options = options.Value ?? throw new ArgumentNullException(nameof(options)); } public void Configure(AuthorizationOptions options) @@ -41,5 +50,84 @@ public void Configure(AuthorizationOptions options) options.AddPolicy(AuthorizationPolicy.Testing, builder => builder .Combine(options.DefaultPolicy) .RequireScope(AuthorizationScope.Testing)); + + options.AddPolicy("foo", policy => policy + .Combine(options.GetPolicy(AuthorizationPolicy.EndUser)!) + .RequireAssertion(context => + { + // Cast resource to MiddleWareContext + // Get first value from FieldSelection.Arguments (DialogIdSubscriptionInput) + // Get HttpContext from context.Resource.ContextData, key is "HttpContext" and assign to var httpContext + if (context.Resource is not AuthorizationContext authContext) + { + return false; + } + + if (!authContext.ContextData.TryGetValue("HttpContext", out var httpContextObj)) + { + return false; + } + + if (httpContextObj is not HttpContext httpContext) + { + return false; + } + + if (!authContext.Document.Definitions.TryGetSubscriptionDialogId(out var dialogId)) + { + return false; + } + + if (!httpContext.Request.Headers.TryGetValue(DialogTokenHeader, out var dialogToken)) + { + // requestBuilder.SetQuery(""); + // const string message = "{\"errors\": [{\"message\": \"Forbidden, missing header 'DigDir-Dialog-Token'\"}]}"; + // await SendForbiddenAsync(context, message, cancellationToken); + return false; + } + + if (string.IsNullOrWhiteSpace(dialogToken)) + { + // requestBuilder.SetQuery(""); + // const string message = "{\"errors\": [{\"message\": \"Forbidden, empty token\"}]}"; + // await SendForbiddenAsync(context, message, cancellationToken); + return false; + } + + if (!_compactJwsGenerator.VerifyCompactJws(dialogToken!)) + { + // requestBuilder.SetQuery(""); + // const string message = "{\"errors\": [{\"message\": \"Forbidden, invalid token\"}]}"; + // await SendForbiddenAsync(context, message, cancellationToken); + return false; + } + + if (!_compactJwsGenerator.VerifyCompactJwsTimestamp(dialogToken!)) + { + // requestBuilder.SetQuery(""); + // const string message = "{\"errors\": [{\"message\": \"Forbidden, expired token\"}]}"; + // await SendForbiddenAsync(context, message, cancellationToken); + return false; + } + + if (!_compactJwsGenerator.TryGetClaimValue(dialogToken!, "i", out var dialogTokenDialogId)) + { + // requestBuilder.SetQuery(""); + // const string message = "{\"errors\": [{\"message\": \"Forbidden, missing claim 'i', (DialogId)\"}]}"; + // await SendForbiddenAsync(context, message, cancellationToken); + return false; + } + + if (dialogId.ToString() != dialogTokenDialogId) + { + // requestBuilder.SetQuery(""); + // const string message = "{\"errors\": [{\"message\": \"Forbidden, token dialogId does not match subscription dialogId\"}]}"; + // await SendForbiddenAsync(context, message, cancellationToken); + return false; + } + + + return true; + })); } } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/DefinitionNodeExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/DefinitionNodeExtensions.cs new file mode 100644 index 000000000..5128eda65 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/DefinitionNodeExtensions.cs @@ -0,0 +1,48 @@ +using HotChocolate.Language; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; + +public static class DefinitionNodeExtensions +{ + public static bool TryGetSubscriptionDialogId(this IReadOnlyList definitions, out Guid dialogId) + { + dialogId = Guid.Empty; + + foreach (var definition in definitions) + { + if (definition is not OperationDefinitionNode operationDefinition) + { + continue; + } + + if (operationDefinition.Operation != OperationType.Subscription) + { + continue; + } + + if (operationDefinition.SelectionSet.Selections[0] is not FieldNode fieldNode) + { + continue; + } + + var dialogIdArgument = fieldNode.Arguments.SingleOrDefault(x => x.Name.Value == "dialogId"); + + if (dialogIdArgument is null) + { + continue; + } + + if (dialogIdArgument.Value.Value is null) + { + continue; + } + + if (Guid.TryParse(dialogIdArgument.Value.Value.ToString(), out dialogId)) + { + return true; + } + } + + return false; + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs index 631063e70..7b2dac1c5 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs @@ -6,10 +6,10 @@ namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; -[Authorize(Policy = AuthorizationPolicy.EndUser)] public sealed class Subscriptions { [Subscribe] + [Authorize(AuthorizationPolicy.EndUserSubscription, ApplyPolicy.Validation)] [Topic($"{Constants.DialogUpdatedTopic}{{{nameof(dialogId)}}}")] public DialogUpdatedPayload DialogUpdated(Guid dialogId, [EventMessage] Guid eventMessage) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs index 3340e3071..5a264afe0 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs @@ -1,7 +1,14 @@ +using System.Net; using Digdir.Domain.Dialogporten.GraphQL.EndUser; using Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; using Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs; using Digdir.Domain.Dialogporten.Infrastructure.Persistence; +using HotChocolate.AspNetCore; +using HotChocolate.AspNetCore.Serialization; +using HotChocolate.Execution; +using HotChocolate.Utilities; +using Microsoft.AspNetCore.Server.HttpSys; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace Digdir.Domain.Dialogporten.GraphQL; @@ -12,8 +19,8 @@ public static IServiceCollection AddDialogportenGraphQl(this IServiceCollection return services .AddGraphQLServer() // This assumes that subscriptions have been set up by the infrastructure - .AddSubscriptionType() .AddAuthorization() + .AddSubscriptionType() .RegisterDbContext() .AddDiagnosticEventListener() .AddQueryType() diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json index 64bae4621..3a96fd06b 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json @@ -53,14 +53,15 @@ } }, "LocalDevelopment": { - "UseLocalDevelopmentUser": true, - "UseLocalDevelopmentResourceRegister": true, - "UseLocalDevelopmentOrganizationRegister": true, - "UseLocalDevelopmentNameRegister": true, - "UseLocalDevelopmentAltinnAuthorization": true, + "UseLocalDevelopmentUser": false, + "UseLocalDevelopmentResourceRegister": false, + "UseLocalDevelopmentOrganizationRegister": false, + "UseLocalDevelopmentNameRegister": false, + "UseLocalDevelopmentAltinnAuthorization": false, "UseLocalDevelopmentCloudEventBus": true, + "UseLocalDevelopmentCompactJwsGenerator": false, "DisableShortCircuitOutboxDispatcher": true, - "DisableCache": false, - "DisableAuth": true + "DisableCache": true, + "DisableAuth": false } } From a451a39b9c673879a5f662e606854c41d7cbcb0f Mon Sep 17 00:00:00 2001 From: Knut Haug Date: Tue, 17 Sep 2024 14:58:59 +0200 Subject: [PATCH 02/32] --wip-- [skip-ci] --- .../Common/Authorization/AuthorizationOptionsSetup.cs | 3 ++- .../Common/Authorization/AuthorizationPolicy.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 520135c9c..a326b802c 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -4,6 +4,7 @@ using HotChocolate.Authorization; using HotChocolate.Resolvers; using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.Extensions.Options; using AuthorizationOptions = Microsoft.AspNetCore.Authorization.AuthorizationOptions; @@ -51,7 +52,7 @@ public void Configure(AuthorizationOptions options) .Combine(options.DefaultPolicy) .RequireScope(AuthorizationScope.Testing)); - options.AddPolicy("foo", policy => policy + options.AddPolicy(AuthorizationPolicy.EndUserSubscription, policy => policy .Combine(options.GetPolicy(AuthorizationPolicy.EndUser)!) .RequireAssertion(context => { diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs index 9a227445a..1b0c35c75 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationPolicy.cs @@ -5,6 +5,7 @@ namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; internal static class AuthorizationPolicy { public const string EndUser = "enduser"; + public const string EndUserSubscription = "enduserSubscription"; public const string ServiceProvider = "serviceprovider"; public const string ServiceProviderSearch = "serviceproviderSearch"; public const string Testing = "testing"; From b9e64b947254a03ac0157e0f17bb5efb850e0322 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Wed, 18 Sep 2024 10:08:23 +0200 Subject: [PATCH 03/32] delet --- .../AuthorizationOptionsSetup.cs | 29 ++----------------- .../ServiceCollectionExtensions.cs | 7 ----- 2 files changed, 2 insertions(+), 34 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index a326b802c..94c79087a 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -1,10 +1,7 @@ -using System.Security.Claims; -using Digdir.Domain.Dialogporten.Application.Common; +using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; using HotChocolate.Authorization; -using HotChocolate.Resolvers; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Authorization.Infrastructure; using Microsoft.Extensions.Options; using AuthorizationOptions = Microsoft.AspNetCore.Authorization.AuthorizationOptions; @@ -56,9 +53,6 @@ public void Configure(AuthorizationOptions options) .Combine(options.GetPolicy(AuthorizationPolicy.EndUser)!) .RequireAssertion(context => { - // Cast resource to MiddleWareContext - // Get first value from FieldSelection.Arguments (DialogIdSubscriptionInput) - // Get HttpContext from context.Resource.ContextData, key is "HttpContext" and assign to var httpContext if (context.Resource is not AuthorizationContext authContext) { return false; @@ -81,53 +75,34 @@ public void Configure(AuthorizationOptions options) if (!httpContext.Request.Headers.TryGetValue(DialogTokenHeader, out var dialogToken)) { - // requestBuilder.SetQuery(""); - // const string message = "{\"errors\": [{\"message\": \"Forbidden, missing header 'DigDir-Dialog-Token'\"}]}"; - // await SendForbiddenAsync(context, message, cancellationToken); return false; } if (string.IsNullOrWhiteSpace(dialogToken)) { - // requestBuilder.SetQuery(""); - // const string message = "{\"errors\": [{\"message\": \"Forbidden, empty token\"}]}"; - // await SendForbiddenAsync(context, message, cancellationToken); return false; } if (!_compactJwsGenerator.VerifyCompactJws(dialogToken!)) { - // requestBuilder.SetQuery(""); - // const string message = "{\"errors\": [{\"message\": \"Forbidden, invalid token\"}]}"; - // await SendForbiddenAsync(context, message, cancellationToken); return false; } if (!_compactJwsGenerator.VerifyCompactJwsTimestamp(dialogToken!)) { - // requestBuilder.SetQuery(""); - // const string message = "{\"errors\": [{\"message\": \"Forbidden, expired token\"}]}"; - // await SendForbiddenAsync(context, message, cancellationToken); return false; } - if (!_compactJwsGenerator.TryGetClaimValue(dialogToken!, "i", out var dialogTokenDialogId)) + if (!_compactJwsGenerator.TryGetClaimValue(dialogToken!, DialogTokenClaimTypes.DialogId, out var dialogTokenDialogId)) { - // requestBuilder.SetQuery(""); - // const string message = "{\"errors\": [{\"message\": \"Forbidden, missing claim 'i', (DialogId)\"}]}"; - // await SendForbiddenAsync(context, message, cancellationToken); return false; } if (dialogId.ToString() != dialogTokenDialogId) { - // requestBuilder.SetQuery(""); - // const string message = "{\"errors\": [{\"message\": \"Forbidden, token dialogId does not match subscription dialogId\"}]}"; - // await SendForbiddenAsync(context, message, cancellationToken); return false; } - return true; })); } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs index 5a264afe0..098207b51 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs @@ -1,14 +1,7 @@ -using System.Net; using Digdir.Domain.Dialogporten.GraphQL.EndUser; using Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; using Digdir.Domain.Dialogporten.GraphQL.EndUser.SearchDialogs; using Digdir.Domain.Dialogporten.Infrastructure.Persistence; -using HotChocolate.AspNetCore; -using HotChocolate.AspNetCore.Serialization; -using HotChocolate.Execution; -using HotChocolate.Utilities; -using Microsoft.AspNetCore.Server.HttpSys; -using Microsoft.Extensions.DependencyInjection.Extensions; namespace Digdir.Domain.Dialogporten.GraphQL; From 5e4fcf203f4774bae4da9415c8feb8e6eb883b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Wed, 18 Sep 2024 10:11:28 +0200 Subject: [PATCH 04/32] msg --- docs/schema/V1/schema.verified.graphql | 5 +++-- .../Common/Authorization/AuthorizationOptionsSetup.cs | 2 +- .../EndUser/DialogById/Subscriptions.cs | 3 ++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/schema/V1/schema.verified.graphql b/docs/schema/V1/schema.verified.graphql index 35b41b0a6..d0589186a 100644 --- a/docs/schema/V1/schema.verified.graphql +++ b/docs/schema/V1/schema.verified.graphql @@ -210,8 +210,9 @@ type SeenLog { isCurrentEndUser: Boolean! } -type Subscriptions @authorize(policy: "enduser") { - dialogUpdated(dialogId: UUID!): DialogUpdatedPayload! +type Subscriptions { + "Requires a dialog token in the 'DigDir-Dialog-Token' header." + dialogUpdated(dialogId: UUID!): DialogUpdatedPayload! @authorize(policy: "enduserSubscription", apply: VALIDATION) } type Transmission { diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 94c79087a..6094e735f 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -11,7 +11,7 @@ internal sealed class AuthorizationOptionsSetup : IConfigureOptions options, ICompactJwsGenerator compactJwsGenerator) { diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs index 7b2dac1c5..d98b47c74 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs @@ -9,8 +9,9 @@ namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; public sealed class Subscriptions { [Subscribe] - [Authorize(AuthorizationPolicy.EndUserSubscription, ApplyPolicy.Validation)] [Topic($"{Constants.DialogUpdatedTopic}{{{nameof(dialogId)}}}")] + [Authorize(AuthorizationPolicy.EndUserSubscription, ApplyPolicy.Validation)] + [GraphQLDescription($"Requires a dialog token in the '{AuthorizationOptionsSetup.DialogTokenHeader}' header.")] public DialogUpdatedPayload DialogUpdated(Guid dialogId, [EventMessage] Guid eventMessage) { From ba6d9f456f4f97b8b86af72dc4ee028ed5e3fa12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Wed, 18 Sep 2024 10:29:17 +0200 Subject: [PATCH 05/32] aps --- .../appsettings.Development.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json index 3a96fd06b..cef3437a3 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json @@ -53,15 +53,15 @@ } }, "LocalDevelopment": { - "UseLocalDevelopmentUser": false, - "UseLocalDevelopmentResourceRegister": false, - "UseLocalDevelopmentOrganizationRegister": false, - "UseLocalDevelopmentNameRegister": false, - "UseLocalDevelopmentAltinnAuthorization": false, + "UseLocalDevelopmentUser": true, + "UseLocalDevelopmentResourceRegister": true, + "UseLocalDevelopmentOrganizationRegister":true, + "UseLocalDevelopmentNameRegister": true, + "UseLocalDevelopmentAltinnAuthorization": true, "UseLocalDevelopmentCloudEventBus": true, - "UseLocalDevelopmentCompactJwsGenerator": false, + "UseLocalDevelopmentCompactJwsGenerator": true, "DisableShortCircuitOutboxDispatcher": true, - "DisableCache": true, - "DisableAuth": false + "DisableCache": false, + "DisableAuth": true } } From 346509e5c420a43e0751d7063494072979b17d57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Fri, 20 Sep 2024 15:33:36 +0200 Subject: [PATCH 06/32] --wip-- [skip ci] --- .../AuthorizationOptionsSetup.cs | 4 +- .../Authorization/DialogTokenMiddleware.cs | 74 +++++++++++++++++++ .../EndUser/DialogById/Subscriptions.cs | 2 +- 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 6094e735f..602b7a740 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -11,7 +11,7 @@ internal sealed class AuthorizationOptionsSetup : IConfigureOptions options, ICompactJwsGenerator compactJwsGenerator) { @@ -73,7 +73,7 @@ public void Configure(AuthorizationOptions options) return false; } - if (!httpContext.Request.Headers.TryGetValue(DialogTokenHeader, out var dialogToken)) + if (!httpContext.Request.Headers.TryGetValue("", out var dialogToken)) { return false; } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs new file mode 100644 index 000000000..f4876a7da --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -0,0 +1,74 @@ +using System.IdentityModel.Tokens.Jwt; +using Digdir.Domain.Dialogporten.Infrastructure.GraphQl; +using Microsoft.IdentityModel.Tokens; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; + +public class DialogTokenMiddleware +{ + public const string DialogTokenHeader = "DigDir-Dialog-Token"; + private readonly RequestDelegate _next; + + public DialogTokenMiddleware(RequestDelegate next) + { + _next = next; + } + public Task InvokeAsync(HttpContext context) + { + if (!context.Request.Headers.TryGetValue(DialogTokenHeader, out var dialogToken)) + { + return _next(context); + } + var token = dialogToken.FirstOrDefault(); + if (string.IsNullOrEmpty(token)) + { + return _next(context); + } + var tokenValidator = new JwtSecurityTokenHandler(); + if (!tokenValidator.CanReadToken(token)) + { + return _next(context); + } + + var jwtToken = tokenValidator.ReadJwtToken(token); + + // tokenValidator.Vali + + var principal = tokenValidator.ValidateToken(dialogToken, new TokenValidationParameters + { + // validate + }, out var validatedToken); + // // Check if DialogToken is present in headers + // if (context.Request.Headers.ContainsKey("DialogToken")) + // { + // var dialogToken = context.Request.Headers["DialogToken"].ToString(); + // + // // Validate and decode the token + // var handler = new JwtSecurityTokenHandler(); + // var tokenValidationParameters = new TokenValidationParameters + // { + // // Add your validation parameters, e.g., issuer, audience, signing keys, etc. + // }; + // + // try + // { + // // Validate the token and extract claims + // var principal = handler.ValidateToken(dialogToken, tokenValidationParameters, out var validatedToken); + // var dialogClaims = principal.Claims; + // + // // Add the DialogToken claims to the current ClaimsPrincipal + // var identity = new ClaimsIdentity(dialogClaims, "DialogToken"); + // context.User.AddIdentity(identity); + // } + // catch (Exception ex) + // { + // // Handle invalid token scenario + // // Log error or handle accordingly + // } + // } + // + // await _next(context); + + return _next(context); + } +} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs index d98b47c74..12411b198 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/Subscriptions.cs @@ -11,7 +11,7 @@ public sealed class Subscriptions [Subscribe] [Topic($"{Constants.DialogUpdatedTopic}{{{nameof(dialogId)}}}")] [Authorize(AuthorizationPolicy.EndUserSubscription, ApplyPolicy.Validation)] - [GraphQLDescription($"Requires a dialog token in the '{AuthorizationOptionsSetup.DialogTokenHeader}' header.")] + [GraphQLDescription($"Requires a dialog token in the '{DialogTokenMiddleware.DialogTokenHeader}' header.")] public DialogUpdatedPayload DialogUpdated(Guid dialogId, [EventMessage] Guid eventMessage) { From 4d9cff317ddbcf708a2f329a76e7d45ca17f62ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Fri, 20 Sep 2024 21:07:21 +0200 Subject: [PATCH 07/32] foo --- .../Common/ICompactJwsGenerator.cs | 1 - .../AuthorizationOptionsSetup.cs | 50 ++------------- .../Authorization/DialogTokenMiddleware.cs | 61 +++++-------------- .../Program.cs | 1 + 4 files changed, 21 insertions(+), 92 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs index 4184f0b14..a1ff673fe 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs @@ -3,7 +3,6 @@ using System.Text; using System.Text.Json; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; using NSec.Cryptography; namespace Digdir.Domain.Dialogporten.Application.Common; diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 602b7a740..3f4c47cac 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -1,4 +1,4 @@ -using Digdir.Domain.Dialogporten.Application.Common; +using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; using HotChocolate.Authorization; using Microsoft.AspNetCore.Authorization; @@ -10,12 +10,9 @@ namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; internal sealed class AuthorizationOptionsSetup : IConfigureOptions { private readonly GraphQlSettings _options; - private readonly ICompactJwsGenerator _compactJwsGenerator; - // public const string DialogTokenHeader = "DigDir-Dialog-Token"; - public AuthorizationOptionsSetup(IOptions options, ICompactJwsGenerator compactJwsGenerator) + public AuthorizationOptionsSetup(IOptions options) { - _compactJwsGenerator = compactJwsGenerator ?? throw new ArgumentNullException(nameof(compactJwsGenerator)); _options = options.Value ?? throw new ArgumentNullException(nameof(options)); } @@ -58,52 +55,13 @@ public void Configure(AuthorizationOptions options) return false; } - if (!authContext.ContextData.TryGetValue("HttpContext", out var httpContextObj)) - { - return false; - } - - if (httpContextObj is not HttpContext httpContext) - { - return false; - } - if (!authContext.Document.Definitions.TryGetSubscriptionDialogId(out var dialogId)) { return false; } - if (!httpContext.Request.Headers.TryGetValue("", out var dialogToken)) - { - return false; - } - - if (string.IsNullOrWhiteSpace(dialogToken)) - { - return false; - } - - if (!_compactJwsGenerator.VerifyCompactJws(dialogToken!)) - { - return false; - } - - if (!_compactJwsGenerator.VerifyCompactJwsTimestamp(dialogToken!)) - { - return false; - } - - if (!_compactJwsGenerator.TryGetClaimValue(dialogToken!, DialogTokenClaimTypes.DialogId, out var dialogTokenDialogId)) - { - return false; - } - - if (dialogId.ToString() != dialogTokenDialogId) - { - return false; - } - - return true; + context.User.TryGetClaimValue("dialogId", out var dialogIdClaimValue); + return dialogId.ToString() == dialogIdClaimValue; })); } } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index f4876a7da..b013db1ee 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -1,73 +1,44 @@ -using System.IdentityModel.Tokens.Jwt; -using Digdir.Domain.Dialogporten.Infrastructure.GraphQl; -using Microsoft.IdentityModel.Tokens; +using System.Security.Claims; +using Digdir.Domain.Dialogporten.Application.Common; namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; -public class DialogTokenMiddleware +public sealed class DialogTokenMiddleware { public const string DialogTokenHeader = "DigDir-Dialog-Token"; private readonly RequestDelegate _next; + private readonly ICompactJwsGenerator _compactJwsGenerator; - public DialogTokenMiddleware(RequestDelegate next) + public DialogTokenMiddleware(RequestDelegate next, ICompactJwsGenerator compactJwsGenerator) { _next = next; + _compactJwsGenerator = compactJwsGenerator; } + public Task InvokeAsync(HttpContext context) { if (!context.Request.Headers.TryGetValue(DialogTokenHeader, out var dialogToken)) { return _next(context); } + var token = dialogToken.FirstOrDefault(); - if (string.IsNullOrEmpty(token)) + if (!_compactJwsGenerator.VerifyCompactJws(token ?? string.Empty)) { return _next(context); } - var tokenValidator = new JwtSecurityTokenHandler(); - if (!tokenValidator.CanReadToken(token)) + + if (!_compactJwsGenerator.VerifyCompactJwsTimestamp(token!)) { return _next(context); } - var jwtToken = tokenValidator.ReadJwtToken(token); - - // tokenValidator.Vali - - var principal = tokenValidator.ValidateToken(dialogToken, new TokenValidationParameters + if (!_compactJwsGenerator.TryGetClaimValue(dialogToken!, DialogTokenClaimTypes.DialogId, out var dialogTokenDialogId)) { - // validate - }, out var validatedToken); - // // Check if DialogToken is present in headers - // if (context.Request.Headers.ContainsKey("DialogToken")) - // { - // var dialogToken = context.Request.Headers["DialogToken"].ToString(); - // - // // Validate and decode the token - // var handler = new JwtSecurityTokenHandler(); - // var tokenValidationParameters = new TokenValidationParameters - // { - // // Add your validation parameters, e.g., issuer, audience, signing keys, etc. - // }; - // - // try - // { - // // Validate the token and extract claims - // var principal = handler.ValidateToken(dialogToken, tokenValidationParameters, out var validatedToken); - // var dialogClaims = principal.Claims; - // - // // Add the DialogToken claims to the current ClaimsPrincipal - // var identity = new ClaimsIdentity(dialogClaims, "DialogToken"); - // context.User.AddIdentity(identity); - // } - // catch (Exception ex) - // { - // // Handle invalid token scenario - // // Log error or handle accordingly - // } - // } - // - // await _next(context); + return _next(context); + } + + context.User.AddIdentity(new ClaimsIdentity([new Claim("dialogId", dialogTokenDialogId)])); return _next(context); } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs index bc73a3cf4..e85ab7ac3 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Program.cs @@ -100,6 +100,7 @@ static void BuildAndRun(string[] args) app.UseJwtSchemeSelector() .UseAuthentication() .UseAuthorization() + .UseMiddleware() .UseSerilogRequestLogging() .UseAzureConfiguration(); From 878ce5367484157b721abb809bf99099733b83bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Sat, 21 Sep 2024 23:26:03 +0200 Subject: [PATCH 08/32] sup --- .../Common/ICompactJwsGenerator.cs | 83 ++----------------- .../Authorization/DialogTokenMiddleware.cs | 32 ++++--- 2 files changed, 30 insertions(+), 85 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs index a1ff673fe..5e60e6317 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs @@ -11,8 +11,6 @@ public interface ICompactJwsGenerator { string GetCompactJws(Dictionary claims); bool VerifyCompactJws(string compactJws); - bool VerifyCompactJwsTimestamp(string compactJwt); - bool TryGetClaimValue(string compactJws, string claim, [NotNullWhen(true)] out string? value); } public sealed class Ed25519Generator : ICompactJwsGenerator @@ -64,80 +62,24 @@ public string GetCompactJws(Dictionary claims) public bool VerifyCompactJws(string compactJws) { - try - { - var parts = compactJws.Split('.'); - if (parts.Length != 3) return false; - - var header = Base64Url.Decode(parts[0]); + var parts = compactJws.Split('.'); + if (parts.Length != 3) return false; - var headerJson = JsonSerializer.Deserialize(header); - if (headerJson.TryGetProperty("kid", out var kid)) - { - if (kid.GetString() != _kid) return false; - } - else - { - return false; - } + var header = Base64Url.Decode(parts[0]); - var signature = Base64Url.Decode(parts[2]); - return SignatureAlgorithm.Ed25519.Verify(_publicKey!, Encoding.UTF8.GetBytes(parts[0] + '.' + parts[1]), signature); - } - catch (Exception) + var headerJson = JsonSerializer.Deserialize(header); + if (headerJson.TryGetProperty("kid", out var kid)) { - // Log? - return false; - } - } - - public bool VerifyCompactJwsTimestamp(string compactJwt) - { - try - { - var parts = compactJwt.Split('.'); - if (parts.Length != 3) return false; - - var payload = Base64Url.Decode(parts[1]); - var payloadJson = JsonSerializer.Deserialize(payload); - if (!payloadJson.TryGetProperty(DialogTokenClaimTypes.Expires, out var exp)) return false; - - var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - return exp.GetInt64() > now; + if (kid.GetString() != _kid) return false; } - catch (Exception) + else { - // Log? return false; } - } - public bool TryGetClaimValue(string compactJws, string claim, [NotNullWhen(true)] out string? value) - { - value = null; - try - { - var parts = compactJws.Split('.'); - if (parts.Length != 3) - { - return false; - } + var signature = Base64Url.Decode(parts[2]); + return SignatureAlgorithm.Ed25519.Verify(_publicKey!, Encoding.UTF8.GetBytes(parts[0] + '.' + parts[1]), signature); - var payload = Base64Url.Decode(parts[1]); - var payloadJson = JsonSerializer.Deserialize(payload); - if (!payloadJson.TryGetProperty(claim, out var claimValue)) - { - return false; - } - - value = claimValue.GetString(); - return value != null; - } - catch (Exception) - { - // Log? - return false; - } } private void InitSigningKey() @@ -163,13 +105,6 @@ public LocalDevelopmentCompactJwsGeneratorDecorator(ICompactJwsGenerator _) public string GetCompactJws(Dictionary claims) => "local-development-jws"; public bool VerifyCompactJws(string compactJws) => true; - public bool VerifyCompactJwsTimestamp(string compactJwt) => true; - - public bool TryGetClaimValue(string compactJws, string claim, [NotNullWhen(true)] out string? value) - { - value = "local-development-claim"; - return true; - } } public static class Base64Url diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index b013db1ee..ffd099ba6 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -1,5 +1,7 @@ +using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using Digdir.Domain.Dialogporten.Application.Common; +using Microsoft.IdentityModel.Tokens; namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; @@ -23,23 +25,31 @@ public Task InvokeAsync(HttpContext context) } var token = dialogToken.FirstOrDefault(); - if (!_compactJwsGenerator.VerifyCompactJws(token ?? string.Empty)) + var tokenHandler = new JwtSecurityTokenHandler(); + try { - return _next(context); - } + tokenHandler.ValidateToken(token, new TokenValidationParameters + { + ValidateIssuer = false, + ValidateAudience = false, + // ValidateLifetime = false, + ValidateIssuerSigningKey = false, + SignatureValidator = (token, parameters) => + { + var jwt = new JwtSecurityToken(token); + return jwt; + }, + }, out var securityToken); + + var jwt = securityToken as JwtSecurityToken; + context.User.AddIdentity(new ClaimsIdentity(jwt!.Claims)); - if (!_compactJwsGenerator.VerifyCompactJwsTimestamp(token!)) - { return _next(context); } - - if (!_compactJwsGenerator.TryGetClaimValue(dialogToken!, DialogTokenClaimTypes.DialogId, out var dialogTokenDialogId)) + catch (Exception e) { + Console.WriteLine(e); return _next(context); } - - context.User.AddIdentity(new ClaimsIdentity([new Claim("dialogId", dialogTokenDialogId)])); - - return _next(context); } } From 67cc040ce2b3268f6d016076542a36412349916b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 09:15:27 +0200 Subject: [PATCH 09/32] --wip-- [skip ci] --- .../Common/ICompactJwsGenerator.cs | 1 - .../AuthorizationOptionsSetup.cs | 5 +-- .../Authorization/DialogTokenMiddleware.cs | 32 +++++++++++++------ .../appsettings.Development.json | 14 ++++---- .../appsettings.Development.json | 2 +- 5 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs index 5e60e6317..3d7697e11 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs @@ -1,5 +1,4 @@ using System.Buffers.Text; -using System.Diagnostics.CodeAnalysis; using System.Text; using System.Text.Json; using Microsoft.Extensions.Options; diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 3f4c47cac..69bb22184 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -1,4 +1,5 @@ -using Digdir.Domain.Dialogporten.Application.Common.Extensions; +using Digdir.Domain.Dialogporten.Application.Common; +using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; using HotChocolate.Authorization; using Microsoft.AspNetCore.Authorization; @@ -60,7 +61,7 @@ public void Configure(AuthorizationOptions options) return false; } - context.User.TryGetClaimValue("dialogId", out var dialogIdClaimValue); + context.User.TryGetClaimValue(DialogTokenClaimTypes.DialogId, out var dialogIdClaimValue); return dialogId.ToString() == dialogIdClaimValue; })); } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index ffd099ba6..95676be37 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -1,7 +1,11 @@ using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; +using System.Text; +using Digdir.Domain.Dialogporten.Application; using Digdir.Domain.Dialogporten.Application.Common; +using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; +using NSec.Cryptography; namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; @@ -9,12 +13,19 @@ public sealed class DialogTokenMiddleware { public const string DialogTokenHeader = "DigDir-Dialog-Token"; private readonly RequestDelegate _next; - private readonly ICompactJwsGenerator _compactJwsGenerator; + private readonly IOptions _applicationSettings; + private readonly PublicKey _publicKey; + private readonly string _issuer; - public DialogTokenMiddleware(RequestDelegate next, ICompactJwsGenerator compactJwsGenerator) + public DialogTokenMiddleware(RequestDelegate next, IOptions applicationSettings) { _next = next; - _compactJwsGenerator = compactJwsGenerator; + _applicationSettings = applicationSettings; + + var keyPair = applicationSettings.Value.Dialogporten.Ed25519KeyPairs.Primary; + _publicKey = PublicKey.Import(SignatureAlgorithm.Ed25519, + Base64Url.Decode(keyPair.PublicComponent), KeyBlobFormat.RawPublicKey); + _issuer = applicationSettings.Value.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + "/api/v1"; } public Task InvokeAsync(HttpContext context) @@ -30,14 +41,17 @@ public Task InvokeAsync(HttpContext context) { tokenHandler.ValidateToken(token, new TokenValidationParameters { - ValidateIssuer = false, ValidateAudience = false, - // ValidateLifetime = false, - ValidateIssuerSigningKey = false, - SignatureValidator = (token, parameters) => + ValidIssuer = _issuer, + SignatureValidator = (encodedToken, parameters) => { - var jwt = new JwtSecurityToken(token); - return jwt; + var jwt = new JwtSecurityToken(encodedToken); + + var signature = Base64Url.Decode(jwt.RawSignature); + var signatureIsValid = SignatureAlgorithm.Ed25519 + .Verify(_publicKey, Encoding.UTF8.GetBytes(jwt.EncodedHeader + '.' + jwt.EncodedPayload), signature); + + return !signatureIsValid ? throw new SecurityTokenInvalidSignatureException("Invalid token signature.") : (SecurityToken)jwt; }, }, out var securityToken); diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json index cef3437a3..ece5b780e 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json @@ -53,15 +53,15 @@ } }, "LocalDevelopment": { - "UseLocalDevelopmentUser": true, - "UseLocalDevelopmentResourceRegister": true, - "UseLocalDevelopmentOrganizationRegister":true, - "UseLocalDevelopmentNameRegister": true, - "UseLocalDevelopmentAltinnAuthorization": true, + "UseLocalDevelopmentUser": false, + "UseLocalDevelopmentResourceRegister": false, + "UseLocalDevelopmentOrganizationRegister":false, + "UseLocalDevelopmentNameRegister": false, + "UseLocalDevelopmentAltinnAuthorization": false, "UseLocalDevelopmentCloudEventBus": true, - "UseLocalDevelopmentCompactJwsGenerator": true, + "UseLocalDevelopmentCompactJwsGenerator": false, "DisableShortCircuitOutboxDispatcher": true, "DisableCache": false, - "DisableAuth": true + "DisableAuth": false } } diff --git a/src/Digdir.Domain.Dialogporten.WebApi/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.WebApi/appsettings.Development.json index e1067c8d2..04a83b520 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.WebApi/appsettings.Development.json @@ -72,7 +72,7 @@ "UseLocalDevelopmentNameRegister": true, "UseLocalDevelopmentAltinnAuthorization": true, "UseLocalDevelopmentCloudEventBus": true, - "UseLocalDevelopmentCompactJwsGenerator": true, + "UseLocalDevelopmentCompactJwsGenerator": false, "DisableShortCircuitOutboxDispatcher": true, "DisableCache": true, "DisableAuth": true From 1f39ff03dd793024c7f43748f2fcc0d561a4bf6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 14:27:43 +0200 Subject: [PATCH 10/32] cln --- .../Common/Authorization/DialogTokenMiddleware.cs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index 95676be37..1c9cd6139 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -13,14 +13,12 @@ public sealed class DialogTokenMiddleware { public const string DialogTokenHeader = "DigDir-Dialog-Token"; private readonly RequestDelegate _next; - private readonly IOptions _applicationSettings; private readonly PublicKey _publicKey; private readonly string _issuer; public DialogTokenMiddleware(RequestDelegate next, IOptions applicationSettings) { _next = next; - _applicationSettings = applicationSettings; var keyPair = applicationSettings.Value.Dialogporten.Ed25519KeyPairs.Primary; _publicKey = PublicKey.Import(SignatureAlgorithm.Ed25519, @@ -43,7 +41,7 @@ public Task InvokeAsync(HttpContext context) { ValidateAudience = false, ValidIssuer = _issuer, - SignatureValidator = (encodedToken, parameters) => + SignatureValidator = (encodedToken, _) => { var jwt = new JwtSecurityToken(encodedToken); @@ -60,9 +58,8 @@ public Task InvokeAsync(HttpContext context) return _next(context); } - catch (Exception e) + catch (Exception) { - Console.WriteLine(e); return _next(context); } } From 78c5947be1d5409f3b4ee725f2c36656ddbe21ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 15:55:24 +0200 Subject: [PATCH 11/32] cln --- .../Common/ICompactJwsGenerator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs index 3d7697e11..6b1a69f38 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/ICompactJwsGenerator.cs @@ -78,7 +78,6 @@ public bool VerifyCompactJws(string compactJws) var signature = Base64Url.Decode(parts[2]); return SignatureAlgorithm.Ed25519.Verify(_publicKey!, Encoding.UTF8.GetBytes(parts[0] + '.' + parts[1]), signature); - } private void InitSigningKey() From 45226117a4dcff106fc806395cec98a74836c35a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 16:01:41 +0200 Subject: [PATCH 12/32] cln --- .../ServiceCollectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs index 098207b51..3340e3071 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/ServiceCollectionExtensions.cs @@ -12,8 +12,8 @@ public static IServiceCollection AddDialogportenGraphQl(this IServiceCollection return services .AddGraphQLServer() // This assumes that subscriptions have been set up by the infrastructure - .AddAuthorization() .AddSubscriptionType() + .AddAuthorization() .RegisterDbContext() .AddDiagnosticEventListener() .AddQueryType() From a619b757d81c25b53f2e4ce6179276d13d22e39d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 16:02:35 +0200 Subject: [PATCH 13/32] cln --- .../appsettings.Development.json | 14 +++++++------- .../appsettings.Development.json | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json index ece5b780e..cef3437a3 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json @@ -53,15 +53,15 @@ } }, "LocalDevelopment": { - "UseLocalDevelopmentUser": false, - "UseLocalDevelopmentResourceRegister": false, - "UseLocalDevelopmentOrganizationRegister":false, - "UseLocalDevelopmentNameRegister": false, - "UseLocalDevelopmentAltinnAuthorization": false, + "UseLocalDevelopmentUser": true, + "UseLocalDevelopmentResourceRegister": true, + "UseLocalDevelopmentOrganizationRegister":true, + "UseLocalDevelopmentNameRegister": true, + "UseLocalDevelopmentAltinnAuthorization": true, "UseLocalDevelopmentCloudEventBus": true, - "UseLocalDevelopmentCompactJwsGenerator": false, + "UseLocalDevelopmentCompactJwsGenerator": true, "DisableShortCircuitOutboxDispatcher": true, "DisableCache": false, - "DisableAuth": false + "DisableAuth": true } } diff --git a/src/Digdir.Domain.Dialogporten.WebApi/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.WebApi/appsettings.Development.json index 04a83b520..e1067c8d2 100644 --- a/src/Digdir.Domain.Dialogporten.WebApi/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.WebApi/appsettings.Development.json @@ -72,7 +72,7 @@ "UseLocalDevelopmentNameRegister": true, "UseLocalDevelopmentAltinnAuthorization": true, "UseLocalDevelopmentCloudEventBus": true, - "UseLocalDevelopmentCompactJwsGenerator": false, + "UseLocalDevelopmentCompactJwsGenerator": true, "DisableShortCircuitOutboxDispatcher": true, "DisableCache": true, "DisableAuth": true From 8baae4e8b05a4c65ea14f2638650723e75061fa8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 16:04:10 +0200 Subject: [PATCH 14/32] cln --- .../appsettings.Development.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json index cef3437a3..5b7e0bc5e 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json @@ -59,7 +59,6 @@ "UseLocalDevelopmentNameRegister": true, "UseLocalDevelopmentAltinnAuthorization": true, "UseLocalDevelopmentCloudEventBus": true, - "UseLocalDevelopmentCompactJwsGenerator": true, "DisableShortCircuitOutboxDispatcher": true, "DisableCache": false, "DisableAuth": true From 16bd19707b702d61ed977ca19c2a2a6f47b7b6a9 Mon Sep 17 00:00:00 2001 From: Knut Haug Date: Mon, 23 Sep 2024 17:05:04 +0200 Subject: [PATCH 15/32] space --- .../appsettings.Development.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json index 5b7e0bc5e..64bae4621 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json +++ b/src/Digdir.Domain.Dialogporten.GraphQL/appsettings.Development.json @@ -55,7 +55,7 @@ "LocalDevelopment": { "UseLocalDevelopmentUser": true, "UseLocalDevelopmentResourceRegister": true, - "UseLocalDevelopmentOrganizationRegister":true, + "UseLocalDevelopmentOrganizationRegister": true, "UseLocalDevelopmentNameRegister": true, "UseLocalDevelopmentAltinnAuthorization": true, "UseLocalDevelopmentCloudEventBus": true, From 03b7f7e384593e5b501d42863e590511d8c205cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 18:06:24 +0200 Subject: [PATCH 16/32] rabbito --- .../AuthorizationOptionsSetup.cs | 9 +++- .../HotChocolate/DefinitionNodeExtensions.cs | 48 ------------------- .../OperationDefinitionNodeExtensions.cs | 24 ++++++++++ 3 files changed, 32 insertions(+), 49 deletions(-) delete mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/DefinitionNodeExtensions.cs create mode 100644 src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 69bb22184..e63404a4d 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -2,6 +2,7 @@ using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; using HotChocolate.Authorization; +using HotChocolate.Language; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; using AuthorizationOptions = Microsoft.AspNetCore.Authorization.AuthorizationOptions; @@ -56,7 +57,13 @@ public void Configure(AuthorizationOptions options) return false; } - if (!authContext.Document.Definitions.TryGetSubscriptionDialogId(out var dialogId)) + var definition = authContext.Document.Definitions[0]; + + if (definition is not OperationDefinitionNode operationDefinition) return false; + + if (operationDefinition.Operation != OperationType.Subscription) return false; + + if (!operationDefinition.TryGetDialogEventsSubscriptionDialogId(out var dialogId)) { return false; } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/DefinitionNodeExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/DefinitionNodeExtensions.cs deleted file mode 100644 index 5128eda65..000000000 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/DefinitionNodeExtensions.cs +++ /dev/null @@ -1,48 +0,0 @@ -using HotChocolate.Language; - -namespace Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; - -public static class DefinitionNodeExtensions -{ - public static bool TryGetSubscriptionDialogId(this IReadOnlyList definitions, out Guid dialogId) - { - dialogId = Guid.Empty; - - foreach (var definition in definitions) - { - if (definition is not OperationDefinitionNode operationDefinition) - { - continue; - } - - if (operationDefinition.Operation != OperationType.Subscription) - { - continue; - } - - if (operationDefinition.SelectionSet.Selections[0] is not FieldNode fieldNode) - { - continue; - } - - var dialogIdArgument = fieldNode.Arguments.SingleOrDefault(x => x.Name.Value == "dialogId"); - - if (dialogIdArgument is null) - { - continue; - } - - if (dialogIdArgument.Value.Value is null) - { - continue; - } - - if (Guid.TryParse(dialogIdArgument.Value.Value.ToString(), out dialogId)) - { - return true; - } - } - - return false; - } -} diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs new file mode 100644 index 000000000..ca51dfc91 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs @@ -0,0 +1,24 @@ +using Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; +using HotChocolate.Language; + +namespace Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; + +public static class OperationDefinitionNodeExtensions +{ + public static bool TryGetDialogEventsSubscriptionDialogId(this OperationDefinitionNode definition, out Guid dialogId) + { + dialogId = Guid.Empty; + + var dialogEventsSelection = definition.SelectionSet.Selections.FirstOrDefault(x => + x is FieldNode fieldNode && fieldNode.Name.Value + .Equals(nameof(Subscriptions.DialogEvents), StringComparison.OrdinalIgnoreCase)); + + if (dialogEventsSelection is not FieldNode fieldNode) return false; + + var dialogIdArgument = fieldNode.Arguments.FirstOrDefault(x => x.Name.Value.Equals("dialogId", StringComparison.OrdinalIgnoreCase)); + + if (dialogIdArgument?.Value.Value is null) return false; + + return Guid.TryParse(dialogIdArgument.Value.Value.ToString(), out dialogId); + } +} From 2a918c4ff7212af10225e636b340d988382ca5fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 18:43:10 +0200 Subject: [PATCH 17/32] check definition count --- .../Common/Authorization/AuthorizationOptionsSetup.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index e63404a4d..d8144f987 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -57,6 +57,8 @@ public void Configure(AuthorizationOptions options) return false; } + if (authContext.Document.Definitions.Count == 0) return false; + var definition = authContext.Document.Definitions[0]; if (definition is not OperationDefinitionNode operationDefinition) return false; From 5d88888ec6a4c01609a394a5cc3a281d4b8eeda7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Mon, 23 Sep 2024 21:53:17 +0200 Subject: [PATCH 18/32] "cln" --- .../Common/Authorization/AuthorizationOptionsSetup.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index d8144f987..5e4e1471b 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -65,10 +65,7 @@ public void Configure(AuthorizationOptions options) if (operationDefinition.Operation != OperationType.Subscription) return false; - if (!operationDefinition.TryGetDialogEventsSubscriptionDialogId(out var dialogId)) - { - return false; - } + if (!operationDefinition.TryGetDialogEventsSubscriptionDialogId(out var dialogId)) return false; context.User.TryGetClaimValue(DialogTokenClaimTypes.DialogId, out var dialogIdClaimValue); return dialogId.ToString() == dialogIdClaimValue; From 4059ccf493a3fc8f8bda411e89c32851e918252d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 24 Sep 2024 22:31:41 +0200 Subject: [PATCH 19/32] add jwt null check --- .../Common/Authorization/DialogTokenMiddleware.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index 1c9cd6139..f59055733 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -53,8 +53,12 @@ public Task InvokeAsync(HttpContext context) }, }, out var securityToken); - var jwt = securityToken as JwtSecurityToken; - context.User.AddIdentity(new ClaimsIdentity(jwt!.Claims)); + if (securityToken is not JwtSecurityToken jwt) + { + return _next(context); + } + + context.User.AddIdentity(new ClaimsIdentity(jwt.Claims)); return _next(context); } From 910fa8e006ac88b35d2da89363bfe4484725780d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 24 Sep 2024 22:35:08 +0200 Subject: [PATCH 20/32] Throw if EndUser policy is not found --- .../Common/Authorization/AuthorizationOptionsSetup.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 5e4e1471b..bde40128d 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -49,13 +49,11 @@ public void Configure(AuthorizationOptions options) .RequireScope(AuthorizationScope.Testing)); options.AddPolicy(AuthorizationPolicy.EndUserSubscription, policy => policy - .Combine(options.GetPolicy(AuthorizationPolicy.EndUser)!) + .Combine(options.GetPolicy(AuthorizationPolicy.EndUser) + ?? throw new InvalidOperationException($"{AuthorizationPolicy.EndUser} policy not found")) .RequireAssertion(context => { - if (context.Resource is not AuthorizationContext authContext) - { - return false; - } + if (context.Resource is not AuthorizationContext authContext) return false; if (authContext.Document.Definitions.Count == 0) return false; From 94584d8f2a023ae2d7900562515839f532851de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 24 Sep 2024 22:37:37 +0200 Subject: [PATCH 21/32] extract vars --- .../HotChocolate/OperationDefinitionNodeExtensions.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs index ca51dfc91..cf2bdab20 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs @@ -5,17 +5,20 @@ namespace Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; public static class OperationDefinitionNodeExtensions { + private const string DialogIdArgumentName = "dialogId"; + private const string DialogEventsFieldName = nameof(Subscriptions.DialogEvents); + public static bool TryGetDialogEventsSubscriptionDialogId(this OperationDefinitionNode definition, out Guid dialogId) { dialogId = Guid.Empty; var dialogEventsSelection = definition.SelectionSet.Selections.FirstOrDefault(x => x is FieldNode fieldNode && fieldNode.Name.Value - .Equals(nameof(Subscriptions.DialogEvents), StringComparison.OrdinalIgnoreCase)); + .Equals(DialogEventsFieldName, StringComparison.OrdinalIgnoreCase)); if (dialogEventsSelection is not FieldNode fieldNode) return false; - var dialogIdArgument = fieldNode.Arguments.FirstOrDefault(x => x.Name.Value.Equals("dialogId", StringComparison.OrdinalIgnoreCase)); + var dialogIdArgument = fieldNode.Arguments.FirstOrDefault(x => x.Name.Value.Equals(DialogIdArgumentName, StringComparison.OrdinalIgnoreCase)); if (dialogIdArgument?.Value.Value is null) return false; From 21824a3c30938a35c464bce94a30b32023801ed6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 24 Sep 2024 22:51:36 +0200 Subject: [PATCH 22/32] Create dialog token issuer version constant --- .../Common/Authorization/Constants.cs | 1 + .../Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs | 2 +- .../Common/Authorization/DialogTokenMiddleware.cs | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs b/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs index 2ada57e95..7dab0a349 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs @@ -2,6 +2,7 @@ public static class Constants { + public const string DialogTokenIssuerVersion = "/api/v1"; public const string MainResource = "main"; public const string ReadAction = "read"; public const string TransmissionReadAction = "transmissionread"; diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs index d8d9b0336..4cda9af8d 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs @@ -134,7 +134,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo dialogDto.DialogToken = _dialogTokenGenerator.GetDialogToken( dialog, authorizationResult, - "/api/v1" + Constants.DialogTokenIssuerVersion ); DecorateWithAuthorization(dialogDto, authorizationResult); diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index f59055733..ec3e00717 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using NSec.Cryptography; +using static Digdir.Domain.Dialogporten.Application.Common.Authorization.Constants; namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; @@ -23,7 +24,7 @@ public DialogTokenMiddleware(RequestDelegate next, IOptions var keyPair = applicationSettings.Value.Dialogporten.Ed25519KeyPairs.Primary; _publicKey = PublicKey.Import(SignatureAlgorithm.Ed25519, Base64Url.Decode(keyPair.PublicComponent), KeyBlobFormat.RawPublicKey); - _issuer = applicationSettings.Value.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + "/api/v1"; + _issuer = applicationSettings.Value.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + DialogTokenIssuerVersion; } public Task InvokeAsync(HttpContext context) From 03dc1f411c95b321ca037182b68e3fd191064a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 24 Sep 2024 23:01:48 +0200 Subject: [PATCH 23/32] Add XML comment --- .../HotChocolate/OperationDefinitionNodeExtensions.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs index cf2bdab20..d63e3353e 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs @@ -8,6 +8,12 @@ public static class OperationDefinitionNodeExtensions private const string DialogIdArgumentName = "dialogId"; private const string DialogEventsFieldName = nameof(Subscriptions.DialogEvents); + /// + /// Attempts to extract the dialog ID from a DialogEvents subscription operation. + /// + /// The GraphQL operation definition node. + /// When this method returns, contains the extracted dialog ID if found; otherwise, Guid.Empty. + /// True if the dialog ID was successfully extracted; otherwise, false. public static bool TryGetDialogEventsSubscriptionDialogId(this OperationDefinitionNode definition, out Guid dialogId) { dialogId = Guid.Empty; From 26138ce5b1cca2d8d58a503585848209c082b808 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 24 Sep 2024 23:03:51 +0200 Subject: [PATCH 24/32] Split ternary --- .../Common/Authorization/DialogTokenMiddleware.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index ec3e00717..6ae36d92b 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -50,7 +50,12 @@ public Task InvokeAsync(HttpContext context) var signatureIsValid = SignatureAlgorithm.Ed25519 .Verify(_publicKey, Encoding.UTF8.GetBytes(jwt.EncodedHeader + '.' + jwt.EncodedPayload), signature); - return !signatureIsValid ? throw new SecurityTokenInvalidSignatureException("Invalid token signature.") : (SecurityToken)jwt; + if (signatureIsValid) + { + return jwt; + } + + throw new SecurityTokenInvalidSignatureException("Invalid token signature."); }, }, out var securityToken); From b45d4b3cc2ac60596c52331c5c980c10139f17ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 24 Sep 2024 23:06:06 +0200 Subject: [PATCH 25/32] Use constant --- .../Queries/Get/GetOauthAuthorizationServerQuery.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerQuery.cs index e8ee08a6e..d686df1e2 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerQuery.cs @@ -1,4 +1,5 @@ -using MediatR; +using Digdir.Domain.Dialogporten.Application.Common.Authorization; +using MediatR; using Microsoft.Extensions.Options; namespace Digdir.Domain.Dialogporten.Application.Features.V1.WellKnown.OauthAuthorizationServer.Queries.Get; @@ -17,7 +18,7 @@ public GetOauthAuthorizationServerQueryHandler( public async Task Handle(GetOauthAuthorizationServerQuery request, CancellationToken cancellationToken) { - var issuerUrl = _applicationSettings.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + "/api/v1"; + var issuerUrl = _applicationSettings.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + Constants.DialogTokenIssuerVersion; return await Task.FromResult(new GetOauthAuthorizationServerDto { Issuer = issuerUrl, From b9581947dcb35d193a205cef9c3912344d2428de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Tue, 24 Sep 2024 23:27:01 +0200 Subject: [PATCH 26/32] Move issuer version constant to V1 --- .../Common/Authorization/Constants.cs | 1 - .../Features/V1/Common/Authorization/Constants.cs | 6 ++++++ .../V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs | 3 ++- .../Queries/Get/GetOauthAuthorizationServerQuery.cs | 1 + .../Common/Authorization/DialogTokenMiddleware.cs | 2 +- 5 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Authorization/Constants.cs diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs b/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs index 7dab0a349..2ada57e95 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs @@ -2,7 +2,6 @@ public static class Constants { - public const string DialogTokenIssuerVersion = "/api/v1"; public const string MainResource = "main"; public const string ReadAction = "read"; public const string TransmissionReadAction = "transmissionread"; diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Authorization/Constants.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Authorization/Constants.cs new file mode 100644 index 000000000..338147c5c --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Authorization/Constants.cs @@ -0,0 +1,6 @@ +namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Authorization; + +public static class Constants +{ + public const string DialogTokenIssuerVersion = "/api/v1"; +} diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs index 4cda9af8d..7360f0707 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs @@ -9,6 +9,7 @@ using MediatR; using Microsoft.EntityFrameworkCore; using OneOf; +using static Digdir.Domain.Dialogporten.Application.Features.V1.Common.Authorization.Constants; namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get; @@ -134,7 +135,7 @@ public async Task Handle(GetDialogQuery request, CancellationTo dialogDto.DialogToken = _dialogTokenGenerator.GetDialogToken( dialog, authorizationResult, - Constants.DialogTokenIssuerVersion + DialogTokenIssuerVersion ); DecorateWithAuthorization(dialogDto, authorizationResult); diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerQuery.cs index d686df1e2..cc3e8ba1c 100644 --- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerQuery.cs +++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/WellKnown/OauthAuthorizationServer/Queries/Get/GetOauthAuthorizationServerQuery.cs @@ -1,6 +1,7 @@ using Digdir.Domain.Dialogporten.Application.Common.Authorization; using MediatR; using Microsoft.Extensions.Options; +using Constants = Digdir.Domain.Dialogporten.Application.Features.V1.Common.Authorization.Constants; namespace Digdir.Domain.Dialogporten.Application.Features.V1.WellKnown.OauthAuthorizationServer.Queries.Get; diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index 6ae36d92b..314dc8966 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -6,7 +6,7 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using NSec.Cryptography; -using static Digdir.Domain.Dialogporten.Application.Common.Authorization.Constants; +using static Digdir.Domain.Dialogporten.Application.Features.V1.Common.Authorization.Constants; namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; From f37c0528ffefe0b5570fcda5c08e05c4e5789730 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Wed, 25 Sep 2024 12:15:10 +0200 Subject: [PATCH 27/32] let it NRE --- .../Common/Authorization/AuthorizationOptionsSetup.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index bde40128d..8ea08fd1d 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -49,8 +49,7 @@ public void Configure(AuthorizationOptions options) .RequireScope(AuthorizationScope.Testing)); options.AddPolicy(AuthorizationPolicy.EndUserSubscription, policy => policy - .Combine(options.GetPolicy(AuthorizationPolicy.EndUser) - ?? throw new InvalidOperationException($"{AuthorizationPolicy.EndUser} policy not found")) + .Combine(options.GetPolicy(AuthorizationPolicy.EndUser)!) .RequireAssertion(context => { if (context.Resource is not AuthorizationContext authContext) return false; From 3acbf6717aae948780d8ce6b1b388e634aaf2218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Wed, 25 Sep 2024 13:03:35 +0200 Subject: [PATCH 28/32] add dialogId claim only --- .../Common/Authorization/DialogTokenMiddleware.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index 314dc8966..170c9792e 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -64,7 +64,13 @@ public Task InvokeAsync(HttpContext context) return _next(context); } - context.User.AddIdentity(new ClaimsIdentity(jwt.Claims)); + var dialogIdClaim = jwt.Claims.FirstOrDefault(x => x.Type == DialogTokenClaimTypes.DialogId); + if (dialogIdClaim is null) + { + return _next(context); + } + + context.User.AddIdentity(new ClaimsIdentity([dialogIdClaim])); return _next(context); } From 01eed88938e77954a4324025abf57207071679c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Wed, 25 Sep 2024 15:12:22 +0200 Subject: [PATCH 29/32] simplify auth assertion, move stuff to extension on auth context --- .../AuthorizationOptionsSetup.cs | 22 ++++--------------- ... AuthorizationHandlerContextExtensions.cs} | 20 +++++++++++++---- 2 files changed, 20 insertions(+), 22 deletions(-) rename src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/{OperationDefinitionNodeExtensions.cs => AuthorizationHandlerContextExtensions.cs} (63%) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index 8ea08fd1d..e0aaad692 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -1,8 +1,6 @@ using Digdir.Domain.Dialogporten.Application.Common; using Digdir.Domain.Dialogporten.Application.Common.Extensions; using Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; -using HotChocolate.Authorization; -using HotChocolate.Language; using Microsoft.AspNetCore.Authorization; using Microsoft.Extensions.Options; using AuthorizationOptions = Microsoft.AspNetCore.Authorization.AuthorizationOptions; @@ -51,21 +49,9 @@ public void Configure(AuthorizationOptions options) options.AddPolicy(AuthorizationPolicy.EndUserSubscription, policy => policy .Combine(options.GetPolicy(AuthorizationPolicy.EndUser)!) .RequireAssertion(context => - { - if (context.Resource is not AuthorizationContext authContext) return false; - - if (authContext.Document.Definitions.Count == 0) return false; - - var definition = authContext.Document.Definitions[0]; - - if (definition is not OperationDefinitionNode operationDefinition) return false; - - if (operationDefinition.Operation != OperationType.Subscription) return false; - - if (!operationDefinition.TryGetDialogEventsSubscriptionDialogId(out var dialogId)) return false; - - context.User.TryGetClaimValue(DialogTokenClaimTypes.DialogId, out var dialogIdClaimValue); - return dialogId.ToString() == dialogIdClaimValue; - })); + context.TryGetDialogEventsSubscriptionDialogId(out var dialogIdTopic) + && context.User.TryGetClaimValue(DialogTokenClaimTypes.DialogId, out var dialogIdClaimValue) + && Guid.TryParse(dialogIdClaimValue, out var dialogIdClaim) + && dialogIdTopic == dialogIdClaim)); } } diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/AuthorizationHandlerContextExtensions.cs similarity index 63% rename from src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs rename to src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/AuthorizationHandlerContextExtensions.cs index d63e3353e..8e99f03d4 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/OperationDefinitionNodeExtensions.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Extensions/HotChocolate/AuthorizationHandlerContextExtensions.cs @@ -1,9 +1,11 @@ using Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById; +using HotChocolate.Authorization; using HotChocolate.Language; +using Microsoft.AspNetCore.Authorization; namespace Digdir.Domain.Dialogporten.GraphQL.Common.Extensions.HotChocolate; -public static class OperationDefinitionNodeExtensions +public static class AuthorizationHandlerContextExtensions { private const string DialogIdArgumentName = "dialogId"; private const string DialogEventsFieldName = nameof(Subscriptions.DialogEvents); @@ -11,14 +13,24 @@ public static class OperationDefinitionNodeExtensions /// /// Attempts to extract the dialog ID from a DialogEvents subscription operation. /// - /// The GraphQL operation definition node. + /// The authorization handler context /// When this method returns, contains the extracted dialog ID if found; otherwise, Guid.Empty. /// True if the dialog ID was successfully extracted; otherwise, false. - public static bool TryGetDialogEventsSubscriptionDialogId(this OperationDefinitionNode definition, out Guid dialogId) + public static bool TryGetDialogEventsSubscriptionDialogId(this AuthorizationHandlerContext context, out Guid dialogId) { dialogId = Guid.Empty; - var dialogEventsSelection = definition.SelectionSet.Selections.FirstOrDefault(x => + if (context.Resource is not AuthorizationContext authContext) return false; + + if (authContext.Document.Definitions.Count == 0) return false; + + var definition = authContext.Document.Definitions[0]; + + if (definition is not OperationDefinitionNode operationDefinition) return false; + + if (operationDefinition.Operation != OperationType.Subscription) return false; + + var dialogEventsSelection = operationDefinition.SelectionSet.Selections.FirstOrDefault(x => x is FieldNode fieldNode && fieldNode.Name.Value .Equals(DialogEventsFieldName, StringComparison.OrdinalIgnoreCase)); From 3bb0bd7c8d2e293a17d61e065d60e48325e4c8fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Wed, 25 Sep 2024 15:22:03 +0200 Subject: [PATCH 30/32] extract method --- .../Authorization/DialogTokenMiddleware.cs | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index 170c9792e..cda932e11 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -24,7 +24,8 @@ public DialogTokenMiddleware(RequestDelegate next, IOptions var keyPair = applicationSettings.Value.Dialogporten.Ed25519KeyPairs.Primary; _publicKey = PublicKey.Import(SignatureAlgorithm.Ed25519, Base64Url.Decode(keyPair.PublicComponent), KeyBlobFormat.RawPublicKey); - _issuer = applicationSettings.Value.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + DialogTokenIssuerVersion; + // _issuer = applicationSettings.Value.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + DialogTokenIssuerVersion; + _issuer = "https://altinn-dev-api.azure-api.net/dialogporten" + DialogTokenIssuerVersion; } public Task InvokeAsync(HttpContext context) @@ -42,21 +43,7 @@ public Task InvokeAsync(HttpContext context) { ValidateAudience = false, ValidIssuer = _issuer, - SignatureValidator = (encodedToken, _) => - { - var jwt = new JwtSecurityToken(encodedToken); - - var signature = Base64Url.Decode(jwt.RawSignature); - var signatureIsValid = SignatureAlgorithm.Ed25519 - .Verify(_publicKey, Encoding.UTF8.GetBytes(jwt.EncodedHeader + '.' + jwt.EncodedPayload), signature); - - if (signatureIsValid) - { - return jwt; - } - - throw new SecurityTokenInvalidSignatureException("Invalid token signature."); - }, + SignatureValidator = ValidateSignature }, out var securityToken); if (securityToken is not JwtSecurityToken jwt) @@ -79,4 +66,20 @@ public Task InvokeAsync(HttpContext context) return _next(context); } } + + private JwtSecurityToken ValidateSignature(string encodedToken, object _) + { + var jwt = new JwtSecurityToken(encodedToken); + + var signature = Base64Url.Decode(jwt.RawSignature); + var signatureIsValid = SignatureAlgorithm.Ed25519 + .Verify(_publicKey, Encoding.UTF8.GetBytes(jwt.EncodedHeader + '.' + jwt.EncodedPayload), signature); + + if (signatureIsValid) + { + return jwt; + } + + throw new SecurityTokenInvalidSignatureException("Invalid token signature."); + } } From e215ecfc1ec958defc7938bb22605a2bbf5adac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Wed, 25 Sep 2024 15:45:03 +0200 Subject: [PATCH 31/32] woops --- .../Common/Authorization/DialogTokenMiddleware.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs index cda932e11..084857991 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs @@ -24,8 +24,7 @@ public DialogTokenMiddleware(RequestDelegate next, IOptions var keyPair = applicationSettings.Value.Dialogporten.Ed25519KeyPairs.Primary; _publicKey = PublicKey.Import(SignatureAlgorithm.Ed25519, Base64Url.Decode(keyPair.PublicComponent), KeyBlobFormat.RawPublicKey); - // _issuer = applicationSettings.Value.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + DialogTokenIssuerVersion; - _issuer = "https://altinn-dev-api.azure-api.net/dialogporten" + DialogTokenIssuerVersion; + _issuer = applicationSettings.Value.Dialogporten.BaseUri.AbsoluteUri.TrimEnd('/') + DialogTokenIssuerVersion; } public Task InvokeAsync(HttpContext context) From 0b1faa3466a3b1dc8cdac17fc85b62b18b69b8cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20J=C3=B8rgen=20Skogstad?= Date: Thu, 26 Sep 2024 09:35:22 +0200 Subject: [PATCH 32/32] check fer nul --- .../Common/Authorization/AuthorizationOptionsSetup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs index e0aaad692..8785bd5c4 100644 --- a/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs +++ b/src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/AuthorizationOptionsSetup.cs @@ -13,7 +13,7 @@ internal sealed class AuthorizationOptionsSetup : IConfigureOptions options) { - _options = options.Value ?? throw new ArgumentNullException(nameof(options)); + _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); } public void Configure(AuthorizationOptions options)