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

[WIP] feat: Add support for legacy enterprise users #634

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Digdir.Domain.Dialogporten.Application.Common.Authentication;

public static class Constants
{
public const string NoAuthenticatedUser = "No valid user was authenticated";
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Security.Claims;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Digdir.Domain.Dialogporten.Application.Externals.Authentication;
using Digdir.Domain.Dialogporten.Domain.Parties;

namespace Digdir.Domain.Dialogporten.Application.Common.Extensions;
Expand All @@ -19,6 +20,10 @@ public static class ClaimsPrincipalExtensions
private const string OrgClaim = "urn:altinn:org";
private const string IdportenAuthLevelClaim = "acr";
private const string AltinnAuthLevelClaim = "urn:altinn:authlevel";
private const string AltinnAuthenticationMethodClaim = "urn:altinn:authenticatemethod";
private const string AltinnAuthenticationEnterpriseUserMethod = "virksomhetsbruker";
private const string AltinnUserIdClaim = "urn:altinn:userid";
private const string AltinnUserNameClaim = "urn:altinn:username";
private const string PidClaim = "pid";

public static bool TryGetClaimValue(this ClaimsPrincipal claimsPrincipal, string claimType, [NotNullWhen(true)] out string? value)
Expand Down Expand Up @@ -52,6 +57,33 @@ public static bool TryGetPid(this Claim? pidClaim, [NotNullWhen(true)] out strin
return pid is not null;
}

// This is used for legacy systems using Altinn 2 enterprise users with Maskinporten authentication + token exchange
// as described in https://altinn.github.io/docs/api/rest/kom-i-gang/virksomhet/#autentisering-med-virksomhetsbruker-og-maskinporten
public static bool TryGetLegacySystemUserId(this ClaimsPrincipal claimsPrincipal, [NotNullWhen(true)] out string? systemUserId)
{
systemUserId = null;
if (claimsPrincipal.TryGetClaimValue(AltinnAuthenticationMethodClaim, out var authMethod) &&
authMethod == AltinnAuthenticationEnterpriseUserMethod &&
claimsPrincipal.TryGetClaimValue(AltinnUserIdClaim, out var userId))
{
systemUserId = userId;
}

return systemUserId is not null;
}

public static bool TryGetLegacySystemUserName(this ClaimsPrincipal claimsPrincipal, [NotNullWhen(true)] out string? systemUserName)
{
systemUserName = null;
if (claimsPrincipal.TryGetLegacySystemUserId(out _) &&
claimsPrincipal.TryGetClaimValue(AltinnUserNameClaim, out var claimValue))
{
systemUserName = claimValue;
}

return systemUserName is not null;
}

public static bool TryGetOrgNumber(this Claim? consumerClaim, [NotNullWhen(true)] out string? orgNumber)
{
orgNumber = null;
Expand Down Expand Up @@ -114,6 +146,26 @@ public static IEnumerable<Claim> GetIdentifyingClaims(this List<Claim> claims) =
c.Type.StartsWith(AltinnClaimPrefix, StringComparison.Ordinal)
).OrderBy(c => c.Type);

public static UserType GetUserType(this ClaimsPrincipal claimsPrincipal)
{
if (claimsPrincipal.TryGetPid(out _))
{
return UserType.Person;
}

if (claimsPrincipal.TryGetLegacySystemUserId(out _))
{
return UserType.LegacySystemUser;
}

if (claimsPrincipal.TryGetOrgNumber(out _))
{
return UserType.Enterprise;
}

return UserType.Unknown;
}

private static bool TryGetOrgShortName(this ClaimsPrincipal claimsPrincipal, [NotNullWhen(true)] out string? orgShortName)
=> claimsPrincipal.FindFirst(OrgClaim).TryGetOrgShortName(out orgShortName);

Expand All @@ -132,4 +184,10 @@ internal static bool TryGetOrgShortName(this IUser user, [NotNullWhen(true)] out

internal static bool TryGetPid(this IUser user, [NotNullWhen(true)] out string? pid) =>
user.GetPrincipal().TryGetPid(out pid);

internal static bool TryGetLegacySystemUserId(this IUser user, [NotNullWhen(true)] out string? systemUserId) =>
user.GetPrincipal().TryGetLegacySystemUserId(out systemUserId);

internal static bool TryGetLegacySystemUserName(this IUser user, [NotNullWhen(true)] out string? systemUserName) =>
user.GetPrincipal().TryGetLegacySystemUserName(out systemUserName);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
using System.Diagnostics.CodeAnalysis;
using Digdir.Domain.Dialogporten.Application.Common.Extensions;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Application.Externals.Authentication;
using Digdir.Domain.Dialogporten.Application.Externals.Presentation;

namespace Digdir.Domain.Dialogporten.Application.Common;

public interface IUserNameRegistry
{
bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid);
bool TryGetCurrentUserExternalId([NotNullWhen(true)] out string? userExternalId);
Task<UserInformation?> GetUserInformation(CancellationToken cancellationToken);
}

Expand All @@ -18,24 +19,49 @@ public class UserNameRegistry : IUserNameRegistry
{
private readonly IUser _user;
private readonly INameRegistry _nameRegistry;
private readonly IOrganizationRegistry _organizationRegistry;

public UserNameRegistry(IUser user, INameRegistry nameRegistry)
public UserNameRegistry(IUser user, INameRegistry nameRegistry, IOrganizationRegistry organizationRegistry)
{
_user = user ?? throw new ArgumentNullException(nameof(user));
_nameRegistry = nameRegistry ?? throw new ArgumentNullException(nameof(nameRegistry));
_organizationRegistry = organizationRegistry ?? throw new ArgumentNullException(nameof(organizationRegistry));
}

public bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid) => _user.TryGetPid(out userPid);
public bool TryGetCurrentUserExternalId([NotNullWhen(true)] out string? userExternalId)
{
if (_user.TryGetPid(out userExternalId)) return true;
if (_user.TryGetLegacySystemUserId(out userExternalId)) return true;
if (_user.TryGetOrgNumber(out userExternalId)) return true;
return false;
}

public async Task<UserInformation?> GetUserInformation(CancellationToken cancellationToken)
{
if (!TryGetCurrentUserPid(out var userPid))
if (!TryGetCurrentUserExternalId(out var userExernalId))
{
return null;
}

var userName = await _nameRegistry.GetName(userPid, cancellationToken);
return new(userPid, userName);
string? userName;
switch (_user.GetPrincipal().GetUserType())
{
case UserType.Person:
userName = await _nameRegistry.GetName(userExernalId, cancellationToken);
break;
case UserType.LegacySystemUser:
_user.TryGetLegacySystemUserName(out userName);
break;
case UserType.Enterprise:
userName = await _organizationRegistry.GetOrgShortName(userExernalId, cancellationToken);
break;
case UserType.Unknown:
case UserType.SystemUser: // Implement when we know how this will be handled
default:
throw new UnreachableException("Unknown user type");
oskogstad marked this conversation as resolved.
Show resolved Hide resolved
}

return new(userExernalId, userName);
}
}

Expand All @@ -50,11 +76,11 @@ public LocalDevelopmentUserNameRegistryDecorator(IUserNameRegistry userNameRegis
_userNameRegistry = userNameRegistry ?? throw new ArgumentNullException(nameof(userNameRegistry));
}

public bool TryGetCurrentUserPid([NotNullWhen(true)] out string? userPid) =>
_userNameRegistry.TryGetCurrentUserPid(out userPid);
public bool TryGetCurrentUserExternalId([NotNullWhen(true)] out string? userExternalId) =>
_userNameRegistry.TryGetCurrentUserExternalId(out userExternalId);

public Task<UserInformation?> GetUserInformation(CancellationToken cancellationToken)
=> _userNameRegistry.TryGetCurrentUserPid(out var userPid)
=> _userNameRegistry.TryGetCurrentUserExternalId(out var userPid)
? Task.FromResult<UserInformation?>(new UserInformation(userPid!, LocalDevelopmentUserPid))
: throw new UnreachableException();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Digdir.Domain.Dialogporten.Application.Externals.Authentication;

public enum UserType
{
Unknown = 0,
Person = 1,
LegacySystemUser = 2,
SystemUser = 3,
Enterprise = 4
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using AutoMapper;
using Digdir.Domain.Dialogporten.Application.Common;
using Digdir.Domain.Dialogporten.Application.Common.Authentication;
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
Expand Down Expand Up @@ -44,9 +45,9 @@ public GetDialogSeenLogQueryHandler(
public async Task<GetDialogSeenLogResult> Handle(GetDialogSeenLogQuery request,
CancellationToken cancellationToken)
{
if (!_userNameRegistry.TryGetCurrentUserPid(out var userPid))
if (!_userNameRegistry.TryGetCurrentUserExternalId(out var userPid))
{
return new Forbidden("No valid user pid found.");
return new Forbidden(Constants.NoAuthenticatedUser);
}

var dialog = await _dbContext.Dialogs
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using AutoMapper;
using Digdir.Domain.Dialogporten.Application.Common;
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;
using Digdir.Domain.Dialogporten.Application.Common.Authentication;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities;
using MediatR;
using OneOf;
using Microsoft.EntityFrameworkCore;
using Digdir.Domain.Dialogporten.Application.Common;

namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search;

Expand Down Expand Up @@ -42,9 +43,9 @@ public SearchDialogSeenLogQueryHandler(

public async Task<SearchDialogSeenLogResult> Handle(SearchDialogSeenLogQuery request, CancellationToken cancellationToken)
{
if (!_userNameRegistry.TryGetCurrentUserPid(out var userPid))
if (!_userNameRegistry.TryGetCurrentUserExternalId(out var userPid))
{
return new Forbidden("No valid user pid found.");
return new Forbidden(Constants.NoAuthenticatedUser);
}

var dialog = await _db.Dialogs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using MediatR;
using Microsoft.EntityFrameworkCore;
using OneOf;
using AuthenticationConstants = Digdir.Domain.Dialogporten.Application.Common.Authentication.Constants;

namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get;

Expand Down Expand Up @@ -57,7 +58,7 @@ public async Task<GetDialogResult> Handle(GetDialogQuery request, CancellationTo

if (userInformation is null)
{
return new Forbidden("No valid user pid found.");
return new Forbidden(AuthenticationConstants.NoAuthenticatedUser);
}

var (userPid, userName) = userInformation;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using AutoMapper;
using AutoMapper.QueryableExtensions;
using Digdir.Domain.Dialogporten.Application.Common;
using Digdir.Domain.Dialogporten.Application.Common.Authentication;
using Digdir.Domain.Dialogporten.Application.Common.Extensions;
using Digdir.Domain.Dialogporten.Application.Common.Extensions.Enumerables;
using Digdir.Domain.Dialogporten.Application.Common.Pagination;
Expand Down Expand Up @@ -136,9 +137,9 @@ public SearchDialogQueryHandler(

public async Task<SearchDialogResult> Handle(SearchDialogQuery request, CancellationToken cancellationToken)
{
if (!_userNameRegistry.TryGetCurrentUserPid(out var userPid))
if (!_userNameRegistry.TryGetCurrentUserExternalId(out var userPid))
{
return new Forbidden("No valid user pid found.");
return new Forbidden(Constants.NoAuthenticatedUser);
}

var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchCultureCode);
Expand Down