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

Populate UserContext.SocialSecurityNumber in UserHelper #930

Merged
merged 8 commits into from
Nov 29, 2024
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)
danielskovli marked this conversation as resolved.
Show resolved Hide resolved
{
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
Loading