diff --git a/src/Altinn.App.Core/Features/Signing/Mocks/AltinnCorrespondance.cs b/src/Altinn.App.Core/Features/Signing/Mocks/AltinnCorrespondance.cs index 068a768d5..f26ff92b8 100644 --- a/src/Altinn.App.Core/Features/Signing/Mocks/AltinnCorrespondance.cs +++ b/src/Altinn.App.Core/Features/Signing/Mocks/AltinnCorrespondance.cs @@ -6,7 +6,9 @@ namespace Altinn.App.Core.Features.Signing.Mocks; #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public class CorrespondanceClientMock { - public async Task Initialize(InitializeCorrespondenceRequestMock requestMock) + public static async Task Initialize( + InitializeCorrespondenceRequestMock requestMock + ) { var responseMock = new InitializeCorrespondencesResponseMock { diff --git a/src/Altinn.App.Core/Features/Signing/SigningDelegationService.cs b/src/Altinn.App.Core/Features/Signing/SigningDelegationService.cs index 07824bef3..0a74956b2 100644 --- a/src/Altinn.App.Core/Features/Signing/SigningDelegationService.cs +++ b/src/Altinn.App.Core/Features/Signing/SigningDelegationService.cs @@ -2,6 +2,7 @@ using Altinn.App.Core.Features.Signing.Models; using Altinn.App.Core.Helpers; using Altinn.App.Core.Internal.AccessManagement; +using Altinn.App.Core.Internal.AccessManagement.Builders; using Altinn.App.Core.Internal.AccessManagement.Models; using Altinn.App.Core.Internal.AccessManagement.Models.Shared; using Altinn.Platform.Storage.Interface.Models; @@ -26,25 +27,40 @@ CancellationToken ct { if (state.IsAccessDelegated is false) { - // csharpier-ignore-start - string appResourceId = AppIdHelper.ToResourceId(instance.AppId); - DelegationRequest delegation = DelegationRequestBuilder - .CreateBuilder(appResourceId, instance.Id) - .WithDelegator(new Delegator { IdType = DelegationConst.Party, Id = "" }) // TODO: assign delegator - .WithRecipient(new Delegatee { IdType = DelegationConst.Party, Id = signeeContext.PartyId.ToString() }) - .AddRight() - .WithAction(DelegationConst.ActionId, ActionType.Read) - .AddResource(DelegationConst.Resource, appResourceId) // TODO: translate app id to altinn resource id - .AddResource(DelegationConst.Task, taskId) - .BuildRight() - .AddRight() - .WithAction(DelegationConst.ActionId, ActionType.Sign) - .AddResource(DelegationConst.Resource, appResourceId) // TODO: translate app id to altinn resource id - .AddResource(DelegationConst.Task, taskId) - .BuildRight() + DelegationRequest delegationRequest = DelegationBuilder + .Create() + .WithApplicationId(instance.AppId) + .WithInstanceId(instance.Id) + .WithDelegator(new Delegator { IdType = DelegationConst.Party, Id = "" }) + .WithRecipient( + new Delegatee { IdType = DelegationConst.Party, Id = signeeContext.PartyId.ToString() } + ) + .WithRights( + [ + AccessRightBuilder + .Create() + .WithAction(ActionType.Read) + .WithResources( + [ + new Resource { Value = AppIdHelper.ToResourceId(instance.AppId) }, + new Resource { Type = DelegationConst.Task, Value = taskId }, + ] + ) + .Build(), + AccessRightBuilder + .Create() + .WithAction(ActionType.Sign) + .WithResources( + [ + new Resource { Value = AppIdHelper.ToResourceId(instance.AppId) }, + new Resource { Type = DelegationConst.Task, Value = taskId }, + ] + ) + .Build(), + ] + ) .Build(); - // csharpier-ignore-end - var response = await accessManagementClient.DelegateRights(delegation, ct); + var response = await accessManagementClient.DelegateRights(delegationRequest, ct); state.IsAccessDelegated = await Task.FromResult(true); } } diff --git a/src/Altinn.App.Core/Helpers/AppIdHelper.cs b/src/Altinn.App.Core/Helpers/AppIdHelper.cs index b0e682f88..d232370ba 100644 --- a/src/Altinn.App.Core/Helpers/AppIdHelper.cs +++ b/src/Altinn.App.Core/Helpers/AppIdHelper.cs @@ -1,3 +1,5 @@ +using System.Diagnostics.CodeAnalysis; + namespace Altinn.App.Core.Helpers; internal sealed class AppIdHelper @@ -6,4 +8,36 @@ internal static string ToResourceId(string appId) { return ""; //TODO } + + internal static bool IsResourceId(string appId) + { + return false; //TODO + } + + internal static bool TryGetResourceId(string appId, [NotNullWhen(true)] out string? resourceId) + { + if (string.IsNullOrEmpty(appId)) + { + resourceId = null; + return false; + } + + if (IsResourceId(appId)) + { + resourceId = appId; + return true; + } + + resourceId = ToResourceId(appId); + + if (IsResourceId(resourceId)) + { + return true; + } + else + { + resourceId = null; + return false; + } + } } diff --git a/src/Altinn.App.Core/Internal/AccessManagement/AccessManagementClient.cs b/src/Altinn.App.Core/Internal/AccessManagement/AccessManagementClient.cs index 7ec52bf49..6c7022b64 100644 --- a/src/Altinn.App.Core/Internal/AccessManagement/AccessManagementClient.cs +++ b/src/Altinn.App.Core/Internal/AccessManagement/AccessManagementClient.cs @@ -8,6 +8,7 @@ using Altinn.App.Core.Internal.App; using Altinn.Common.AccessTokenClient.Services; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Altinn.App.Core.Internal.AccessManagement; @@ -21,7 +22,7 @@ internal sealed class AccessManagementClient( HttpClient httpClient, IAppMetadata appMetadata, IAccessTokenGenerator accessTokenGenerator, - PlatformSettings platformSettings, + IOptions platformSettings, Telemetry? telemetry = null ) : IAccessManagementClient { @@ -36,7 +37,7 @@ public async Task DelegateRights(DelegationRequest delegatio HttpResponseMessage? httpResponseMessage = null; string? httpContent = null; - UrlHelper urlHelper = new(platformSettings); + UrlHelper urlHelper = new(platformSettings.Value); try { var application = await appMetadata.GetApplicationMetadata(); @@ -71,9 +72,10 @@ public async Task DelegateRights(DelegationRequest delegatio } catch (Exception e) { - var ex = new DelegationException( + var ex = new AccessManagementRequestException( $"Something went wrong when processing the access management request.", - httpResponseMessage, + null, + httpResponseMessage?.StatusCode, httpContent, e ); diff --git a/src/Altinn.App.Core/Internal/AccessManagement/Builders/AccessRightBuilder.cs b/src/Altinn.App.Core/Internal/AccessManagement/Builders/AccessRightBuilder.cs new file mode 100644 index 000000000..ee70abc73 --- /dev/null +++ b/src/Altinn.App.Core/Internal/AccessManagement/Builders/AccessRightBuilder.cs @@ -0,0 +1,54 @@ +using Altinn.App.Core.Internal.AccessManagement.Models; +using Altinn.App.Core.Internal.AccessManagement.Models.Shared; + +namespace Altinn.App.Core.Internal.AccessManagement.Builders; + +internal interface IAccessRightBuilderStart +{ + IAccessRightBuilderAction WithAction(string value); +} + +internal interface IAccessRightBuilderAction +{ + IAccessRightBuilder WithResource(string value); + IAccessRightBuilder WithResources(List resources); +} + +internal interface IAccessRightBuilder : IAccessRightBuilderStart, IAccessRightBuilderAction +{ + RightRequest Build(); +} + +internal sealed class AccessRightBuilder : IAccessRightBuilder +{ + private AltinnAction? _action; + private List? _resources; + + private AccessRightBuilder() { } + + public static IAccessRightBuilderStart Create() => new AccessRightBuilder(); + + public IAccessRightBuilderAction WithAction(string value) + { + _action = new AltinnAction { Type = DelegationConst.ActionId, Value = value }; + return this; + } + + public IAccessRightBuilder WithResource(string value) + { + _resources = [new Resource { Type = DelegationConst.Resource, Value = value }]; + + return this; + } + + public IAccessRightBuilder WithResources(List resources) + { + _resources = [.. _resources ?? [], .. resources]; + return this; + } + + public RightRequest Build() + { + return new RightRequest { Action = _action, Resource = _resources ?? [] }; + } +} diff --git a/src/Altinn.App.Core/Internal/AccessManagement/Builders/DelegationBuilder.cs b/src/Altinn.App.Core/Internal/AccessManagement/Builders/DelegationBuilder.cs new file mode 100644 index 000000000..d8f0e73da --- /dev/null +++ b/src/Altinn.App.Core/Internal/AccessManagement/Builders/DelegationBuilder.cs @@ -0,0 +1,137 @@ +using System.Diagnostics.CodeAnalysis; +using Altinn.App.Core.Helpers; +using Altinn.App.Core.Internal.AccessManagement.Exceptions; +using Altinn.App.Core.Internal.AccessManagement.Models; +using Altinn.App.Core.Internal.AccessManagement.Models.Shared; + +namespace Altinn.App.Core.Internal.AccessManagement.Builders; + +internal abstract class DelegationBuilderBase +{ + internal static void NotNullOrEmpty([NotNull] object? value, string? errorMessage = null) + { + if ( + value is null + || value is string str && string.IsNullOrWhiteSpace(str) + || value is ReadOnlyMemory { IsEmpty: true } + ) + { + throw new AccessManagementArgumentException(errorMessage); // TODO: add custom exception + } + } +} + +internal interface IDelegationBuilderStart +{ + IDelegationBuilderApplicationId WithApplicationId(string applicationId); +} + +internal interface IDelegationBuilderApplicationId +{ + IDelegationBuilderInstanceId WithInstanceId(string instanceId); +} + +internal interface IDelegationBuilderInstanceId +{ + IDelegationBuilderDelegator WithDelegator(Delegator delegator); +} + +internal interface IDelegationBuilderDelegator +{ + IDelegationBuilderRecipient WithRecipient(Delegatee recipient); +} + +internal interface IDelegationBuilderRecipient +{ + IDelegationBuilder WithRight(RightRequest rightRequest); + IDelegationBuilder WithRights(List rightRequests); +} + +internal interface IDelegationBuilder + : IDelegationBuilderStart, + IDelegationBuilderApplicationId, + IDelegationBuilderInstanceId, + IDelegationBuilderDelegator, + IDelegationBuilderRecipient +{ + DelegationRequest Build(); +} + +internal sealed class DelegationBuilder : DelegationBuilderBase, IDelegationBuilder +{ + private string? _applicationId; + private string? _instanceId; + private Delegator? _delegator; + private Delegatee? _recipient; + private List? _rights; + + private DelegationBuilder() { } + + public static IDelegationBuilderStart Create() => new DelegationBuilder(); + + public IDelegationBuilderApplicationId WithApplicationId(string applicationId) + { + NotNullOrEmpty(applicationId, nameof(applicationId)); + _applicationId = AppIdHelper.TryGetResourceId(applicationId, out string? resourceId) + ? resourceId + : throw new ArgumentException("Invalid application ID", nameof(applicationId)); + return this; + } + + public IDelegationBuilderInstanceId WithInstanceId(string instanceId) + { + NotNullOrEmpty(instanceId, nameof(instanceId)); + _instanceId = instanceId; + return this; + } + + public IDelegationBuilderDelegator WithDelegator(Delegator delegator) + { + NotNullOrEmpty(delegator, nameof(delegator)); + _delegator = delegator; + return this; + } + + public IDelegationBuilderRecipient WithRecipient(Delegatee recipient) + { + NotNullOrEmpty(recipient, nameof(recipient)); + _recipient = recipient; + return this; + } + + public IDelegationBuilder WithRight(RightRequest rightRequest) + { + _rights = [rightRequest]; + return this; + } + + public IDelegationBuilder WithRights(List rightRequests) + { + _rights = [.. _rights ?? [], .. rightRequests]; + return this; + } + + public IDelegationBuilder WithRight(AccessRightBuilder rightBuilder) + { + _rights = [rightBuilder.Build()]; + return this; + } + + public DelegationRequest Build() + { + NotNullOrEmpty(_applicationId, nameof(_applicationId)); + NotNullOrEmpty(_instanceId, nameof(_instanceId)); + NotNullOrEmpty(_delegator, nameof(_delegator)); + NotNullOrEmpty(_recipient, nameof(_recipient)); + NotNullOrEmpty(_rights, nameof(_rights)); + + return new DelegationRequest + { + ResourceId = _applicationId, + InstanceId = _instanceId, + From = _delegator, + To = _recipient, + Rights = _rights, + }; + } +} diff --git a/src/Altinn.App.Core/Internal/AccessManagement/DelegationRequestBuilder.cs b/src/Altinn.App.Core/Internal/AccessManagement/DelegationRequestBuilder.cs deleted file mode 100644 index 0e339e309..000000000 --- a/src/Altinn.App.Core/Internal/AccessManagement/DelegationRequestBuilder.cs +++ /dev/null @@ -1,95 +0,0 @@ -using Altinn.App.Core.Internal.AccessManagement.Models; -using Altinn.App.Core.Internal.AccessManagement.Models.Shared; - -namespace Altinn.App.Core.Internal.AccessManagement; - -internal interface IDelegatorStep -{ - IRecipientStep WithDelegator(Delegator delegator); -} - -internal interface IRecipientStep -{ - IRightStep WithRecipient(Delegatee recipient); -} - -internal interface IRightStep : IDelegationCreateStep -{ - IRightBuilder AddRight(); -} - -internal interface IDelegationCreateStep -{ - DelegationRequest Build(); -} - -internal interface IRightBuilder -{ - IRightBuilder WithAction(string type, string value); - IRightBuilder AddResource(string type, string value); - IRightStep BuildRight(); -} - -internal sealed class DelegationRequestBuilder : IDelegatorStep, IRecipientStep, IRightStep, IDelegationCreateStep -{ - private DelegationRequest _delegation; - - public DelegationRequestBuilder(string applicationId, string instanceId) - { - _delegation = new DelegationRequest() { ResourceId = applicationId, InstanceId = instanceId }; - } - - public static IDelegatorStep CreateBuilder(string applicationId, string instanceId) => - new DelegationRequestBuilder(applicationId, instanceId); - - public IRecipientStep WithDelegator(Delegator delegator) - { - _delegation.From = delegator; - return this; - } - - public IRightStep WithRecipient(Delegatee recipient) - { - _delegation.To = recipient; - return this; - } - - public IRightBuilder AddRight() - { - return new RightBuilder(this); - } - - public DelegationRequest Build() - { - return _delegation; - } - - internal sealed class RightBuilder : IRightBuilder - { - private readonly IRightStep _parentBuilder; - private readonly RightRequest _right = new RightRequest { Resource = new List() }; - - public RightBuilder(IRightStep parentBuilder) - { - _parentBuilder = parentBuilder; - } - - public IRightBuilder WithAction(string type, string value) - { - _right.Action = new AltinnAction { Type = type, Value = value }; - return this; - } - - public IRightBuilder AddResource(string type, string value) - { - _right.Resource.Add(new Resource { Type = type, Value = value }); - return this; - } - - public IRightStep BuildRight() - { - ((DelegationRequestBuilder)_parentBuilder)._delegation.Rights.Add(_right); - return _parentBuilder; - } - } -} diff --git a/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementArgumentException.cs b/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementArgumentException.cs new file mode 100644 index 000000000..d88bc3cc8 --- /dev/null +++ b/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementArgumentException.cs @@ -0,0 +1,12 @@ +namespace Altinn.App.Core.Internal.AccessManagement.Exceptions; + +internal sealed class AccessManagementArgumentException : AccessManagementException +{ + public AccessManagementArgumentException() { } + + public AccessManagementArgumentException(string? message) + : base(message) { } + + public AccessManagementArgumentException(string? message, Exception? innerException) + : base(message, innerException) { } +} diff --git a/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementException.cs b/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementException.cs new file mode 100644 index 000000000..0b07ca5fb --- /dev/null +++ b/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementException.cs @@ -0,0 +1,18 @@ +namespace Altinn.App.Core.Internal.AccessManagement.Exceptions; + +/// +/// Generic Access Management related exception. +/// +internal abstract class AccessManagementException : Exception +{ + /// + protected AccessManagementException() { } + + /// + protected AccessManagementException(string? message) + : base(message) { } + + /// + protected AccessManagementException(string? message, Exception? innerException) + : base(message, innerException) { } +} diff --git a/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementRequestException.cs b/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementRequestException.cs new file mode 100644 index 000000000..6106d923e --- /dev/null +++ b/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/AccessManagementRequestException.cs @@ -0,0 +1,46 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; + +namespace Altinn.App.Core.Internal.AccessManagement.Exceptions; + +internal sealed class AccessManagementRequestException : AccessManagementException +{ + internal ProblemDetails? ProblemDetails { get; init; } + internal HttpStatusCode? StatusCode { get; init; } + internal string? ResponseBody { get; init; } + + public AccessManagementRequestException() { } + + public AccessManagementRequestException(string? message) + : base(message) { } + + public AccessManagementRequestException(string? message, Exception? innerException) + : base(message, innerException) { } + + public AccessManagementRequestException( + string? message, + ProblemDetails? problemDetails, + HttpStatusCode? statusCode, + string? responseBody + ) + : base(message) + { + ProblemDetails = problemDetails; + StatusCode = statusCode; + ResponseBody = responseBody; + } + + public AccessManagementRequestException( + string? message, + ProblemDetails? problemDetails, + HttpStatusCode? statusCode, + string? responseBody, + Exception? innerException + ) + : base(message, innerException) + { + ProblemDetails = problemDetails; + StatusCode = statusCode; + ResponseBody = responseBody; + } +} diff --git a/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/DelegationException.cs b/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/DelegationException.cs deleted file mode 100644 index 8b5b11ca9..000000000 --- a/src/Altinn.App.Core/Internal/AccessManagement/Exceptions/DelegationException.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace Altinn.App.Core.Internal.AccessManagement.Exceptions; - -internal sealed class DelegationException : Exception -{ - internal DelegationException( - string? message, - HttpResponseMessage? response, - string? content, - Exception? innerException - ) - : base( - $"{message}: StatusCode={response?.StatusCode}\nReason={response?.ReasonPhrase}\nBody={content}\n", - innerException - ) { } -} diff --git a/src/Altinn.App.Core/Internal/AccessManagement/Models/DelegationRequest.cs b/src/Altinn.App.Core/Internal/AccessManagement/Models/DelegationRequest.cs index effc914d2..83cea9894 100644 --- a/src/Altinn.App.Core/Internal/AccessManagement/Models/DelegationRequest.cs +++ b/src/Altinn.App.Core/Internal/AccessManagement/Models/DelegationRequest.cs @@ -28,4 +28,7 @@ internal sealed class RightRequest [JsonPropertyName("action")] internal AltinnAction? Action { get; set; } + + [JsonPropertyName("taskId")] + internal string? TaskId { get; set; } } diff --git a/src/Altinn.App.Core/Internal/AccessManagement/Models/Shared/Resource.cs b/src/Altinn.App.Core/Internal/AccessManagement/Models/Shared/Resource.cs index c6959a839..f25902638 100644 --- a/src/Altinn.App.Core/Internal/AccessManagement/Models/Shared/Resource.cs +++ b/src/Altinn.App.Core/Internal/AccessManagement/Models/Shared/Resource.cs @@ -5,7 +5,7 @@ namespace Altinn.App.Core.Internal.AccessManagement.Models.Shared; internal sealed class Resource { [JsonPropertyName("type")] - internal required string Type { get; set; } + internal string Type { get; set; } = DelegationConst.Resource; [JsonPropertyName("value")] internal required string Value { get; set; }