Skip to content
2 changes: 1 addition & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<PackageVersion Include="Altinn.Common.EFormidlingClient" Version="1.3.3" />
<PackageVersion Include="Altinn.Common.PEP" Version="4.2.2" />
<PackageVersion Include="Altinn.Platform.Models" Version="1.6.1" />
<PackageVersion Include="Altinn.Platform.Storage.Interface" Version="4.3.0" />
<PackageVersion Include="Altinn.Platform.Storage.Interface" Version="4.4.0" />
<PackageVersion Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.4.0" />
<PackageVersion Include="Azure.Identity" Version="1.17.1" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.6.0" />
Expand Down
10 changes: 8 additions & 2 deletions src/Altinn.App.Api/Controllers/InstancesController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,10 @@ public async Task<ActionResult<InstanceResponse>> Post(
try
{
party = await LookupParty(instanceTemplate.InstanceOwner) ?? throw new Exception("Unknown party");
instanceTemplate.InstanceOwner = InstantiationHelper.PartyToInstanceOwner(party);
instanceTemplate.InstanceOwner = await InstantiationHelper.PartyToInstanceOwner(
party,
_authenticationContext
);
}
catch (Exception partyLookupException)
{
Expand Down Expand Up @@ -491,7 +494,10 @@ public async Task<ActionResult<InstanceResponse>> PostSimplified(
try
{
party = await LookupParty(instansiationInstance.InstanceOwner) ?? throw new Exception("Unknown party");
instansiationInstance.InstanceOwner = InstantiationHelper.PartyToInstanceOwner(party);
instansiationInstance.InstanceOwner = await InstantiationHelper.PartyToInstanceOwner(
party,
_authenticationContext
);
}
catch (Exception partyLookupException)
{
Expand Down
6 changes: 6 additions & 0 deletions src/Altinn.App.Api/Models/InstanceResponse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ internal static InstanceResponse From(Instance instance, Party instanceOwnerPart
PersonNumber = instance.InstanceOwner.PersonNumber,
OrganisationNumber = instance.InstanceOwner.OrganisationNumber,
Username = instance.InstanceOwner.Username,
ExternalIdentifier = instance.InstanceOwner.ExternalIdentifier,
Party = PartyResponse.From(instanceOwnerParty),
},
AppId = instance.AppId,
Expand Down Expand Up @@ -159,6 +160,11 @@ public sealed class InstanceOwnerResponse
/// </summary>
public required string Username { get; init; }

/// <summary>
/// The external identifier of a self identified party. Null if the party is not self identified.
/// </summary>
public string ExternalIdentifier { get; init; }

/// <summary>
/// Party information for the instance owner.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions src/Altinn.App.Core/Constants/AltinnUrns.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ internal static class AltinnUrns
public const string PersonId = "urn:altinn:person:identifier-no";
public const string UserId = "urn:altinn:userid";
public const string UserName = "urn:altinn:username";
public const string SelfIdentifiedEmail = "urn:altinn:person:idporten-email";
public const string PartyId = "urn:altinn:partyid";
public const string PartyUuid = "urn:altinn:partyuuid";
public const string RepresentingPartyId = "urn:altinn:representingpartyid";
public const string App = "urn:altinn:app";
public const string AppResource = "urn:altinn:appresource";
Expand Down
59 changes: 59 additions & 0 deletions src/Altinn.App.Core/Helpers/InstantiationHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System.Globalization;
using Altinn.App.Core.Features.Auth;
using Altinn.Platform.Profile.Models;
using Altinn.Platform.Register.Enums;
using Altinn.Platform.Register.Models;
using Altinn.Platform.Storage.Interface.Models;
Expand Down Expand Up @@ -217,4 +219,61 @@ public static InstanceOwner PartyToInstanceOwner(Party party)
// instanceOwnerPartyType == "unknown"
};
}

/// <summary>
/// Get the correct <see cref="InstanceOwner" /> object from the <see cref="Party" /> object of the entity that should own the instance
/// Use authenticationContext to get the external identity for self identified parties
/// </summary>
public static async Task<InstanceOwner> PartyToInstanceOwner(
Party party,
IAuthenticationContext authenticationContext
)
{
if (!string.IsNullOrEmpty(party.SSN))
{
return new() { PartyId = party.PartyId.ToString(CultureInfo.InvariantCulture), PersonNumber = party.SSN };
}
else if (!string.IsNullOrEmpty(party.OrgNumber))
{
return new()
{
PartyId = party.PartyId.ToString(CultureInfo.InvariantCulture),
OrganisationNumber = party.OrgNumber,
};
}
else if (party.PartyTypeName.Equals(PartyType.SelfIdentified))
{
string? externalIdentifier = null;
if (authenticationContext is not null)
{
externalIdentifier = await GetExternalIdentityForSelfIdentifiedParty(party, authenticationContext);
}
return new()
{
PartyId = party.PartyId.ToString(CultureInfo.InvariantCulture),
Username = party.Name,
ExternalIdentifier = externalIdentifier,
};
}
return new()
{
PartyId = party.PartyId.ToString(CultureInfo.InvariantCulture),
// instanceOwnerPartyType == "unknown"
};
}

internal static async Task<string?> GetExternalIdentityForSelfIdentifiedParty(
Party party,
IAuthenticationContext authenticationContext
)
{
if (party.PartyTypeName != PartyType.SelfIdentified)
return null;

if (authenticationContext.Current is not Authenticated.User user)
return null;

UserProfile profile = await user.LookupProfile();
return profile.ExternalIdentity;
}
}
15 changes: 9 additions & 6 deletions src/Altinn.App.Core/Internal/Expressions/LayoutEvaluatorState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,16 +224,19 @@ public string GetInstanceContext(string key)
?? throw new InvalidOperationException("InstanceOwner or PartyId is null"),
"appId" => Instance.AppId ?? throw new InvalidOperationException("AppId is null"),
"instanceId" => Instance.Id ?? throw new InvalidOperationException("InstanceId is null"),
"instanceOwnerPartyType" => (
!string.IsNullOrWhiteSpace(Instance.InstanceOwner?.OrganisationNumber) ? "org"
: !string.IsNullOrWhiteSpace(Instance.InstanceOwner?.PersonNumber) ? "person"
: !string.IsNullOrWhiteSpace(Instance.InstanceOwner?.Username) ? "selfIdentified"
: "unknown"
),
"instanceOwnerPartyType" => GetInstanceOwnerPartyType(Instance.InstanceOwner),
_ => throw new ExpressionEvaluatorTypeErrorException($"Unknown Instance context property {key}"),
};
}

private static string GetInstanceOwnerPartyType(InstanceOwner? instanceOwner)
{
return !string.IsNullOrWhiteSpace(instanceOwner?.OrganisationNumber) ? "org"
: !string.IsNullOrWhiteSpace(instanceOwner?.PersonNumber) ? "person"
: !string.IsNullOrWhiteSpace(instanceOwner?.Username) ? "selfIdentified"
: "unknown";
}

/// <summary>
/// Count the number of data elements of a specific type
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
personNumber: 01039012345,
organisationNumber: null,
username: null,
externalIdentifier: null,
party: {
partyId: 501337,
partyUuid: null,
Expand Down Expand Up @@ -92,6 +93,7 @@
personNumber: 01039012345,
organisationNumber: null,
username: null,
externalIdentifier: null,
party: {
partyId: 501337,
partyUuid: null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2126,7 +2126,7 @@
"type": "string",
"nullable": true
},
"email": {
"externalIdentifier": {
"type": "string",
"nullable": true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8632,7 +8632,7 @@
"type": "string",
"nullable": true
},
"email": {
"externalIdentifier": {
"type": "string",
"nullable": true
}
Expand Down Expand Up @@ -8669,6 +8669,11 @@
"description": "The username of the party. Null if the party is not self identified.",
"nullable": true
},
"externalIdentifier": {
"type": "string",
"description": "The external identifier of a self identified party. Null if the party is not self identified.",
"nullable": true
},
"party": {
"$ref": "#/components/schemas/PartyResponse"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1015,6 +1015,7 @@ namespace Altinn.App.Api.Models
public sealed class InstanceOwnerResponse
{
public InstanceOwnerResponse() { }
public string ExternalIdentifier { get; init; }
public required string OrganisationNumber { get; init; }
public required Altinn.App.Api.Models.PartyResponse Party { get; init; }
public required string PartyId { get; init; }
Expand Down
Loading
Loading