From bc1996412f5008e731a7bf627220af90b3671682 Mon Sep 17 00:00:00 2001 From: Ronny Birkeli Date: Tue, 8 Nov 2022 11:57:00 +0100 Subject: [PATCH] Add call to next step in process before completing --- .../EformidlingStatusCheckEventHandler.cs | 49 ++++++++++++++----- src/Altinn.App.Core/Models/AppIdentifier.cs | 34 +++++++++++++ .../Models/InstanceIdentifier.cs | 12 ++++- ...EformidlingStatusCheckEventHandlerTests.cs | 9 ++-- .../Models/AppIdentifierTests.cs | 20 ++++++++ 5 files changed, 107 insertions(+), 17 deletions(-) diff --git a/src/Altinn.App.Core/EFormidling/Implementation/EformidlingStatusCheckEventHandler.cs b/src/Altinn.App.Core/EFormidling/Implementation/EformidlingStatusCheckEventHandler.cs index a155dca91..2d1cb426b 100644 --- a/src/Altinn.App.Core/EFormidling/Implementation/EformidlingStatusCheckEventHandler.cs +++ b/src/Altinn.App.Core/EFormidling/Implementation/EformidlingStatusCheckEventHandler.cs @@ -28,32 +28,35 @@ public class EformidlingStatusCheckEventHandler : IEventHandler { private readonly IEFormidlingClient _eFormidlingClient; private readonly ILogger _logger; - private readonly HttpClient _httpClient; + private readonly IHttpClientFactory _httpClientFactory; private readonly IMaskinportenService _maskinportenService; private readonly MaskinportenSettings _maskinportenSettings; private readonly IX509CertificateProvider _x509CertificateProvider; private readonly PlatformSettings _platformSettings; + private readonly GeneralSettings _generalSettings; /// /// Initializes a new instance of the class. /// public EformidlingStatusCheckEventHandler( IEFormidlingClient eFormidlingClient, - HttpClient httpClient, + IHttpClientFactory httpClientFactory, ILogger logger, IMaskinportenService maskinportenService, IOptions maskinportenSettings, IX509CertificateProvider x509CertificateProvider, - IOptions platformSettings + IOptions platformSettings, + IOptions generalSettings ) { _eFormidlingClient = eFormidlingClient; _logger = logger; - _httpClient = httpClient; + _httpClientFactory = httpClientFactory; _maskinportenService = maskinportenService; _maskinportenSettings = maskinportenSettings.Value; _x509CertificateProvider = x509CertificateProvider; _platformSettings = platformSettings.Value; + _generalSettings = generalSettings.Value; } /// @@ -66,6 +69,7 @@ public async Task ProcessEvent(CloudEvent cloudEvent) _logger.LogInformation("Received reminder for subject {subject}", subject); + AppIdentifier appIdentifier = AppIdentifier.CreateFromUrl(cloudEvent.Source.ToString()); InstanceIdentifier instanceIdentifier = InstanceIdentifier.CreateFromUrl(cloudEvent.Source.ToString()); // Instance GUID is used as shipment identifier @@ -78,7 +82,8 @@ public async Task ProcessEvent(CloudEvent cloudEvent) // Moving forward sending to Eformidling should considered as a ServiceTask with auto advance in the process // when the message is confirmed. - _ = await AddCompleteConfirmation(instanceIdentifier.InstanceOwnerPartyId, instanceIdentifier.InstanceGuid); + await ProcessMoveNext(appIdentifier, instanceIdentifier); + _ = await AddCompleteConfirmation(instanceIdentifier); return true; } @@ -102,23 +107,43 @@ public async Task ProcessEvent(CloudEvent cloudEvent) // and be handled by the Platform team manually. } + private async Task ProcessMoveNext(AppIdentifier appIdentifier, InstanceIdentifier instanceIdentifier) + { + string url = $"https://{appIdentifier.Org}.apps.{_generalSettings.HostName}/{appIdentifier}/instances/{instanceIdentifier}/process"; + + HttpClient httpClient = _httpClientFactory.CreateClient(); + + HttpResponseMessage response = await httpClient.PutAsync(url, new StringContent(string.Empty)); + + if (response.IsSuccessStatusCode) + { + _logger.LogInformation("Moved instance {instanceId} to next step.", instanceIdentifier); + } + else + { + _logger.LogError("Failed moving instance {instanceId} to next step.", instanceIdentifier); + } + } + /// This is basically a duplicate of the method in /// Duplication is done since the original method requires an http context /// with a logged on user/org, while we would like to authenticate against maskinporten /// here and now and avoid calling out of the app and back into the app on the matching - /// endpoint in InstanceController. This method should be remove once we have a better + /// endpoint in InstanceController. This method should be removed once we have a better /// alernative for authenticating the app/org without having a http request context with /// a logged on user/org. - private async Task AddCompleteConfirmation(int instanceOwnerPartyId, Guid instanceGuid) + private async Task AddCompleteConfirmation(InstanceIdentifier instanceIdentifier) { - string apiUrl = $"instances/{instanceOwnerPartyId}/{instanceGuid}/complete"; + string url = $"instances/{instanceIdentifier.InstanceOwnerPartyId}/{instanceIdentifier.InstanceGuid}/complete"; TokenResponse altinnToken = await GetOrganizationToken(); - _httpClient.BaseAddress = new Uri(_platformSettings.ApiStorageEndpoint); - _httpClient.DefaultRequestHeaders.Add(General.SubscriptionKeyHeaderName, _platformSettings.SubscriptionKey); - _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); - HttpResponseMessage response = await _httpClient.PostAsync(altinnToken.AccessToken, apiUrl, new StringContent(string.Empty)); + HttpClient httpClient = _httpClientFactory.CreateClient(); + httpClient.BaseAddress = new Uri(_platformSettings.ApiStorageEndpoint); + httpClient.DefaultRequestHeaders.Add(General.SubscriptionKeyHeaderName, _platformSettings.SubscriptionKey); + httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + + HttpResponseMessage response = await httpClient.PostAsync(altinnToken.AccessToken, url, new StringContent(string.Empty)); if (response.StatusCode == HttpStatusCode.OK) { diff --git a/src/Altinn.App.Core/Models/AppIdentifier.cs b/src/Altinn.App.Core/Models/AppIdentifier.cs index 8685eb20e..c8e65cda6 100644 --- a/src/Altinn.App.Core/Models/AppIdentifier.cs +++ b/src/Altinn.App.Core/Models/AppIdentifier.cs @@ -97,5 +97,39 @@ public override int GetHashCode() { return Org.GetHashCode() ^ App.GetHashCode(); } + + /// + /// A absolute url expected to point to an Altinn 3 app ie. the first two segments of the path + /// has to be the organization and the app name for example: https://org.apps.altinn.no/{org}/{app}/api/... + /// + /// The url to parse + /// A new instance of + public static AppIdentifier CreateFromUrl(string url) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + + if (!Uri.IsWellFormedUriString(url, UriKind.Absolute)) + { + throw new ArgumentException($"The provided url ({url}) is not well formed. Please check that it is an absolute url with at least two path segments."); + } + + Uri uri = new(url, UriKind.Absolute); + + // Remove the first slash as this will only result in an empty first segment when splitting. + string[] segments = uri.PathAndQuery[1..].Split("/"); + + if (segments.Length < 2) + { + throw new ArgumentException($"The provided url ({url} must contain at least two path segments.)"); + } + + var org = segments[0]; + var app = segments[1]; + + return new AppIdentifier(org, app); + } } } diff --git a/src/Altinn.App.Core/Models/InstanceIdentifier.cs b/src/Altinn.App.Core/Models/InstanceIdentifier.cs index d09022c1f..309287c90 100644 --- a/src/Altinn.App.Core/Models/InstanceIdentifier.cs +++ b/src/Altinn.App.Core/Models/InstanceIdentifier.cs @@ -71,9 +71,9 @@ public static InstanceIdentifier CreateFromUrl(string url) /// /// Gets the instance id to be used when looking up this instance in storage api. - /// The url needs to conform to .../instances/{instanceOwerId}/{instanceOwnerGuid}/... pattern. + /// The url needs to conform to .../instances/{instanceOwnerId}/{instanceGuid}/... pattern. /// - /// Instance id combinging instance owner and instance guid. + /// Instance id combining instance owner and instance guid. public string GetInstanceId() { if (IsNoInstance) @@ -84,6 +84,14 @@ public string GetInstanceId() return $"{InstanceOwnerPartyId}/{InstanceGuid}"; } + /// + /// A string on the format {instanceOwnerId}/{instanceGuid} without leading or trailing slashes. + /// + public override string ToString() + { + return GetInstanceId(); + } + /// /// Deconstructs an instance id into it's two logical parts - instanceOwnerPartyId and instanceGuid. /// Party represents either the person or the organization that owns the instance. diff --git a/test/Altinn.App.Api.Tests/EFormidling/EformidlingStatusCheckEventHandlerTests.cs b/test/Altinn.App.Api.Tests/EFormidling/EformidlingStatusCheckEventHandlerTests.cs index 6a8eaf5bd..31df4a36b 100644 --- a/test/Altinn.App.Api.Tests/EFormidling/EformidlingStatusCheckEventHandlerTests.cs +++ b/test/Altinn.App.Api.Tests/EFormidling/EformidlingStatusCheckEventHandlerTests.cs @@ -47,7 +47,7 @@ private static CloudEvent GetValidCloudEvent() private static EformidlingStatusCheckEventHandler GetMockedEventHandler() { var eFormidlingClientMock = new Mock(); - var httpClient = new Mock(); + var httpClientFactoryMock = new Mock(); var eFormidlingLoggerMock = new Mock>(); var httpClientMock = new Mock(); @@ -58,15 +58,18 @@ private static EformidlingStatusCheckEventHandler GetMockedEventHandler() var maskinportenSettingsMock = new Mock>(); var x509CertificateProviderMock = new Mock(); var platformSettingsMock = new Mock>(); + var generalSettingsMock = new Mock>(); EformidlingStatusCheckEventHandler eventHandler = new( eFormidlingClientMock.Object, - httpClient.Object, + httpClientFactoryMock.Object, eFormidlingLoggerMock.Object, maskinportenServiceMock.Object, maskinportenSettingsMock.Object, x509CertificateProviderMock.Object, - platformSettingsMock.Object); + platformSettingsMock.Object, + generalSettingsMock.Object + ); return eventHandler; } } diff --git a/test/Altinn.App.Core.Tests/Models/AppIdentifierTests.cs b/test/Altinn.App.Core.Tests/Models/AppIdentifierTests.cs index 0145623b4..534f5d744 100644 --- a/test/Altinn.App.Core.Tests/Models/AppIdentifierTests.cs +++ b/test/Altinn.App.Core.Tests/Models/AppIdentifierTests.cs @@ -78,6 +78,26 @@ public void Constructor_Equals_ShouldBeEqual() appIdentifier1.Should().BeEquivalentTo(appIdentifier2); appIdentifier1.GetHashCode().Should().Be(appIdentifier2.GetHashCode()); } + + [Theory] + [InlineData("https://dihe.apps.tt02.altinn.no/dihe/redusert-foreldrebetaling-bhg/api/v1/eventsreceiver?code=16eda4f0-653a-4fdc-b516-c4702392a4eb", "dihe", "redusert-foreldrebetaling-bhg")] + public void CreateFromUrl_ValidUrl_ShouldConstruct(string url, string expectedOrg, string expectedApp) + { + AppIdentifier appIdentifier = AppIdentifier.CreateFromUrl(url); + + appIdentifier.Org.Should().Be(expectedOrg); + appIdentifier.App.Should().Be(expectedApp); + } + + [Theory] + [InlineData("https://dihe.apps.tt02.altinn.no/dihe")] + [InlineData("dihe/redusert-foreldrebetaling-bhg")] + public void CreateFromUrl_InvalidUrl_ShouldThrowArgumentException(string url) + { + Action action = () => AppIdentifier.CreateFromUrl(url); + + action.Should().Throw(); + } } #pragma warning restore CA1806 // Do not ignore method results