diff --git a/docs/schema/V1/schema.verified.graphql b/docs/schema/V1/schema.verified.graphql
index e6b5177e3..a77019319 100644
--- a/docs/schema/V1/schema.verified.graphql
+++ b/docs/schema/V1/schema.verified.graphql
@@ -141,6 +141,10 @@ type DialogByIdForbidden implements DialogByIdError {
message: String!
}
+type DialogByIdForbiddenAuthLevelToLow implements DialogByIdError {
+ message: String!
+}
+
type DialogByIdNotFound implements DialogByIdError {
message: String!
}
@@ -466,4 +470,4 @@ scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time"
scalar URL @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc3986")
-scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122")
\ No newline at end of file
+scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122")
diff --git a/docs/schema/V1/swagger.verified.json b/docs/schema/V1/swagger.verified.json
index 05a308733..3dc480624 100644
--- a/docs/schema/V1/swagger.verified.json
+++ b/docs/schema/V1/swagger.verified.json
@@ -2258,6 +2258,24 @@
}
]
},
+ "nonSensitiveSummary": {
+ "description": "An optional non-sensitive summary of the dialog and its current state.\nUsed for search and list views if the user authorization does not meet the required eIDAS level",
+ "nullable": true,
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/V1CommonContent_ContentValue"
+ }
+ ]
+ },
+ "nonSensitiveTitle": {
+ "description": "An optional non-sensitive title of the dialog.\nUsed for search and list views if the user authorization does not meet the required eIDAS level",
+ "nullable": true,
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/V1CommonContent_ContentValue"
+ }
+ ]
+ },
"senderName": {
"description": "Overridden sender name. If not supplied, assume \u0022org\u0022 as the sender name. Must be text/plain if supplied.\nSupported media types: text/plain",
"nullable": true,
@@ -2912,6 +2930,24 @@
}
]
},
+ "nonSensitiveSummary": {
+ "description": "An optional non-sensitive summary of the dialog and its current state.\nUsed for search and list views if the user authorization does not meet the required eIDAS level",
+ "nullable": true,
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/V1CommonContent_ContentValue"
+ }
+ ]
+ },
+ "nonSensitiveTitle": {
+ "description": "An optional non-sensitive title of the dialog.\nUsed for search and list views if the user authorization does not meet the required eIDAS level",
+ "nullable": true,
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/V1CommonContent_ContentValue"
+ }
+ ]
+ },
"senderName": {
"description": "Overridden sender name. If not supplied, assume \u0022org\u0022 as the sender name. Must be text/plain if supplied.",
"nullable": true,
@@ -3367,6 +3403,24 @@
}
]
},
+ "nonSensitiveSummary": {
+ "description": "An optional non-sensitive summary of the dialog and its current state.\nUsed for search and list views if the user authorization does not meet the required eIDAS level",
+ "nullable": true,
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/V1CommonContent_ContentValue"
+ }
+ ]
+ },
+ "nonSensitiveTitle": {
+ "description": "An optional non-sensitive title of the dialog.\nUsed for search and list views if the user authorization does not meet the required eIDAS level",
+ "nullable": true,
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/V1CommonContent_ContentValue"
+ }
+ ]
+ },
"senderName": {
"description": "Overridden sender name. If not supplied, assume \u0022org\u0022 as the sender name.",
"nullable": true,
@@ -4062,6 +4116,24 @@
}
]
},
+ "nonSensitiveSummary": {
+ "description": "An optional non-sensitive summary of the dialog and its current state.\nUsed for search and list views if the user authorization does not meet the required eIDAS level",
+ "nullable": true,
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/V1CommonContent_ContentValue"
+ }
+ ]
+ },
+ "nonSensitiveTitle": {
+ "description": "An optional non-sensitive title of the dialog.\nUsed for search and list views if the user authorization does not meet the required eIDAS level",
+ "nullable": true,
+ "oneOf": [
+ {
+ "$ref": "#/components/schemas/V1CommonContent_ContentValue"
+ }
+ ]
+ },
"senderName": {
"description": "Overridden sender name. If not supplied, assume \u0022org\u0022 as the sender name.",
"nullable": true,
diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs b/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs
index b999531c7..d156a0339 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Common/Authorization/Constants.cs
@@ -10,6 +10,10 @@ public static class Constants
public const string TransmissionReadAction = "transmissionread";
public static readonly Uri UnauthorizedUri = new("urn:dialogporten:unauthorized");
+ public const string IdportenLoaSubstantial = "idporten-loa-substantial";
+ public const string IdportenLoaHigh = "idporten-loa-high";
+ public const string AltinnAuthLevelTooLow = "Altinn authentication level too low.";
+
public const string DisableAltinnEventsRequiresAdminScope =
"Disabling Altinn events requires service owner admin scope.";
@@ -24,7 +28,7 @@ public static class Constants
public static class AuthorizationScope
{
///
- /// Needed to be able to modify (create/update/delete) correspondence service resources. Primarily used by the correspondence service.
+ /// Needed to be able to modify (create/update/delete) correspondence service resources. Primarily used by the correspondence service.
///
public const string CorrespondenceScope = "digdir:dialogporten.correspondence";
diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs b/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs
index 0d4c92054..85e6e1889 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Common/Extensions/ClaimsPrincipalExtensions.cs
@@ -1,4 +1,5 @@
-using Digdir.Domain.Dialogporten.Application.Externals.Presentation;
+using System.Diagnostics;
+using Digdir.Domain.Dialogporten.Application.Externals.Presentation;
using System.Security.Claims;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
@@ -175,30 +176,26 @@ public static bool TryGetOrganizationNumber(this Claim? consumerClaim, [NotNullW
return orgNumber is not null;
}
- public static bool TryGetAuthenticationLevel(this ClaimsPrincipal claimsPrincipal, [NotNullWhen(true)] out int? authenticationLevel)
+ public static int GetAuthenticationLevel(this ClaimsPrincipal claimsPrincipal)
{
if (claimsPrincipal.TryGetClaimValue(AltinnAuthLevelClaim, out var claimValue) && int.TryParse(claimValue, out var level))
{
- authenticationLevel = level;
- return true;
+ return level;
}
if (claimsPrincipal.TryGetClaimValue(IdportenAuthLevelClaim, out claimValue))
{
// The acr claim value is either "idporten-loa-substantial" (previously "Level3") or "idporten-loa-high" (previously "Level4")
// https://docs.digdir.no/docs/idporten/oidc/oidc_protocol_new_idporten#new-acr-values
- authenticationLevel = claimValue switch
+ return claimValue switch
{
- "idporten-loa-substantial" => 3,
- "idporten-loa-high" => 4,
- _ => null
+ Constants.IdportenLoaSubstantial => 3,
+ Constants.IdportenLoaHigh => 4,
+ _ => throw new ArgumentException("Unknown acr value")
};
-
- return authenticationLevel.HasValue;
}
- authenticationLevel = null;
- return false;
+ throw new UnreachableException("No authentication level claim found");
}
public static IEnumerable GetIdentifyingClaims(this IEnumerable claims)
diff --git a/src/Digdir.Domain.Dialogporten.Application/Common/IDialogTokenGenerator.cs b/src/Digdir.Domain.Dialogporten.Application/Common/IDialogTokenGenerator.cs
index b14d22402..15df63bdb 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Common/IDialogTokenGenerator.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Common/IDialogTokenGenerator.cs
@@ -50,10 +50,7 @@ public string GetDialogToken(DialogEntity dialog, DialogDetailsAuthorizationResu
{
{ DialogTokenClaimTypes.JwtId, Guid.NewGuid() },
{ DialogTokenClaimTypes.AuthenticatedParty, GetAuthenticatedParty() },
- { DialogTokenClaimTypes.AuthenticationLevel,
- claimsPrincipal.TryGetAuthenticationLevel(out var authenticationLevel)
- ? authenticationLevel.Value
- : 0 },
+ { DialogTokenClaimTypes.AuthenticationLevel, claimsPrincipal.GetAuthenticationLevel() },
{ DialogTokenClaimTypes.DialogParty, dialog.Party },
{ DialogTokenClaimTypes.ServiceResource, dialog.ServiceResource },
{ DialogTokenClaimTypes.DialogId, dialog.Id },
diff --git a/src/Digdir.Domain.Dialogporten.Application/Externals/AltinnAuthorization/IAltinnAuthorization.cs b/src/Digdir.Domain.Dialogporten.Application/Externals/AltinnAuthorization/IAltinnAuthorization.cs
index ae0503854..4090a4acc 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Externals/AltinnAuthorization/IAltinnAuthorization.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Externals/AltinnAuthorization/IAltinnAuthorization.cs
@@ -18,4 +18,7 @@ Task GetAuthorizedParties(IPartyIdentifier authenticate
CancellationToken cancellationToken = default);
Task HasListAuthorizationForDialog(DialogEntity dialog, CancellationToken cancellationToken);
+
+ bool UserHasRequiredAuthLevel(int minimumAuthenticationLevel);
+ Task UserHasRequiredAuthLevel(string serviceResource, CancellationToken cancellationToken);
}
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetActivityQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetActivityQuery.cs
index 245445b55..784055962 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetActivityQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Get/GetActivityQuery.cs
@@ -1,4 +1,5 @@
using AutoMapper;
+using Digdir.Domain.Dialogporten.Application.Common.Authorization;
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
@@ -17,7 +18,7 @@ public sealed class GetActivityQuery : IRequest
}
[GenerateOneOf]
-public sealed partial class GetActivityResult : OneOfBase;
+public sealed partial class GetActivityResult : OneOfBase;
internal sealed class GetActivityQueryHandler : IRequestHandler
{
@@ -67,6 +68,11 @@ public async Task Handle(GetActivityQuery request,
return new EntityDeleted(request.DialogId);
}
+ if (!await _altinnAuthorization.UserHasRequiredAuthLevel(dialog.ServiceResource, cancellationToken))
+ {
+ return new Forbidden(Constants.AltinnAuthLevelTooLow);
+ }
+
var activity = dialog.Activities.FirstOrDefault();
if (activity is null)
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Search/SearchActivityQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Search/SearchActivityQuery.cs
index 09b46bdc5..9f99fa285 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Search/SearchActivityQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogActivities/Queries/Search/SearchActivityQuery.cs
@@ -1,4 +1,5 @@
using AutoMapper;
+using Digdir.Domain.Dialogporten.Application.Common.Authorization;
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
@@ -15,7 +16,7 @@ public sealed class SearchActivityQuery : IRequest
}
[GenerateOneOf]
-public sealed partial class SearchActivityResult : OneOfBase, EntityNotFound, EntityDeleted>;
+public sealed partial class SearchActivityResult : OneOfBase, EntityNotFound, EntityDeleted, Forbidden>;
internal sealed class SearchActivityQueryHandler : IRequestHandler
{
@@ -61,6 +62,11 @@ public async Task Handle(SearchActivityQuery request, Canc
return new EntityDeleted(request.DialogId);
}
+ if (!await _altinnAuthorization.UserHasRequiredAuthLevel(dialog.ServiceResource, cancellationToken))
+ {
+ return new Forbidden(Constants.AltinnAuthLevelTooLow);
+ }
+
return _mapper.Map>(dialog.Activities);
}
}
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchLabelAssignmentLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchLabelAssignmentLogQuery.cs
index 5d229e1bc..d363c778c 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchLabelAssignmentLogQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogLabelAssignmentLog/Queries/Search/SearchLabelAssignmentLogQuery.cs
@@ -1,4 +1,5 @@
using AutoMapper;
+using Digdir.Domain.Dialogporten.Application.Common.Authorization;
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
@@ -15,7 +16,7 @@ public sealed class SearchLabelAssignmentLogQuery : IRequest, EntityNotFound, EntityDeleted>;
+public sealed partial class SearchLabelAssignmentLogResult : OneOfBase, EntityNotFound, EntityDeleted, Forbidden>;
internal sealed class SearchLabelAssignmentLogQueryHandler : IRequestHandler
{
@@ -55,6 +56,11 @@ public async Task Handle(SearchLabelAssignmentLo
return new EntityDeleted(request.DialogId);
}
+ if (!await _altinnAuthorization.UserHasRequiredAuthLevel(dialog.ServiceResource, cancellationToken))
+ {
+ return new Forbidden(Constants.AltinnAuthLevelTooLow);
+ }
+
return _mapper.Map>(dialog.DialogEndUserContext.LabelAssignmentLogs);
}
}
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetSeenLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetSeenLogQuery.cs
index e4c79666c..9fb99dae4 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetSeenLogQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Get/GetSeenLogQuery.cs
@@ -1,5 +1,6 @@
using AutoMapper;
using Digdir.Domain.Dialogporten.Application.Common;
+using Digdir.Domain.Dialogporten.Application.Common.Authorization;
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
@@ -77,6 +78,11 @@ public async Task Handle(GetSeenLogQuery request,
return new EntityNotFound(request.SeenLogId);
}
+ if (!await _altinnAuthorization.UserHasRequiredAuthLevel(dialog.ServiceResource, cancellationToken))
+ {
+ return new Forbidden(Constants.AltinnAuthLevelTooLow);
+ }
+
var dto = _mapper.Map(seenLog);
dto.IsCurrentEndUser = currentUserInformation.UserId.ExternalIdWithPrefix == seenLog.SeenBy.ActorId;
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchSeenLogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchSeenLogQuery.cs
index 8aa65d356..af8b9dc92 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchSeenLogQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogSeenLogs/Queries/Search/SearchSeenLogQuery.cs
@@ -7,6 +7,7 @@
using OneOf;
using Microsoft.EntityFrameworkCore;
using Digdir.Domain.Dialogporten.Application.Common;
+using Digdir.Domain.Dialogporten.Application.Common.Authorization;
namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.DialogSeenLogs.Queries.Search;
@@ -69,6 +70,11 @@ public async Task Handle(SearchSeenLogQuery request, Cancel
return new EntityDeleted(request.DialogId);
}
+ if (!await _altinnAuthorization.UserHasRequiredAuthLevel(dialog.ServiceResource, cancellationToken))
+ {
+ return new Forbidden(Constants.AltinnAuthLevelTooLow);
+ }
+
return dialog.SeenLog
.Select(x =>
{
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogTransmissions/Queries/Get/GetTransmissionQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogTransmissions/Queries/Get/GetTransmissionQuery.cs
index 0dd96e5e2..0f2b6f57a 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogTransmissions/Queries/Get/GetTransmissionQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogTransmissions/Queries/Get/GetTransmissionQuery.cs
@@ -18,7 +18,7 @@ public sealed class GetTransmissionQuery : IRequest
}
[GenerateOneOf]
-public sealed partial class GetTransmissionResult : OneOfBase;
+public sealed partial class GetTransmissionResult : OneOfBase;
internal sealed class GetTransmissionQueryHandler : IRequestHandler
{
@@ -72,6 +72,11 @@ public async Task Handle(GetTransmissionQuery request,
return new EntityDeleted(request.DialogId);
}
+ if (!await _altinnAuthorization.UserHasRequiredAuthLevel(dialog.ServiceResource, cancellationToken))
+ {
+ return new Forbidden(Constants.AltinnAuthLevelTooLow);
+ }
+
var transmission = dialog.Transmissions.FirstOrDefault();
if (transmission is null)
{
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogTransmissions/Queries/Search/SearchTransmissionQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogTransmissions/Queries/Search/SearchTransmissionQuery.cs
index 8e1675ade..6254ea3f2 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogTransmissions/Queries/Search/SearchTransmissionQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/DialogTransmissions/Queries/Search/SearchTransmissionQuery.cs
@@ -16,7 +16,7 @@ public sealed class SearchTransmissionQuery : IRequest
}
[GenerateOneOf]
-public sealed partial class SearchTransmissionResult : OneOfBase, EntityNotFound, EntityDeleted>;
+public sealed partial class SearchTransmissionResult : OneOfBase, EntityNotFound, EntityDeleted, Forbidden>;
internal sealed class SearchTransmissionQueryHandler : IRequestHandler
{
@@ -69,6 +69,11 @@ public async Task Handle(SearchTransmissionQuery reque
return new EntityDeleted(request.DialogId);
}
+ if (!await _altinnAuthorization.UserHasRequiredAuthLevel(dialog.ServiceResource, cancellationToken))
+ {
+ return new Forbidden(Constants.AltinnAuthLevelTooLow);
+ }
+
var dto = _mapper.Map>(dialog.Transmissions);
foreach (var transmission in dto)
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs
index 7c807edc6..10667ef3f 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Get/GetDialogQuery.cs
@@ -116,6 +116,11 @@ public async Task Handle(GetDialogQuery request, CancellationTo
return new EntityDeleted(request.DialogId);
}
+ if (!await _altinnAuthorization.UserHasRequiredAuthLevel(dialog.ServiceResource, cancellationToken))
+ {
+ return new Forbidden(Constants.AltinnAuthLevelTooLow);
+ }
+
// TODO: What if name lookup fails
// https://github.com/altinn/dialogporten/issues/387
var currentUserInformation = await _userRegistry.GetCurrentUserInformation(cancellationToken);
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/DialogContentExtensions.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/DialogContentExtensions.cs
new file mode 100644
index 000000000..357bf5ae9
--- /dev/null
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/DialogContentExtensions.cs
@@ -0,0 +1,23 @@
+using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities.Contents;
+
+namespace Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search;
+
+internal static class DialogContentExtensions
+{
+ public static void SetNonSensitiveContent(this List content)
+ {
+ var nonSensitiveTitle = content.FirstOrDefault(x => x.TypeId == DialogContentType.Values.NonSensitiveTitle);
+ if (nonSensitiveTitle is not null)
+ {
+ var title = content.First(x => x.TypeId == DialogContentType.Values.Title);
+ title.Value = nonSensitiveTitle.Value;
+ }
+
+ var nonSensitiveSummary = content.FirstOrDefault(x => x.TypeId == DialogContentType.Values.NonSensitiveSummary);
+ if (nonSensitiveSummary is not null)
+ {
+ var summary = content.First(x => x.TypeId == DialogContentType.Values.Summary);
+ summary.Value = nonSensitiveSummary.Value;
+ }
+ }
+}
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQuery.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQuery.cs
index 9dd51bcba..fb311c402 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQuery.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/EndUser/Dialogs/Queries/Search/SearchDialogQuery.cs
@@ -188,6 +188,27 @@ public async Task Handle(SearchDialogQuery request, Cancella
seenLog.IsCurrentEndUser = IdentifierMasker.GetMaybeMaskedIdentifier(_userRegistry.GetCurrentUserId().ExternalIdWithPrefix) == seenLog.SeenBy.ActorId;
}
+ var serviceResources = paginatedList.Items
+ .Select(x => x.ServiceResource)
+ .Distinct()
+ .ToList();
+
+ var resourcePolicyInformation = await _db.ResourcePolicyInformation
+ .Where(x => serviceResources.Contains(x.Resource))
+ .ToDictionaryAsync(x => x.Resource, x => x.MinimumAuthenticationLevel, cancellationToken);
+
+ foreach (var dialog in paginatedList.Items)
+ {
+ if (!resourcePolicyInformation.TryGetValue(dialog.ServiceResource, out var minimumAuthenticationLevel))
+ {
+ continue;
+ }
+
+ if (!_altinnAuthorization.UserHasRequiredAuthLevel(minimumAuthenticationLevel))
+ {
+ dialog.Content.SetNonSensitiveContent();
+ }
+ }
return paginatedList.ConvertTo(_mapper.Map);
}
}
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogDto.cs
index f0499d5c8..ecc45a708 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogDto.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Create/CreateDialogDto.cs
@@ -220,12 +220,24 @@ public sealed class ContentDto
///
public ContentValueDto Title { get; set; } = null!;
+ ///
+ /// An optional non-sensitive title of the dialog.
+ /// Used for search and list views if the user authorization does not meet the required eIDAS level
+ ///
+ public ContentValueDto? NonSensitiveTitle { get; set; }
+
///
/// A short summary of the dialog and its current state.
/// Supported media types: text/plain
///
public ContentValueDto Summary { get; set; } = null!;
+ ///
+ /// An optional non-sensitive summary of the dialog and its current state.
+ /// Used for search and list views if the user authorization does not meet the required eIDAS level
+ ///
+ public ContentValueDto? NonSensitiveSummary { get; set; }
+
///
/// Overridden sender name. If not supplied, assume "org" as the sender name. Must be text/plain if supplied.
/// Supported media types: text/plain
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDto.cs
index 09c953bf4..4824e92ed 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDto.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Commands/Update/UpdateDialogDto.cs
@@ -194,11 +194,23 @@ public sealed class ContentDto
///
public ContentValueDto Title { get; set; } = null!;
+ ///
+ /// An optional non-sensitive title of the dialog.
+ /// Used for search and list views if the user authorization does not meet the required eIDAS level
+ ///
+ public ContentValueDto? NonSensitiveTitle { get; set; }
+
///
/// A short summary of the dialog and its current state. Must be text/plain.
///
public ContentValueDto Summary { get; set; } = null!;
+ ///
+ /// An optional non-sensitive summary of the dialog and its current state.
+ /// Used for search and list views if the user authorization does not meet the required eIDAS level
+ ///
+ public ContentValueDto? NonSensitiveSummary { get; set; }
+
///
/// Overridden sender name. If not supplied, assume "org" as the sender name. Must be text/plain if supplied.
///
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/DialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/DialogDto.cs
index e95e98cbe..04eb20fa6 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/DialogDto.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Get/DialogDto.cs
@@ -282,11 +282,23 @@ public sealed class ContentDto
///
public ContentValueDto Title { get; set; } = null!;
+ ///
+ /// An optional non-sensitive title of the dialog.
+ /// Used for search and list views if the user authorization does not meet the required eIDAS level
+ ///
+ public ContentValueDto? NonSensitiveTitle { get; set; }
+
///
/// A short summary of the dialog and its current state.
///
public ContentValueDto Summary { get; set; } = null!;
+ ///
+ /// An optional non-sensitive summary of the dialog and its current state.
+ /// Used for search and list views if the user authorization does not meet the required eIDAS level
+ ///
+ public ContentValueDto? NonSensitiveSummary { get; set; }
+
///
/// Overridden sender name. If not supplied, assume "org" as the sender name.
///
diff --git a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/DialogDto.cs b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/DialogDto.cs
index 6a852a27c..e628e81f6 100644
--- a/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/DialogDto.cs
+++ b/src/Digdir.Domain.Dialogporten.Application/Features/V1/ServiceOwner/Dialogs/Queries/Search/DialogDto.cs
@@ -20,11 +20,23 @@ public sealed class ContentDto
///
public ContentValueDto Title { get; set; } = null!;
+ ///
+ /// An optional non-sensitive title of the dialog.
+ /// Used for search and list views if the user authorization does not meet the required eIDAS level
+ ///
+ public ContentValueDto? NonSensitiveTitle { get; set; }
+
///
/// A short summary of the dialog and its current state.
///
public ContentValueDto Summary { get; set; } = null!;
+ ///
+ /// An optional non-sensitive summary of the dialog and its current state.
+ /// Used for search and list views if the user authorization does not meet the required eIDAS level
+ ///
+ public ContentValueDto? NonSensitiveSummary { get; set; }
+
///
/// Overridden sender name. If not supplied, assume "org" as the sender name.
///
diff --git a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Contents/DialogContentType.cs b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Contents/DialogContentType.cs
index c5d63a5ed..5f763f34d 100644
--- a/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Contents/DialogContentType.cs
+++ b/src/Digdir.Domain.Dialogporten.Domain/Dialogs/Entities/Contents/DialogContentType.cs
@@ -13,7 +13,9 @@ public enum Values
Summary = 3,
AdditionalInfo = 4,
ExtendedStatus = 5,
- MainContentReference = 6
+ MainContentReference = 6,
+ NonSensitiveTitle = 7,
+ NonSensitiveSummary = 8,
}
public bool Required { get; private init; }
@@ -67,6 +69,20 @@ public enum Values
OutputInList = false,
AllowedMediaTypes = [MediaTypes.EmbeddableMarkdown]
},
+ Values.NonSensitiveTitle => new(id)
+ {
+ Required = false,
+ MaxLength = Constants.DefaultMaxStringLength,
+ OutputInList = true,
+ AllowedMediaTypes = [MediaTypes.PlainText]
+ },
+ Values.NonSensitiveSummary => new(id)
+ {
+ Required = false,
+ MaxLength = Constants.DefaultMaxStringLength,
+ OutputInList = true,
+ AllowedMediaTypes = [MediaTypes.PlainText]
+ },
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null)
};
diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs
index ac5cb36e4..99c037d40 100644
--- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs
+++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogById/ObjectTypes.cs
@@ -1,3 +1,4 @@
+using Digdir.Domain.Dialogporten.Application.Common.Authorization;
using Digdir.Domain.Dialogporten.GraphQL.EndUser.Common;
namespace Digdir.Domain.Dialogporten.GraphQL.EndUser.DialogById;
@@ -23,6 +24,11 @@ public sealed class DialogByIdForbidden : IDialogByIdError
public string Message { get; set; } = "Forbidden";
}
+public sealed class DialogByIdForbiddenAuthLevelToLow : IDialogByIdError
+{
+ public string Message { get; set; } = Constants.AltinnAuthLevelTooLow;
+}
+
public sealed class DialogByIdPayload
{
public Dialog? Dialog { get; set; }
diff --git a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs
index a56867716..99244cc56 100644
--- a/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs
+++ b/src/Digdir.Domain.Dialogporten.GraphQL/EndUser/DialogQueries.cs
@@ -1,5 +1,6 @@
using AppAny.HotChocolate.FluentValidation;
using AutoMapper;
+using Digdir.Domain.Dialogporten.Application.Common.Authorization;
using Digdir.Domain.Dialogporten.Application.Common.Pagination.Continuation;
using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Get;
using Digdir.Domain.Dialogporten.Application.Features.V1.EndUser.Dialogs.Queries.Search;
@@ -23,7 +24,21 @@ public async Task GetDialogById(
dialog => new DialogByIdPayload { Dialog = mapper.Map