Skip to content

Commit

Permalink
Populate UserContext.SocialSecurityNumber in UserHelper (#930)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielskovli authored Nov 29, 2024
1 parent fea0541 commit ad4193e
Show file tree
Hide file tree
Showing 4 changed files with 194 additions and 35 deletions.
65 changes: 34 additions & 31 deletions src/Altinn.App.Core/Helpers/UserHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Globalization;
using System.Security.Claims;
using Altinn.App.Core.Configuration;
using Altinn.App.Core.Features;
using Altinn.App.Core.Internal.Profile;
Expand Down Expand Up @@ -50,28 +49,33 @@ public UserHelper(
public async Task<UserContext> GetUserContext(HttpContext context)
{
using var activity = _telemetry?.StartGetUserContextActivity();
string? partyCookieValue = context.Request.Cookies[_settings.GetAltinnPartyCookieName];
Dictionary<string, string> tokenClaims = context.User.Claims.ToDictionary(
x => x.Type,
y => y.Value,
StringComparer.Ordinal
);

UserContext userContext = new UserContext() { User = context.User };

foreach (Claim claim in context.User.Claims)
UserContext userContext = new()
{
if (claim.Type.Equals(AltinnCoreClaimTypes.UserName, StringComparison.Ordinal))
{
userContext.UserName = claim.Value;
}
else if (claim.Type.Equals(AltinnCoreClaimTypes.UserId, StringComparison.Ordinal))
User = context.User,
UserName = tokenClaims.GetValueOrDefault(AltinnCoreClaimTypes.UserName),
UserId = tokenClaims.GetValueOrDefault(AltinnCoreClaimTypes.UserId) switch
{
userContext.UserId = Convert.ToInt32(claim.Value, CultureInfo.InvariantCulture);
}
else if (claim.Type.Equals(AltinnCoreClaimTypes.PartyID, StringComparison.Ordinal))
{ } value => Convert.ToInt32(value, CultureInfo.InvariantCulture),
_ => default,
},
PartyId = tokenClaims.GetValueOrDefault(AltinnCoreClaimTypes.PartyID) switch
{
userContext.PartyId = Convert.ToInt32(claim.Value, CultureInfo.InvariantCulture);
}
else if (claim.Type.Equals(AltinnCoreClaimTypes.AuthenticationLevel, StringComparison.Ordinal))
{ } value => Convert.ToInt32(value, CultureInfo.InvariantCulture),
_ => default,
},
AuthenticationLevel = tokenClaims.GetValueOrDefault(AltinnCoreClaimTypes.AuthenticationLevel) switch
{
userContext.AuthenticationLevel = Convert.ToInt32(claim.Value, CultureInfo.InvariantCulture);
}
}
{ } value => Convert.ToInt32(value, CultureInfo.InvariantCulture),
_ => default,
},
};

if (userContext.UserId == default)
{
Expand All @@ -81,24 +85,23 @@ public async Task<UserContext> GetUserContext(HttpContext context)
UserProfile userProfile =
await _profileClient.GetUserProfile(userContext.UserId)
?? throw new Exception("Could not get user profile while getting user context");
userContext.UserParty = userProfile.Party;

if (context.Request.Cookies[_settings.GetAltinnPartyCookieName] != null)
{
userContext.PartyId = Convert.ToInt32(
context.Request.Cookies[_settings.GetAltinnPartyCookieName],
CultureInfo.InvariantCulture
);
}
if (partyCookieValue is not null)
userContext.PartyId = Convert.ToInt32(partyCookieValue, CultureInfo.InvariantCulture);

userContext.UserParty = userProfile.Party;

if (userContext.PartyId == userProfile.PartyId)
{
userContext.Party = userProfile.Party;
}
else
{
else if (userContext.PartyId != default)
userContext.Party = await _altinnPartyClientService.GetParty(userContext.PartyId);
}

if (!string.IsNullOrWhiteSpace(userContext.Party?.SSN))
userContext.SocialSecurityNumber = userContext.Party.SSN;
else if (!string.IsNullOrWhiteSpace(userContext.Party?.Person?.SSN))
userContext.SocialSecurityNumber = userContext.Party.Person.SSN;
else if (!string.IsNullOrWhiteSpace(userContext.UserParty?.SSN))
userContext.SocialSecurityNumber = userContext.UserParty.SSN;

return userContext;
}
Expand Down
5 changes: 4 additions & 1 deletion test/Altinn.App.Api.Tests/Data/Profile/User/1337.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
"PhoneNumber": "90001337",
"Email": "[email protected]",
"PartyId": 501337,
"Party": {},
"Party": {
"partyId": "501337",
"ssn": "01039012345"
},
"UserType": 1,
"ProfileSettingPreference": {
"Language": "nn",
Expand Down
149 changes: 149 additions & 0 deletions test/Altinn.App.Api.Tests/Helpers/UserHelperTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
using System.Security.Claims;
using Altinn.App.Api.Tests.Mocks;
using Altinn.App.Api.Tests.Utils;
using Altinn.App.Core.Configuration;
using Altinn.App.Core.Helpers;
using Altinn.App.Core.Internal.Profile;
using Altinn.App.Core.Internal.Registers;
using FluentAssertions;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;

namespace Altinn.App.Api.Tests.Helpers;

public class UserHelperTest
{
private sealed record Fixture(WebApplication App) : IAsyncDisposable
{
public readonly IOptions<GeneralSettings> GeneralSettings = Options.Create(new GeneralSettings());
public IProfileClient ProfileClientMock => App.Services.GetRequiredService<IProfileClient>();
public IAltinnPartyClient AltinnPartyClientMock => App.Services.GetRequiredService<IAltinnPartyClient>();

public static Fixture Create(ClaimsPrincipal userPrincipal, string? partyCookieValue = null)
{
var app = TestUtils.AppBuilder.Build(overrideAltinnAppServices: services =>
{
var httpContextMock = new Mock<HttpContext>();
httpContextMock.Setup(x => x.Request.Cookies["AltinnPartyId"]).Returns(partyCookieValue);
httpContextMock.Setup(httpContext => httpContext.User).Returns(userPrincipal);
var httpContextAccessor = new Mock<IHttpContextAccessor>();
httpContextAccessor.Setup(x => x.HttpContext).Returns(httpContextMock.Object);

services.AddSingleton(httpContextAccessor.Object);
services.AddTransient<IProfileClient, ProfileClientMock>();
services.AddTransient<IAltinnPartyClient, AltinnPartyClientMock>();
});
return new Fixture(app);
}

public async ValueTask DisposeAsync() => await App.DisposeAsync();
}

[Theory]
[InlineData(1337, 501337, "01039012345")] // Has `Party` containing correct SSN
[InlineData(1001, 510001, null)] // Has no SSN, because of empty `Party`
[InlineData(1337, 510001, "01899699552")] // `Party` mismatch, forcing load via `IAltinnPartyClient`, resulting in SSN belonging to party 510001
public async Task GetUserContext_PerformsCorrectLogic(int userId, int partyId, string? ssn)
{
// Arrange
const int authLevel = 3;
var userPrincipal = PrincipalUtil.GetUserPrincipal(userId, partyId, authLevel);
await using var fixture = Fixture.Create(userPrincipal);
var userHelper = new UserHelper(
profileClient: fixture.ProfileClientMock,
altinnPartyClientService: fixture.AltinnPartyClientMock,
settings: fixture.GeneralSettings
);
var httpContextAccessor = fixture.App.Services.GetRequiredService<IHttpContextAccessor>();
var httpContext = httpContextAccessor.HttpContext;
var userProfile = await fixture.ProfileClientMock.GetUserProfile(userId);
var party = partyId.Equals(userProfile!.PartyId)
? userProfile!.Party
: await fixture.AltinnPartyClientMock.GetParty(partyId);

// Act
var result = await userHelper.GetUserContext(httpContext!);

// Assert
result
.Should()
.BeEquivalentTo(
new Altinn.App.Core.Models.UserContext
{
SocialSecurityNumber = ssn,
UserName = $"User{userId}",
UserId = userId,
PartyId = partyId,
AuthenticationLevel = authLevel,
User = userPrincipal,
UserParty = userProfile!.Party,
Party = party,
}
);
}

[Fact]
public async Task GetUserContext_HandlesMissingClaims()
{
// Arrange
const int userId = 1001;
const int authLevel = 3;
var userPrincipal = PrincipalUtil.GetUserPrincipal(userId, default, authLevel);
await using var fixture = Fixture.Create(userPrincipal);
var userHelper = new UserHelper(
profileClient: fixture.ProfileClientMock,
altinnPartyClientService: fixture.AltinnPartyClientMock,
settings: fixture.GeneralSettings
);
var httpContextAccessor = fixture.App.Services.GetRequiredService<IHttpContextAccessor>();
var httpContext = httpContextAccessor.HttpContext;
var userProfile = await fixture.ProfileClientMock.GetUserProfile(userId);

// Act
var result = await userHelper.GetUserContext(httpContext!);

// Assert
result
.Should()
.BeEquivalentTo(
new Altinn.App.Core.Models.UserContext
{
SocialSecurityNumber = null,
UserName = $"User{userId}",
UserId = userId,
PartyId = default,
AuthenticationLevel = authLevel,
User = userPrincipal,
UserParty = userProfile!.Party,
Party = null,
}
);
}

[Fact]
public async Task GetUserContext_ThrowsOnMissingUserId()
{
// Arrange
var userPrincipal = PrincipalUtil.GetUserPrincipal(default, default);
await using var fixture = Fixture.Create(userPrincipal);
var userHelper = new UserHelper(
profileClient: fixture.ProfileClientMock,
altinnPartyClientService: fixture.AltinnPartyClientMock,
settings: fixture.GeneralSettings
);
var httpContextAccessor = fixture.App.Services.GetRequiredService<IHttpContextAccessor>();
var httpContext = httpContextAccessor.HttpContext;

// Act
var act = async () =>
{
await userHelper.GetUserContext(httpContext!);
};

// Assert
await act.Should().ThrowAsync<Exception>().WithMessage("*not*ID*from*claims*");
}
}
10 changes: 7 additions & 3 deletions test/Altinn.App.Api.Tests/TestUtils/AppBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ public static class AppBuilder
public static WebApplication Build(
WebApplicationBuilder? builder = default,
IEnumerable<KeyValuePair<string, string?>>? configData = default,
Action<IServiceCollection>? registerCustomAppServices = default
Action<IServiceCollection>? registerCustomAppServices = default,
Action<IServiceCollection>? overrideAltinnAppServices = default
)
{
// Here we follow the order of operations currently present in the Program.cs generated by the template for apps,
Expand Down Expand Up @@ -37,10 +38,13 @@ public static WebApplication Build(
builder.Environment
);

// 4. ConfigureAppWebHost
// 4. OverrideAltinnAppServices
overrideAltinnAppServices?.Invoke(builder.Services);

// 5. ConfigureAppWebHost
Altinn.App.Api.Extensions.WebHostBuilderExtensions.ConfigureAppWebHost(builder.WebHost, []);

// 5. UseAltinnAppCommonConfiguration
// 6. UseAltinnAppCommonConfiguration
var app = builder.Build();
Altinn.App.Api.Extensions.WebApplicationBuilderExtensions.UseAltinnAppCommonConfiguration(app);

Expand Down

0 comments on commit ad4193e

Please sign in to comment.