Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#3 Adding the option of searching for instances based on person or org number, or other in storage #360

Merged
merged 31 commits into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from 28 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
edbfcf2
RegisterService client added
khanrn Mar 25, 2024
88e9138
Party ID look up with register service client
khanrn Mar 28, 2024
b98d9ed
Fixed issues and typos
khanrn Apr 4, 2024
695a521
IAccessTokenGenerator added as singleton
khanrn Apr 4, 2024
54a45fe
RegisterService test added
khanrn Apr 4, 2024
fe36cb6
Username removed from haystack
khanrn Apr 4, 2024
e800c38
Tests added for GetIdentifierFromInstanceOwnerIdentifier
khanrn Apr 4, 2024
ab22b0a
GetIdentifierFromInstanceOwnerIdentifier refactored
khanrn Apr 4, 2024
e0ccd48
ApiRegisterEndpoint value added for test
khanrn Apr 8, 2024
61bb327
JwtCookieName value added to appsettings
khanrn Apr 8, 2024
9de8c87
Fixed the namespace for platform settings
khanrn Apr 8, 2024
44db598
Changed expected text in the test
khanrn Apr 8, 2024
e077c92
SonarCloud issue fix
khanrn Apr 8, 2024
0f25bc7
Test added for GetParty
khanrn Apr 8, 2024
6dae234
Test fixed with proper arguments
khanrn Apr 8, 2024
e742957
Test update
khanrn Apr 9, 2024
209a887
Contains firxed the test issue
khanrn Apr 9, 2024
2ec6905
Test for log error passed
khanrn Apr 9, 2024
fd47008
Party ID update on the query parameters
khanrn Apr 9, 2024
1f3f435
Party ID update logic specified to only instance owner ID
khanrn Apr 10, 2024
8dad9bf
Tests added for HttpClientExtension
khanrn Apr 10, 2024
fc5d176
Tests for PlatformAccessToken in HttpClientExtension added
khanrn Apr 10, 2024
938aa16
Test added for no party ID but wrong instance owner ID
khanrn Apr 10, 2024
3e6fd3a
Suggestion implemented for error message
khanrn Apr 11, 2024
ae951b5
Logic enhanced for updating instance owner party ID
khanrn Apr 11, 2024
fb60579
Delegating handler stub added for testing http client
khanrn Apr 11, 2024
86e6844
Await keyword added to async function in the test
khanrn Apr 11, 2024
59ca0da
PascalCase fixed for the logged error message
khanrn Apr 11, 2024
15cb1f4
Update test/UnitTest/Extensions/HttpClientExtensionTests.cs
khanrn Apr 11, 2024
18660c0
PascalCase fixed for another issue in the RegisterService
khanrn Apr 11, 2024
d902e27
Reponse status code added insted of full reponse in party lookup
khanrn Apr 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/Storage/Altinn.Platform.Storage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

<ItemGroup>
<PackageReference Include="Altinn.Common.AccessToken" Version="3.0.3" />
<PackageReference Include="Altinn.Common.AccessTokenClient" Version="3.0.1" />
<PackageReference Include="Altinn.Platform.Models" Version="1.5.0" />
<PackageReference Include="Altinn.Platform.Storage.Interface" Version="3.24.0" />
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.1" />
<PackageReference Include="Azure.Identity" Version="1.10.4" />
Expand Down
5 changes: 5 additions & 0 deletions src/Storage/Configuration/GeneralSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,10 @@ public class GeneralSettings
/// Gets or sets the cache lifetime for application metadata document.
/// </summary>
public int AppMetadataCacheLifeTimeInSeconds { get; set; }

/// <summary>
/// Name of the cookie for where JWT is stored
/// </summary>
public string JwtCookieName { get; set; }
}
}
31 changes: 31 additions & 0 deletions src/Storage/Configuration/PlatformSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Runtime.Serialization;

namespace Altinn.Platform.Storage.Configuration
{
/// <summary>
/// Represents a set of configuration options when communicating with the platform API.
/// Instances of this class is initialised with values from app settings. Some values can be overridden by environment variables.
/// </summary>
public class PlatformSettings
{
/// <summary>
/// Gets or sets the url for the Register API endpoint.
/// </summary>
public string ApiRegisterEndpoint { get; set; }

/// <summary>
/// Gets or sets the url for the Profile API endpoint
/// </summary>
public string ApiProfileEndpoint { get; set; }

/// <summary>
/// Gets or sets the apps domain used to match events source
/// </summary>
public string AppsDomain { get; set; }

/// <summary>
/// The lifetime to cache subscriptions
/// </summary>
public int SubscriptionCachingLifetimeInSeconds { get; set; }
}
}
28 changes: 27 additions & 1 deletion src/Storage/Controllers/InstancesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Primitives;
using Microsoft.IdentityModel.Tokens;

using Newtonsoft.Json;

Expand All @@ -46,6 +47,7 @@
private readonly IInstanceEventService _instanceEventService;
private readonly string _storageBaseAndHost;
private readonly GeneralSettings _generalSettings;
private readonly IRegisterService _registerService;

/// <summary>
/// Initializes a new instance of the <see cref="InstancesController"/> class
Expand All @@ -56,14 +58,16 @@
/// <param name="logger">the logger</param>
/// <param name="authorizationService">the authorization service.</param>
/// <param name="instanceEventService">the instance event service.</param>
/// <param name="registerService">the instance register service.</param>
/// <param name="settings">the general settings.</param>
public InstancesController(

Check warning on line 63 in src/Storage/Controllers/InstancesController.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Constructor has 8 parameters, which is greater than the 7 authorized. (https://rules.sonarsource.com/csharp/RSPEC-107)
IInstanceRepository instanceRepository,
IApplicationRepository applicationRepository,
IPartiesWithInstancesClient partiesWithInstancesClient,
ILogger<InstancesController> logger,
IAuthorization authorizationService,
IInstanceEventService instanceEventService,
IRegisterService registerService,
IOptions<GeneralSettings> settings)
{
_instanceRepository = instanceRepository;
Expand All @@ -73,6 +77,7 @@
_storageBaseAndHost = $"{settings.Value.Hostname}/storage/api/v1/";
_authorizationService = authorizationService;
_instanceEventService = instanceEventService;
_registerService = registerService;
_generalSettings = settings.Value;
}

Expand All @@ -86,6 +91,7 @@
/// <param name="processEndEvent">Process end state.</param>
/// <param name="processEnded">Process ended value.</param>
/// <param name="instanceOwnerPartyId">Instance owner id.</param>
/// <param name="instanceOwnerIdentifier">Instance owner identifier, i.e. Person:PersonNumber, Organisation:OrganisationNumber, Username:Username.</param>
/// <param name="lastChanged">Last changed date.</param>
/// <param name="created">Created time.</param>
/// <param name="visibleAfter">The visible after date time.</param>
Expand All @@ -103,7 +109,7 @@
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[Produces("application/json")]
public async Task<ActionResult<QueryResponse<Instance>>> GetInstances(

Check warning on line 112 in src/Storage/Controllers/InstancesController.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Refactor this method to reduce its Cognitive Complexity from 42 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
[FromQuery] string org,
[FromQuery] string appId,
[FromQuery(Name = "process.currentTask")] string currentTaskId,
Expand All @@ -111,6 +117,7 @@
[FromQuery(Name = "process.endEvent")] string processEndEvent,
[FromQuery(Name = "process.ended")] string processEnded,
[FromQuery(Name = "instanceOwner.partyId")] int? instanceOwnerPartyId,
[FromHeader(Name = "X-Ai-InstanceOwnerIdentifier")] string instanceOwnerIdentifier,
[FromQuery] string lastChanged,
[FromQuery] string created,
[FromQuery(Name = "visibleAfter")] string visibleAfter,
Expand Down Expand Up @@ -155,7 +162,22 @@
{
if (instanceOwnerPartyId == null)
{
return BadRequest("InstanceOwnerPartyId must be defined.");
if (string.IsNullOrEmpty(instanceOwnerIdentifier))
{
return BadRequest("Either InstanceOwnerPartyId or InstanceOwnerIdentifier need to be defined.");
}

(string instanceOwnerIdType, string instanceOwnerIdValue) = InstanceHelper.GetIdentifierFromInstanceOwnerIdentifier(instanceOwnerIdentifier);

if (string.IsNullOrEmpty(instanceOwnerIdType) || string.IsNullOrEmpty(instanceOwnerIdValue))
{
return BadRequest("Invalid InstanceOwnerIdentifier.");
}

string orgNo = instanceOwnerIdType == "organization" ? instanceOwnerIdValue : string.Empty;
string person = instanceOwnerIdType == "person" ? instanceOwnerIdValue : string.Empty;

instanceOwnerPartyId = await _registerService.PartyLookup(orgNo, person);
}
}
else
Expand All @@ -170,6 +192,10 @@
}

Dictionary<string, StringValues> queryParams = QueryHelpers.ParseQuery(Request.QueryString.Value);
if (instanceOwnerPartyId > 0)
{
queryParams["instanceOwner.partyId"] = new StringValues(instanceOwnerPartyId.ToString());
}

// filter out hard deleted instances if it isn't appOwner requesting instances
if (!appOwnerRequestingInstances)
Expand Down Expand Up @@ -305,7 +331,7 @@
{
(appInfo, appInfoError) = await GetApplicationOrErrorAsync(appId);
}
catch (Exception ex)

Check warning on line 334 in src/Storage/Controllers/InstancesController.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Either log this exception and handle it, or rethrow it with some contextual information. (https://rules.sonarsource.com/csharp/RSPEC-2139)
{
_logger.LogError(ex, "Something went wrong during GetApplicationOrErrorAsync for application id: {appId}", appId);
throw;
Expand All @@ -327,7 +353,7 @@
{
request = DecisionHelper.CreateDecisionRequest(appInfo.Org, appInfo.Id.Split('/')[1], HttpContext.User, "instantiate", instanceOwnerPartyId, null, Request.Headers);
}
catch (Exception ex)

Check warning on line 356 in src/Storage/Controllers/InstancesController.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Either log this exception and handle it, or rethrow it with some contextual information. (https://rules.sonarsource.com/csharp/RSPEC-2139)
{
_logger.LogError(ex, "Something went wrong during CreateDecisionRequest for application id: {appId} AppInfo: {appInfo}", appId, appInfo.ToString());
throw;
Expand Down
40 changes: 40 additions & 0 deletions src/Storage/Exceptions/PlatformHttpException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System;
using System.Net.Http;
using System.Threading.Tasks;

namespace Altinn.Platform.Storage.Exceptions
{
/// <summary>
/// Exception class to hold exceptions when talking to the platform REST services
/// </summary>
public class PlatformHttpException : Exception
{
/// <summary>
/// Responsible for holding an http request exception towards platform.
/// </summary>
public HttpResponseMessage Response { get; }

/// <summary>
/// Creates a platform exception
/// </summary>
/// <param name="response">The http response</param>
/// <returns>A PlatformHttpException</returns>
public static async Task<PlatformHttpException> CreateAsync(HttpResponseMessage response)
{
string content = await response.Content.ReadAsStringAsync();
string message = $"{(int)response.StatusCode} - {response.ReasonPhrase} - {content}";

return new PlatformHttpException(response, message);
}

/// <summary>
/// Copy the response for further investigations
/// </summary>
/// <param name="response">the response</param>
/// <param name="message">the message</param>
public PlatformHttpException(HttpResponseMessage response, string message) : base(message)
{
this.Response = response;
}
}
}
56 changes: 56 additions & 0 deletions src/Storage/Extensions/HttpClientExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

namespace Altinn.Platform.Storage.Extensions
{
/// <summary>
/// This extension is created to make it easy to add a bearer token to a HttpRequests.
/// </summary>
public static class HttpClientExtension
{
/// <summary>
/// Extension that add authorization header to request
/// </summary>
/// <param name="httpClient">The HttpClient</param>
/// <param name="authorizationToken">the authorization token (jwt)</param>
/// <param name="requestUri">The request Uri</param>
/// <param name="content">The http content</param>
/// <param name="platformAccessToken">The platformAccess tokens</param>
/// <returns>A HttpResponseMessage</returns>
public static Task<HttpResponseMessage> PostAsync(this HttpClient httpClient, string authorizationToken, string requestUri, HttpContent content, string platformAccessToken = null)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(requestUri, UriKind.Relative));
request.Headers.Add("Authorization", "Bearer " + authorizationToken);
request.Content = content;

if (!string.IsNullOrEmpty(platformAccessToken))
{
request.Headers.Add("PlatformAccessToken", platformAccessToken);
}

return httpClient.SendAsync(request, CancellationToken.None);
}

/// <summary>
/// Extension that add authorization header to request
/// </summary>
/// <param name="httpClient">The HttpClient</param>
/// <param name="authorizationToken">the authorization token (jwt)</param>
/// <param name="requestUri">The request Uri</param>
/// <param name="platformAccessToken">The platformAccess tokens</param>
/// <returns>A HttpResponseMessage</returns>
public static Task<HttpResponseMessage> GetAsync(this HttpClient httpClient, string authorizationToken, string requestUri, string platformAccessToken = null)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.Headers.Add("Authorization", "Bearer " + authorizationToken);
if (!string.IsNullOrEmpty(platformAccessToken))
{
request.Headers.Add("PlatformAccessToken", platformAccessToken);
}

return httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
}
}
}
33 changes: 33 additions & 0 deletions src/Storage/Helpers/InstanceHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -246,5 +246,38 @@ private static bool HideOnCurrentTask(HideSettings hideSettings, ProcessElementI

return hideSettings.HideOnTask.Contains(currentTask.ElementId);
}

/// <summary>
/// Parsing instanceOwnerIdentifier string and find the number type and value.
/// </summary>
/// <param name="instanceOwnerIdentifier">The list of applications</param>
public static (string InstanceOwnerIdType, string InstanceOwnerIdValue) GetIdentifierFromInstanceOwnerIdentifier(string instanceOwnerIdentifier)
{
string partyType = null;
string partyNumber = null;

if (string.IsNullOrEmpty(instanceOwnerIdentifier))
{
return (string.Empty, string.Empty);
}

string[] parts = instanceOwnerIdentifier.Replace(" ", string.Empty).ToLower().Split(':');
if (parts.Length != 2)
{
return (string.Empty, string.Empty);
}

partyType = parts[0];
partyNumber = parts[1];

string[] partyTypeHayStack = ["person", "organization"];

if (Array.IndexOf(partyTypeHayStack, partyType) != -1)
{
return (partyType, partyNumber);
}

return (string.Empty, string.Empty);
}
}
}
5 changes: 4 additions & 1 deletion src/Storage/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System;

Check warning on line 1 in src/Storage/Program.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Refactor this top-level file to reduce its Cognitive Complexity from 26 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
Expand All @@ -6,6 +6,7 @@
using Altinn.Common.AccessToken;
using Altinn.Common.AccessToken.Configuration;
using Altinn.Common.AccessToken.Services;
using Altinn.Common.AccessTokenClient.Services;
using Altinn.Common.PEP.Authorization;
using Altinn.Common.PEP.Clients;
using Altinn.Common.PEP.Configuration;
Expand Down Expand Up @@ -139,7 +140,7 @@
}
catch (Exception vaultException)
{
logger.LogError(vaultException, $"Unable to read application insights key.");

Check warning on line 143 in src/Storage/Program.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Don't use string interpolation in logging message templates. (https://rules.sonarsource.com/csharp/RSPEC-2629)
}
}
}
Expand Down Expand Up @@ -189,12 +190,13 @@
services.AddHealthChecks().AddCheck<HealthCheck>("storage_health_check");

services.AddHttpClient<AuthorizationApiClient>();
services.AddHttpClient<IRegisterService, RegisterService>();

services.Configure<AzureStorageConfiguration>(config.GetSection("AzureStorageConfiguration"));
services.Configure<GeneralSettings>(config.GetSection("GeneralSettings"));
services.Configure<KeyVaultSettings>(config.GetSection("kvSetting"));
services.Configure<PepSettings>(config.GetSection("PepSettings"));
services.Configure<PlatformSettings>(config.GetSection("PlatformSettings"));
services.Configure<Altinn.Platform.Storage.Configuration.PlatformSettings>(config.GetSection("PlatformSettings"));
services.Configure<QueueStorageSettings>(config.GetSection("QueueStorageSettings"));
services.Configure<AccessTokenSettings>(config.GetSection("AccessTokenSettings"));
services.Configure<PostgreSqlSettings>(config.GetSection("PostgreSqlSettings"));
Expand Down Expand Up @@ -250,6 +252,7 @@
services.AddTransient<IAuthorizationHandler, ScopeAccessHandler>();
services.AddTransient<IAuthorizationHandler, ClaimAccessHandler>();
services.AddTransient<IAuthorization, AuthorizationService>();
services.AddSingleton<IAccessTokenGenerator, AccessTokenGenerator>();
services.AddTransient<IDataService, DataService>();
services.AddTransient<IInstanceService, InstanceService>();
services.AddTransient<IInstanceEventService, InstanceEventService>();
Expand All @@ -259,7 +262,7 @@

if (!string.IsNullOrEmpty(applicationInsightsConnectionString))
{
services.AddSingleton(typeof(ITelemetryChannel), new ServerTelemetryChannel() { StorageFolder = "/tmp/logtelemetry" });

Check warning on line 265 in src/Storage/Program.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Make sure publicly writable directories are used safely here. (https://rules.sonarsource.com/csharp/RSPEC-5443)
services.AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions
{
ConnectionString = applicationInsightsConnectionString,
Expand Down
26 changes: 26 additions & 0 deletions src/Storage/Services/IRegisterService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Threading.Tasks;
using Altinn.Platform.Register.Models;

namespace Altinn.Platform.Storage.Services
{
/// <summary>
/// Interface to handle services exposed in Platform Register
/// </summary>
public interface IRegisterService
{
/// <summary>
/// Returns party information
/// </summary>
/// <param name="partyId">The partyId</param>
/// <returns>The party for the given partyId</returns>
Task<Party> GetParty(int partyId);

/// <summary>
/// Party lookup
/// </summary>
/// <param name="orgNo">organisation number</param>
/// <param name="person">f or d number</param>
/// <returns></returns>
Task<int> PartyLookup(string orgNo, string person);
}
}
Loading
Loading