-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(GraphQL): Add DialogToken requirement for subscriptions (#1124)
<!--- Provide a general summary of your changes in the Title above --> ## Description DialogEvents subscription now requires a valid DialogToken <!--- Describe your changes in detail --> ## Related Issue(s) - #1104 ## Verification - [x] **Your** code builds clean without any errors or warnings - [ ] Manual testing done (required) - [ ] Relevant automated test added (if you find this hard, leave it and we'll help out) ## Documentation - [ ] Documentation is updated (either in `docs`-directory, Altinnpedia or a separate linked PR in [altinn-studio-docs.](https://github.com/Altinn/altinn-studio-docs), if applicable) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Enhanced security for `dialogEvents` subscription with new authorization requirements. - Introduced `DialogTokenMiddleware` for handling JWT in requests. - Added methods to extract dialog ID from subscription operations. - New constant for dialog token issuer version introduced. - **Bug Fixes** - Improved authorization policies with added null checks and validations. - **Documentation** - Updated configuration settings for local development to enable authentication and adjust JWT generation settings. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Magnus Sandgren <5285192+MagnusSandgren@users.noreply.github.com> Co-authored-by: Knut Haug <knut.espen.haug@digdir.no>
- Loading branch information
1 parent
d5729de
commit 651ca62
Showing
11 changed files
with
164 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
src/Digdir.Domain.Dialogporten.Application/Features/V1/Common/Authorization/Constants.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace Digdir.Domain.Dialogporten.Application.Features.V1.Common.Authorization; | ||
|
||
public static class Constants | ||
{ | ||
public const string DialogTokenIssuerVersion = "/api/v1"; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
src/Digdir.Domain.Dialogporten.GraphQL/Common/Authorization/DialogTokenMiddleware.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
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; | ||
using static Digdir.Domain.Dialogporten.Application.Features.V1.Common.Authorization.Constants; | ||
|
||
namespace Digdir.Domain.Dialogporten.GraphQL.Common.Authorization; | ||
|
||
public sealed class DialogTokenMiddleware | ||
{ | ||
public const string DialogTokenHeader = "DigDir-Dialog-Token"; | ||
private readonly RequestDelegate _next; | ||
private readonly PublicKey _publicKey; | ||
private readonly string _issuer; | ||
|
||
public DialogTokenMiddleware(RequestDelegate next, IOptions<ApplicationSettings> applicationSettings) | ||
{ | ||
_next = next; | ||
|
||
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; | ||
} | ||
|
||
public Task InvokeAsync(HttpContext context) | ||
{ | ||
if (!context.Request.Headers.TryGetValue(DialogTokenHeader, out var dialogToken)) | ||
{ | ||
return _next(context); | ||
} | ||
|
||
var token = dialogToken.FirstOrDefault(); | ||
var tokenHandler = new JwtSecurityTokenHandler(); | ||
try | ||
{ | ||
tokenHandler.ValidateToken(token, new TokenValidationParameters | ||
{ | ||
ValidateAudience = false, | ||
ValidIssuer = _issuer, | ||
SignatureValidator = ValidateSignature | ||
}, out var securityToken); | ||
|
||
if (securityToken is not JwtSecurityToken jwt) | ||
{ | ||
return _next(context); | ||
} | ||
|
||
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); | ||
} | ||
catch (Exception) | ||
{ | ||
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."); | ||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
...logporten.GraphQL/Common/Extensions/HotChocolate/AuthorizationHandlerContextExtensions.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
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 AuthorizationHandlerContextExtensions | ||
{ | ||
private const string DialogIdArgumentName = "dialogId"; | ||
private const string DialogEventsFieldName = nameof(Subscriptions.DialogEvents); | ||
|
||
/// <summary> | ||
/// Attempts to extract the dialog ID from a DialogEvents subscription operation. | ||
/// </summary> | ||
/// <param name="context">The authorization handler context</param> | ||
/// <param name="dialogId">When this method returns, contains the extracted dialog ID if found; otherwise, Guid.Empty.</param> | ||
/// <returns>True if the dialog ID was successfully extracted; otherwise, false.</returns> | ||
public static bool TryGetDialogEventsSubscriptionDialogId(this AuthorizationHandlerContext context, out Guid dialogId) | ||
{ | ||
dialogId = Guid.Empty; | ||
|
||
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)); | ||
|
||
if (dialogEventsSelection is not FieldNode fieldNode) return false; | ||
|
||
var dialogIdArgument = fieldNode.Arguments.FirstOrDefault(x => x.Name.Value.Equals(DialogIdArgumentName, StringComparison.OrdinalIgnoreCase)); | ||
|
||
if (dialogIdArgument?.Value.Value is null) return false; | ||
|
||
return Guid.TryParse(dialogIdArgument.Value.Value.ToString(), out dialogId); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters