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
69 changes: 36 additions & 33 deletions src/Altinn.App.Core/Helpers/UserHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,28 +50,16 @@ public UserHelper(
public async Task<UserContext> GetUserContext(HttpContext context)
{
using var activity = _telemetry?.StartGetUserContextActivity();
string? cookieValue = context.Request.Cookies[_settings.GetAltinnPartyCookieName];

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))
{
userContext.UserId = Convert.ToInt32(claim.Value, CultureInfo.InvariantCulture);
}
else if (claim.Type.Equals(AltinnCoreClaimTypes.PartyID, StringComparison.Ordinal))
{
userContext.PartyId = Convert.ToInt32(claim.Value, CultureInfo.InvariantCulture);
}
else if (claim.Type.Equals(AltinnCoreClaimTypes.AuthenticationLevel, StringComparison.Ordinal))
{
userContext.AuthenticationLevel = Convert.ToInt32(claim.Value, CultureInfo.InvariantCulture);
}
}
User = context.User,
UserName = GetClaim(context.User.Claims, AltinnCoreClaimTypes.UserName),
UserId = GetClaim(context.User.Claims, AltinnCoreClaimTypes.UserId),
PartyId = GetClaim(context.User.Claims, AltinnCoreClaimTypes.PartyID),
AuthenticationLevel = GetClaim(context.User.Claims, AltinnCoreClaimTypes.AuthenticationLevel),
};

if (userContext.UserId == default)
{
Expand All @@ -81,25 +69,40 @@ 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
);
}
userContext.PartyId = cookieValue is not null
? Convert.ToInt32(cookieValue, CultureInfo.InvariantCulture)
: userContext.PartyId;

if (userContext.PartyId == userProfile.PartyId)
danielskovli marked this conversation as resolved.
Show resolved Hide resolved
userContext.Party = userContext.PartyId.Equals(userProfile.Party?.PartyId)
? userContext.Party = userProfile.Party
: await _altinnPartyClientService.GetParty(userContext.PartyId);

userContext.SocialSecurityNumber = userContext.Party?.SSN ?? userContext.UserParty.SSN;

return userContext;
}

private static ClaimWrapper GetClaim(IEnumerable<Claim> claims, string claimType)
{
var claim = claims.FirstOrDefault(x => x.Type.Equals(claimType, StringComparison.Ordinal))?.Value;
return new ClaimWrapper(claim);
}

private readonly record struct ClaimWrapper(string? Value)
{
public static implicit operator string?(ClaimWrapper claimWrapper)
{
userContext.Party = userProfile.Party;
return claimWrapper.Value;
}
else

public static implicit operator int(ClaimWrapper claimWrapper)
{
userContext.Party = await _altinnPartyClientService.GetParty(userContext.PartyId);
return string.IsNullOrEmpty(claimWrapper.Value)
? default
: Convert.ToInt32(claimWrapper.Value, CultureInfo.InvariantCulture);
martinothamar marked this conversation as resolved.
Show resolved Hide resolved
}

return userContext;
}
}
85 changes: 85 additions & 0 deletions test/Altinn.App.Api.Tests/Helpers/UserHelperTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
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(postRegisterCustomAppServices: 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();
}

[Fact]
public async Task GetUserContext_PerformsCorrectLogic()
{
// Arrange
const int userId = 1337;
const int partyId = 501337;
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 = await fixture.AltinnPartyClientMock.GetParty(partyId);

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

// Assert
result
.Should()
.BeEquivalentTo(
new Altinn.App.Core.Models.UserContext
{
SocialSecurityNumber = "01039012345",
UserName = $"User{userId}",
UserId = userId,
PartyId = partyId,
AuthenticationLevel = authLevel,
User = userPrincipal,
UserParty = userProfile!.Party,
Party = party,
}
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public void ConfigureMaskinportenClient_OverridesDefaultMaskinportenConfiguratio
var authority = "https://maskinporten.dev/";

// Act
var app = AppBuilder.Build(registerCustomAppServices: services =>
var app = AppBuilder.Build(preRegisterCustomAppServices: services =>
{
services.ConfigureMaskinportenClient(config =>
{
Expand Down Expand Up @@ -101,7 +101,7 @@ public void ConfigureMaskinportenClient_BindsToSpecifiedConfigPath()
// Act
var app = AppBuilder.Build(
configData: configData,
registerCustomAppServices: services =>
preRegisterCustomAppServices: services =>
{
services.ConfigureMaskinportenClient("CustomMaskinportenSettings");
}
Expand Down Expand Up @@ -131,7 +131,7 @@ params string[] additionalScopes
{
// Arrange
Enum.TryParse(tokenAuthority, false, out TokenAuthorities actualTokenAuthority);
var app = AppBuilder.Build(registerCustomAppServices: services =>
var app = AppBuilder.Build(preRegisterCustomAppServices: services =>
{
_ = actualTokenAuthority switch
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ public async Task OpenTelemetry_Sampler_Override_Is_Possible()
var samplerToUse = new ParentBasedSampler(new AlwaysOnSampler());
await using var app = AppBuilder.Build(
configData: configData,
registerCustomAppServices: services =>
preRegisterCustomAppServices: services =>
{
services.ConfigureOpenTelemetryTracerProvider(builder =>
{
Expand Down Expand Up @@ -328,7 +328,7 @@ public async Task OpenTelemetry_MetricReaderOptions_Override_Is_Possible_Through
var timeoutToUse = 4_000;
await using var app = AppBuilder.Build(
configData: configData,
registerCustomAppServices: services =>
preRegisterCustomAppServices: services =>
{
services.Configure<PeriodicExportingMetricReaderOptions>(options =>
{
Expand Down
12 changes: 8 additions & 4 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>? preRegisterCustomAppServices = default,
Action<IServiceCollection>? postRegisterCustomAppServices = default
danielskovli marked this conversation as resolved.
Show resolved Hide resolved
)
{
// Here we follow the order of operations currently present in the Program.cs generated by the template for apps,
Expand All @@ -28,7 +29,7 @@ public static WebApplication Build(
Altinn.App.Api.Extensions.ServiceCollectionExtensions.AddAltinnAppControllersWithViews(builder.Services);

// 2. RegisterCustomAppServices
registerCustomAppServices?.Invoke(builder.Services);
preRegisterCustomAppServices?.Invoke(builder.Services);

// 3. AddAltinnAppServices
Altinn.App.Api.Extensions.ServiceCollectionExtensions.AddAltinnAppServices(
Expand All @@ -37,10 +38,13 @@ public static WebApplication Build(
builder.Environment
);

// 4. ConfigureAppWebHost
// 4. RegisterCustomAppServices
postRegisterCustomAppServices?.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
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public static Fixture Create()
var mockHttpClientFactory = new Mock<IHttpClientFactory>();
var mockMaskinportenClient = new Mock<IMaskinportenClient>();

var app = Api.Tests.TestUtils.AppBuilder.Build(registerCustomAppServices: services =>
var app = Api.Tests.TestUtils.AppBuilder.Build(preRegisterCustomAppServices: services =>
{
services.AddSingleton(mockHttpClientFactory.Object);
services.AddSingleton(mockMaskinportenClient.Object);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public static Fixture Create(bool configureMaskinporten = true)
var mockHttpClientFactory = new Mock<IHttpClientFactory>();
var fakeTimeProvider = new FakeTime(new DateTimeOffset(2024, 1, 1, 10, 0, 0, TimeSpan.Zero));

var app = Api.Tests.TestUtils.AppBuilder.Build(registerCustomAppServices: services =>
var app = Api.Tests.TestUtils.AppBuilder.Build(preRegisterCustomAppServices: services =>
{
services.AddSingleton(mockHttpClientFactory.Object);
services.Configure<MemoryCacheOptions>(options => options.Clock = fakeTimeProvider);
Expand Down
Loading