Skip to content

Commit 9e7f04e

Browse files
authored
Merge pull request #49 from Altinn/feat/addPidLookupOfInternalIdsFromRegister
feat: add support for lookup of internal ids for personal tokens
2 parents 7069b35 + e5330d1 commit 9e7f04e

9 files changed

Lines changed: 247 additions & 50 deletions

File tree

TokenGenerator/GetPersonalToken.cs

Lines changed: 77 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,80 +1,107 @@
11
using System;
2-
using System.Collections.Generic;
32
using System.Linq;
4-
using System.Threading;
53
using Microsoft.AspNetCore.Mvc;
64
using Microsoft.Azure.WebJobs;
75
using Microsoft.Azure.WebJobs.Extensions.Http;
86
using Microsoft.AspNetCore.Http;
97
using System.Threading.Tasks;
108
using Microsoft.Extensions.Options;
119
using TokenGenerator.Services.Interfaces;
10+
using System.Threading;
11+
12+
namespace TokenGenerator;
1213

13-
namespace TokenGenerator
14+
public class GetPersonalToken
1415
{
15-
public class GetPersonalToken
16+
private readonly IToken tokenHelper;
17+
private readonly IRequestValidator requestValidator;
18+
private readonly IAuthorization authorization;
19+
private readonly IRandomIdentifier randomIdentifier;
20+
private readonly IRegisterService registerService;
21+
private readonly Settings settings;
22+
23+
public GetPersonalToken(IToken tokenHelper, IRequestValidator requestValidator, IAuthorization authorization, IRandomIdentifier randomIdentifier, IRegisterService registerService, IOptions<Settings> settings)
1624
{
17-
private readonly IToken tokenHelper;
18-
private readonly IRequestValidator requestValidator;
19-
private readonly IAuthorization authorization;
20-
private readonly IRandomIdentifier randomIdentifier;
21-
private readonly Settings settings;
25+
this.tokenHelper = tokenHelper;
26+
this.requestValidator = requestValidator;
27+
this.authorization = authorization;
28+
this.randomIdentifier = randomIdentifier;
29+
this.registerService = registerService;
30+
this.settings = settings.Value;
31+
}
2232

23-
public GetPersonalToken(IToken tokenHelper, IRequestValidator requestValidator, IAuthorization authorization, IRandomIdentifier randomIdentifier, IOptions<Settings> settings)
33+
[FunctionName(nameof(GetPersonalToken))]
34+
public async Task<ActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req)
35+
{
36+
using var cancellationSource = CancellationTokenSource.CreateLinkedTokenSource(req.HttpContext.RequestAborted);
37+
ActionResult failedAuthorizationResult = await authorization.Authorize(settings.AuthorizedScopePersonal);
38+
if (failedAuthorizationResult != null)
2439
{
25-
this.tokenHelper = tokenHelper;
26-
this.requestValidator = requestValidator;
27-
this.authorization = authorization;
28-
this.randomIdentifier = randomIdentifier;
29-
this.settings = settings.Value;
40+
return failedAuthorizationResult;
3041
}
3142

32-
[FunctionName(nameof(GetPersonalToken))]
33-
public async Task<ActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req)
43+
requestValidator.ValidateQueryParam("env", true, tokenHelper.IsValidEnvironment, out string env);
44+
requestValidator.ValidateQueryParam("scopes", false, tokenHelper.TryParseScopes, out string[] scopes, new[] { "altinn:enduser" });
45+
requestValidator.ValidateQueryParam("userId", false, uint.TryParse, out uint userId);
46+
requestValidator.ValidateQueryParam("partyId", false, uint.TryParse, out uint partyId);
47+
requestValidator.ValidateQueryParam("pid", false, tokenHelper.IsValidPid, out string pid);
48+
requestValidator.ValidateQueryParam("bulkCount", false, uint.TryParse, out uint bulkCount);
49+
requestValidator.ValidateQueryParam("authLvl", false, tokenHelper.IsValidAuthLvl, out string authLvl, "3");
50+
requestValidator.ValidateQueryParam("consumerOrgNo", false, tokenHelper.IsValidPidOrOrgNo, out string consumerOrgNo, "991825827");
51+
requestValidator.ValidateQueryParam("partyuuid", false, Guid.TryParse, out Guid partyUuid);
52+
requestValidator.ValidateQueryParam("userName", false, tokenHelper.IsValidIdentifier, out string userName, "");
53+
requestValidator.ValidateQueryParam("clientAmr", false, tokenHelper.IsValidIdentifier, out string clientAmr, "virksomhetssertifikat");
54+
requestValidator.ValidateQueryParam<uint>("ttl", false, uint.TryParse, out uint ttl, 1800);
55+
requestValidator.ValidateQueryParam("delegationSource", false, tokenHelper.IsValidUri, out string delegationSource);
56+
requestValidator.ValidateQueryParam("getEnvIds", false, bool.TryParse, out bool getEnvIds);
57+
58+
if (requestValidator.GetErrors().Count > 0)
3459
{
35-
ActionResult failedAuthorizationResult = await authorization.Authorize(settings.AuthorizedScopePersonal);
36-
if (failedAuthorizationResult != null)
37-
{
38-
return failedAuthorizationResult;
39-
}
60+
return new BadRequestObjectResult(requestValidator.GetErrors());
61+
}
4062

41-
requestValidator.ValidateQueryParam("env", true, tokenHelper.IsValidEnvironment, out string env);
42-
requestValidator.ValidateQueryParam("scopes", false, tokenHelper.TryParseScopes, out string[] scopes, new[] { "altinn:enduser" });
43-
requestValidator.ValidateQueryParam("userId", false, uint.TryParse, out uint userId);
44-
requestValidator.ValidateQueryParam("partyId", false, uint.TryParse, out uint partyId);
45-
requestValidator.ValidateQueryParam("pid", false, tokenHelper.IsValidPid, out string pid);
46-
requestValidator.ValidateQueryParam("bulkCount", false, uint.TryParse, out uint bulkCount);
47-
requestValidator.ValidateQueryParam("authLvl", false, tokenHelper.IsValidAuthLvl, out string authLvl, "3");
48-
requestValidator.ValidateQueryParam("consumerOrgNo", false, tokenHelper.IsValidPidOrOrgNo, out string consumerOrgNo, "991825827");
49-
requestValidator.ValidateQueryParam("partyuuid", false, Guid.TryParse, out Guid partyUuid);
50-
requestValidator.ValidateQueryParam("userName", false, tokenHelper.IsValidIdentifier, out string userName, "");
51-
requestValidator.ValidateQueryParam("clientAmr", false, tokenHelper.IsValidIdentifier, out string clientAmr, "virksomhetssertifikat");
52-
requestValidator.ValidateQueryParam<uint>("ttl", false, uint.TryParse, out uint ttl, 1800);
53-
requestValidator.ValidateQueryParam("delegationSource", false, tokenHelper.IsValidUri, out string delegationSource);
54-
55-
if (requestValidator.GetErrors().Count > 0)
63+
if (bulkCount > 0)
64+
{
65+
var randomList = randomIdentifier.GetRandomPersonalIdentifiers(bulkCount);
66+
var tokenList = await tokenHelper.GetTokenList(randomList, async randomPid =>
67+
await tokenHelper.GetPersonalToken(req, env, scopes, userId, partyId, randomPid, authLvl, consumerOrgNo, userName, clientAmr, ttl, delegationSource, partyUuid));
68+
69+
return new OkObjectResult(tokenList);
70+
}
71+
72+
if (getEnvIds)
73+
{
74+
if (!settings.EnvPlatformSubscriptionKeyDict.TryGetValue(env, out string subscriptionKey))
5675
{
57-
return new BadRequestObjectResult(requestValidator.GetErrors());
76+
return new BadRequestObjectResult($"No subscription key configured for environment: {env}");
5877
}
5978

60-
if (bulkCount > 0)
79+
if (string.IsNullOrWhiteSpace(pid))
6180
{
62-
var randomList = randomIdentifier.GetRandomPersonalIdentifiers(bulkCount);
63-
var tokenList = await tokenHelper.GetTokenList(randomList, async randomPid =>
64-
await tokenHelper.GetPersonalToken(req, env, scopes, userId, partyId, randomPid, authLvl, consumerOrgNo, userName, clientAmr, ttl, delegationSource, partyUuid));
65-
66-
return new OkObjectResult(tokenList);
81+
return new BadRequestObjectResult("pid is required when getEnvIds is true.");
6782
}
68-
69-
pid ??= randomIdentifier.GetRandomPersonalIdentifiers(1).First();
70-
string token = await tokenHelper.GetPersonalToken(req, env, scopes, userId, partyId, pid, authLvl, consumerOrgNo, userName, clientAmr, ttl, delegationSource, partyUuid);
7183

72-
if (!string.IsNullOrEmpty(req.Query["dump"]))
84+
var platformAccessToken = await tokenHelper.GetPlatformAccessToken(env, settings.PlatformAccessTokenIssuerName, 300);
85+
var result = await registerService.GetEnvironmentIdentifiers(env, pid, platformAccessToken, subscriptionKey, cancellationSource.Token);
86+
if (!result.Success)
7387
{
74-
return new OkObjectResult(tokenHelper.Dump(token));
88+
return new BadRequestObjectResult("Could not retrieve environment identifiers. Check that the pid is valid for the specified environment.");
7589
}
7690

77-
return new OkObjectResult(token);
91+
userId = result.Party.User.UserId;
92+
userName = result.Party.User.Username;
93+
partyId = result.Party.PartyId;
94+
partyUuid = result.Party.Uuid;
7895
}
96+
97+
pid ??= randomIdentifier.GetRandomPersonalIdentifiers(1).First();
98+
string token = await tokenHelper.GetPersonalToken(req, env, scopes, userId, partyId, pid, authLvl, consumerOrgNo, userName, clientAmr, ttl, delegationSource, partyUuid);
99+
100+
if (!string.IsNullOrEmpty(req.Query["dump"]))
101+
{
102+
return new OkObjectResult(tokenHelper.Dump(token));
103+
}
104+
105+
return new OkObjectResult(token);
79106
}
80107
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#nullable enable
2+
3+
using System.Collections.Generic;
4+
using System.Text.Json.Serialization;
5+
6+
namespace Altinn.Register.Models;
7+
8+
/// <summary>
9+
/// A list object is a wrapper around a list of items to allow for the API to be
10+
/// extended in the future without breaking backwards compatibility.
11+
/// </summary>
12+
public abstract record ListObject
13+
{
14+
/// <summary>
15+
/// Creates a new <see cref="ListObject{T}"/> from a list of items.
16+
/// </summary>
17+
/// <typeparam name="T">The list type.</typeparam>
18+
/// <param name="items">The list of items.</param>
19+
/// <returns>A <see cref="ListObject{T}"/>.</returns>
20+
public static ListObject<T> Create<T>(IEnumerable<T> items)
21+
=> new(items);
22+
}
23+
24+
/// <summary>
25+
/// A concrete list object.
26+
/// </summary>
27+
/// <typeparam name="T">The item type.</typeparam>
28+
/// <param name="Items">The items.</param>
29+
public record ListObject<T>(
30+
[property: JsonPropertyName("data")]
31+
IEnumerable<T> Items)
32+
: ListObject;
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System;
2+
using System.Text.Json.Serialization;
3+
4+
namespace Altinn.Register.Models;
5+
6+
/// <summary>
7+
/// Represents a party in Altinn Register.
8+
/// </summary>
9+
public class Party
10+
{
11+
/// <summary>
12+
/// Gets the type of the party.
13+
/// </summary>
14+
[JsonPropertyName("partyType")]
15+
public string Type { get; }
16+
17+
/// <summary>
18+
/// Gets the type of the party.
19+
/// </summary>
20+
[JsonPropertyName("personIdentifier")]
21+
public string Pid { get; }
22+
23+
/// <summary>
24+
/// Gets the UUID of the party.
25+
/// </summary>
26+
[JsonPropertyName("partyUuid")]
27+
public Guid Uuid { get; init; }
28+
29+
/// <summary>
30+
/// Gets the canonical URN of the party.
31+
/// </summary>
32+
[JsonPropertyName("urn")]
33+
public string Urn { get; init; }
34+
35+
/// <summary>
36+
/// Gets the ID of the party.
37+
/// </summary>
38+
[JsonPropertyName("partyId")]
39+
public uint PartyId { get; init; }
40+
41+
/// <summary>
42+
/// Gets the display-name of the party.
43+
/// </summary>
44+
[JsonPropertyName("displayName")]
45+
public string DisplayName { get; init; }
46+
47+
/// <summary>
48+
/// Gets the user object associated with the party.
49+
/// </summary>
50+
[JsonPropertyName("user")]
51+
public User User { get; init; }
52+
}
53+
54+
/// <summary>
55+
/// Represents the user properties from Altinn Register.
56+
/// </summary>
57+
public class User
58+
{
59+
/// <summary>
60+
/// Gets the userId of the party.
61+
/// </summary>
62+
[JsonPropertyName("userId")]
63+
public uint UserId { get; }
64+
65+
/// <summary>
66+
/// Gets the username of the party.
67+
/// </summary>
68+
[JsonPropertyName("username")]
69+
public string Username { get; }
70+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using Altinn.Register.Models;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
5+
namespace TokenGenerator.Services.Interfaces
6+
{
7+
public interface IRegisterService
8+
{
9+
Task<(bool Success, Party Party)> GetEnvironmentIdentifiers(string env, string pid, string platformAccessToken, string subscriptionKey, CancellationToken cancellationToken);
10+
}
11+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using Altinn.Register.Models;
2+
using System;
3+
using System.Linq;
4+
using System.Net.Http;
5+
using System.Net.Http.Json;
6+
using System.Threading;
7+
using System.Threading.Tasks;
8+
using TokenGenerator.Services.Interfaces;
9+
10+
namespace TokenGenerator.Services;
11+
12+
public class RegisterService : IRegisterService
13+
{
14+
private readonly HttpClient httpClient;
15+
16+
public RegisterService(HttpClient httpClient)
17+
{
18+
this.httpClient = httpClient;
19+
}
20+
21+
public async Task<(bool Success, Party Party)> GetEnvironmentIdentifiers(string env, string pid, string platformAccessToken, string subscriptionKey, CancellationToken cancellationToken)
22+
{
23+
string requestUri = $"https://platform.{env}.altinn.cloud/register/api/v1/access-management/parties/query?fields=user";
24+
if (env.Equals("tt02", StringComparison.OrdinalIgnoreCase))
25+
{
26+
requestUri = $"https://platform.tt02.altinn.no/register/api/v1/access-management/parties/query?fields=user";
27+
}
28+
29+
ListObject<string> body = ListObject.Create(new[] { pid });
30+
31+
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestUri)
32+
{
33+
Content = JsonContent.Create(body)
34+
};
35+
request.Headers.Add("PlatformAccessToken", platformAccessToken);
36+
request.Headers.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
37+
38+
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken);
39+
if (response.IsSuccessStatusCode)
40+
{
41+
ListObject<Party> result = await response.Content.ReadFromJsonAsync<ListObject<Party>>(cancellationToken: cancellationToken);
42+
if (result == null || !result.Items.Any())
43+
{
44+
return (false, null);
45+
}
46+
47+
return (true, result.Items.First());
48+
}
49+
50+
return (false, null);
51+
}
52+
}

TokenGenerator/Settings.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,12 @@ public class Settings
2828
public string EnvironmentsApiToken { get; set; }
2929
public string EnvironmentsConsentToken { get; set; }
3030
public string EnvironmentsTtdAccessToken { get; set; }
31+
public string EnvironmentSubscriptionKeys { get; set; }
3132
public string IssuerSigningKeys { get; set; }
3233
public Dictionary<string, string> EnvironmentsApiTokenDict => GetKeyValuePairs(EnvironmentsApiToken);
3334
public Dictionary<string, string> EnvironmentsConsentTokenDict => GetKeyValuePairs(EnvironmentsConsentToken);
3435
public Dictionary<string, string> EnvironmentsTtdAccessTokenDict => GetKeyValuePairs(EnvironmentsTtdAccessToken);
36+
public Dictionary<string, string> EnvPlatformSubscriptionKeyDict => GetKeyValuePairs(EnvironmentSubscriptionKeys);
3537
public Dictionary<string, string> IssuerSigningKeysDict => GetKeyValuePairs(IssuerSigningKeys);
3638

3739
private Dictionary<string, string> GetKeyValuePairs(string stringfieldToSplit, char fieldSeparator = ';', char keyValueSeparator = ':')

TokenGenerator/Startup.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public override void Configure(IFunctionsHostBuilder builder)
3535
builder.Services.AddScoped<IAuthorizationBasic, AuthorizationBasic>();
3636
builder.Services.AddScoped<IRequestValidator, RequestValidator>();
3737
builder.Services.AddScoped<IAuthorization, Authorization>();
38+
builder.Services.AddHttpClient<IRegisterService, RegisterService>();
3839
}
3940
}
4041
}

TokenGenerator/TokenGenerator.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<PackageReference Include="Azure.Security.KeyVault.Secrets" Version="4.6.0" />
1111
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
1212
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
13+
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.9" />
1314
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="8.1.0" />
1415
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.1.0" />
1516
<PackageReference Include="Microsoft.NET.Sdk.Functions" Version="4.4.1" />

TokenGenerator/local.settings.json.COPYME

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
"EnvironmentsApiToken": "none:;dev:altinn-testtools-kv",
2222
"EnvironmentsConsentToken": "dev:altinn-testtools-kv",
2323
"EnvironmentsTtdAccessToken": "dev:altinn-testtools-kv",
24+
"EnvironmentSubscriptionKeys": "dev:mysubscriptionkey",
2425
"TokenAuthorizationWellKnownEndpoint": "https://test.maskinporten.no/.well-known/oauth-authorization-server",
2526
"IssuerSigningKeys": "key1:MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgJjBP7srUVMWuPAzzSZPiPnmpYdsDuQg28p3d4oj9+UahRANCAAQ+0RXI0tdgdkhEVWb/tdkIdstPsMo8JnBCJwmV98/LOAnROt4EAiSeFWTwCtF64wBiDICzAHAiNXxCik6ZL51G"
2627
}

0 commit comments

Comments
 (0)