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

Filter SO dialog search using fnr/dnr #333

Merged
merged 16 commits into from
Jan 22, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
Expand Up @@ -11,5 +11,6 @@ public Task<DialogDetailsAuthorizationResult> GetDialogDetailsAuthorization(
public Task<DialogSearchAuthorizationResult> GetAuthorizedResourcesForSearch(
List<string> constraintParties,
List<string> constraintServiceResources,
string? authEndUserPid = null,
CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task<GetDialogActivityResult> Handle(GetDialogActivityQuery request

var authorizationResult = await _altinnAuthorization.GetDialogDetailsAuthorization(
dialog,
cancellationToken);
cancellationToken: cancellationToken);

// If we cannot read the dialog at all, we don't allow access to any of the activity history
if (!authorizationResult.HasReadAccessToMainResource())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
var authorizedResources = await _altinnAuthorization.GetAuthorizedResourcesForSearch(
request.Party ?? new List<string>(),
request.ServiceResource ?? new List<string>(),
cancellationToken);
cancellationToken: cancellationToken);

if (authorizedResources.HasNoAuthorizations)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public sealed class SearchDialogDto
public Guid Id { get; set; }
public string Org { get; set; } = null!;
public string ServiceResource { get; set; } = null!;
public string? AuthEndUserPid { get; set; } = null!;
public string Party { get; set; } = null!;
public int? Progress { get; set; }
public string? ExtendedStatus { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Digdir.Domain.Dialogporten.Application.Common.Pagination.OrderOption;
using Digdir.Domain.Dialogporten.Application.Common.ReturnTypes;
using Digdir.Domain.Dialogporten.Application.Externals;
using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization;
using Digdir.Domain.Dialogporten.Domain.Dialogs.Entities;
using Digdir.Domain.Dialogporten.Domain.Localizations;
using MediatR;
Expand All @@ -29,6 +30,11 @@ public sealed class SearchDialogQuery : SortablePaginationParameter<SearchDialog
/// </summary>
public List<string>? Party { get; init; }

/// <summary>
/// Filter by national identity number
/// </summary>
public string? AuthEndUserPid { get; init; }

/// <summary>
/// Filter by one or more extended statuses
/// </summary>
Expand Down Expand Up @@ -116,25 +122,31 @@ internal sealed class SearchDialogQueryHandler : IRequestHandler<SearchDialogQue
private readonly IDialogDbContext _db;
private readonly IMapper _mapper;
private readonly IUserService _userService;
private readonly IAltinnAuthorization _altinnAuthorization;

public SearchDialogQueryHandler(
IDialogDbContext db,
IMapper mapper,
IUserService userService)
IUserService userService,
IAltinnAuthorization altinnAuthorization)
{
_db = db ?? throw new ArgumentNullException(nameof(db));
_mapper = mapper ?? throw new ArgumentNullException(nameof(mapper));
_userService = userService ?? throw new ArgumentNullException(nameof(userService));
_altinnAuthorization = altinnAuthorization;
}

public async Task<SearchDialogResult> Handle(SearchDialogQuery request, CancellationToken cancellationToken)
{
var resourceIds = await _userService.GetCurrentUserResourceIds(cancellationToken);
var searchExpression = Expressions.LocalizedSearchExpression(request.Search, request.SearchCultureCode);
return await _db.Dialogs
.WhereIf(!request.ServiceResource.IsNullOrEmpty(), x => request.ServiceResource!.Contains(x.ServiceResource))

var query = _db.Dialogs
.WhereIf(!request.ServiceResource.IsNullOrEmpty(),
x => request.ServiceResource!.Contains(x.ServiceResource))
.WhereIf(!request.Party.IsNullOrEmpty(), x => request.Party!.Contains(x.Party))
.WhereIf(!request.ExtendedStatus.IsNullOrEmpty(), x => x.ExtendedStatus != null && request.ExtendedStatus!.Contains(x.ExtendedStatus))
.WhereIf(!request.ExtendedStatus.IsNullOrEmpty(),
x => x.ExtendedStatus != null && request.ExtendedStatus!.Contains(x.ExtendedStatus))
.WhereIf(!string.IsNullOrWhiteSpace(request.ExternalReference),
x => x.ExternalReference != null && request.ExternalReference == x.ExternalReference)
.WhereIf(!request.Status.IsNullOrEmpty(), x => request.Status!.Contains(x.StatusId))
Expand All @@ -150,7 +162,19 @@ public async Task<SearchDialogResult> Handle(SearchDialogQuery request, Cancella
x.Content.Any(x => x.Value.Localizations.AsQueryable().Any(searchExpression)) ||
x.SearchTags.Any(x => EF.Functions.ILike(x.Value, request.Search!))
)
.Where(x => resourceIds.Contains(x.ServiceResource))
.Where(x => resourceIds.Contains(x.ServiceResource));

if (request.AuthEndUserPid is not null)
{
var authorizedResources = await _altinnAuthorization.GetAuthorizedResourcesForSearch(
request.Party ?? new List<string>(),
request.ServiceResource ?? new List<string>(),
request.AuthEndUserPid,
cancellationToken);
query = query.WhereUserIsAuthorizedFor(authorizedResources);
}

return await query
.ProjectTo<SearchDialogDto>(_mapper.ConfigurationProvider)
.ToPaginatedListAsync(request, cancellationToken: cancellationToken);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using Digdir.Domain.Dialogporten.Application.Common.Pagination;
using Digdir.Domain.Dialogporten.Application.Common.Extensions.Enumerables;
using Digdir.Domain.Dialogporten.Application.Common.Numbers;
using Digdir.Domain.Dialogporten.Application.Common.Pagination;
using Digdir.Domain.Dialogporten.Domain.Localizations;
using FluentValidation;

Expand All @@ -17,6 +19,13 @@ public SearchDialogQueryValidator()
.Must(x => x is null || Localization.IsValidCultureCode(x))
.WithMessage("'{PropertyName}' must be a valid culture code.");

RuleFor(x => x)
.Must(x => SocialSecurityNumber.IsValid(x.AuthEndUserPid))
.WithMessage($"'{nameof(SearchDialogQuery.AuthEndUserPid)}' must be a valid national identity number.")
.Must(x => !x.ServiceResource.IsNullOrEmpty() || !x.Party.IsNullOrEmpty())
.WithMessage($"Either {nameof(SearchDialogQuery.ServiceResource)} or {nameof(SearchDialogQuery.Party)} must be specified.")
knuhau marked this conversation as resolved.
Show resolved Hide resolved
.When(x => x.AuthEndUserPid is not null);

RuleFor(x => x.ServiceResource!.Count)
.LessThanOrEqualTo(20)
.When(x => x.ServiceResource is not null);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Net;
using System.Security.Claims;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
Expand All @@ -14,6 +15,8 @@ namespace Digdir.Domain.Dialogporten.Infrastructure.Altinn.Authorization;

internal sealed class AltinnAuthorizationClient : IAltinnAuthorization
{
private const string AttributeIdSsn = "urn:altinn:ssn";

private readonly HttpClient _httpClient;
private readonly IUser _user;
private readonly IDialogDbContext _db;
Expand All @@ -35,7 +38,7 @@ public async Task<DialogDetailsAuthorizationResult> GetDialogDetailsAuthorizatio
CancellationToken cancellationToken = default) =>
await PerformDialogDetailsAuthorization(new DialogDetailsAuthorizationRequest
{
ClaimsPrincipal = _user.GetPrincipal(),
Claims = _user.GetPrincipal().Claims.ToList(),
ServiceResource = dialogEntity.ServiceResource,
DialogId = dialogEntity.Id,
Party = dialogEntity.Party,
Expand All @@ -45,13 +48,17 @@ await PerformDialogDetailsAuthorization(new DialogDetailsAuthorizationRequest
public async Task<DialogSearchAuthorizationResult> GetAuthorizedResourcesForSearch(
List<string> constraintParties,
List<string> serviceResources,
CancellationToken cancellationToken = default) =>
await PerformNonScalableDialogSearchAuthorization(new DialogSearchAuthorizationRequest
string? authEndUserPid,
CancellationToken cancellationToken = default)
{
var claims = GetOrCreateClaimsBasedOnEndUserPid(authEndUserPid);
return await PerformNonScalableDialogSearchAuthorization(new DialogSearchAuthorizationRequest
{
ClaimsPrincipal = _user.GetPrincipal(),
Claims = claims,
ConstraintParties = constraintParties,
ConstraintServiceResources = serviceResources
}, cancellationToken);
}

private async Task<DialogSearchAuthorizationResult> PerformNonScalableDialogSearchAuthorization(DialogSearchAuthorizationRequest request, CancellationToken cancellationToken)
{
Expand Down Expand Up @@ -97,6 +104,20 @@ private async Task<DialogDetailsAuthorizationResult> PerformDialogDetailsAuthori
return DecisionRequestHelper.CreateDialogDetailsResponse(request.AltinnActions, xamlJsonResponse);
}

private List<Claim> GetOrCreateClaimsBasedOnEndUserPid(string? endUserPid)
{
List<Claim> claims = new();
if (endUserPid is not null)
{
claims.Add(new Claim(AttributeIdSsn, endUserPid));
}
else
{
claims.AddRange(_user.GetPrincipal().Claims);
}
return claims;
}

private static readonly JsonSerializerOptions _serializerOptions = new()
{
PropertyNameCaseInsensitive = true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ internal static class DecisionRequestHelper

public static XacmlJsonRequestRoot CreateDialogDetailsRequest(DialogDetailsAuthorizationRequest request)
{
var accessSubject = CreateAccessSubjectCategory(request.ClaimsPrincipal.Claims);
var accessSubject = CreateAccessSubjectCategory(request.Claims);
var actions = CreateActionCategories(request.AltinnActions, out var actionIdByName);
var resources = CreateResourceCategories(request.ServiceResource, request.DialogId, request.Party, request.AltinnActions, out var resourceIdByName);

Expand Down Expand Up @@ -223,7 +223,7 @@ public static XacmlJsonRequestRoot CreateDialogSearchRequest(DialogSearchAuthori
new (Constants.ReadAction, Constants.MainResource)
};

var accessSubject = CreateAccessSubjectCategory(request.ClaimsPrincipal.Claims);
var accessSubject = CreateAccessSubjectCategory(request.Claims);
var actions = CreateActionCategories(requestActions, out _);
var resources = CreateResourceCategoriesForSearch(request.ConstraintServiceResources, request.ConstraintParties);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace Digdir.Domain.Dialogporten.Infrastructure.Altinn.Authorization;

public sealed class DialogDetailsAuthorizationRequest
{
public required ClaimsPrincipal ClaimsPrincipal { get; init; }
public required List<Claim> Claims { get; init; }
public required string ServiceResource { get; init; }
public required Guid DialogId { get; init; }
public required string Party { get; init; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ namespace Digdir.Domain.Dialogporten.Infrastructure.Altinn.Authorization;

public sealed class DialogSearchAuthorizationRequest
{
public required ClaimsPrincipal ClaimsPrincipal { get; init; }
public required List<Claim> Claims { get; init; }
public List<string> ConstraintParties { get; set; } = new();
public List<string> ConstraintServiceResources { get; set; } = new();
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public Task<DialogDetailsAuthorizationResult> GetDialogDetailsAuthorization(Dial
// Just allow everything
Task.FromResult(new DialogDetailsAuthorizationResult { AuthorizedAltinnActions = dialogEntity.GetAltinnActions() });

public async Task<DialogSearchAuthorizationResult> GetAuthorizedResourcesForSearch(List<string> constraintParties, List<string> serviceResources,
public async Task<DialogSearchAuthorizationResult> GetAuthorizedResourcesForSearch(List<string> constraintParties, List<string> serviceResources, string? authEndUserPid,
CancellationToken cancellationToken = default)
{
// Allow all resources for all parties
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ private static DialogDetailsAuthorizationRequest CreateDialogDetailsAuthorizatio
allClaims.AddRange(principalClaims);
return new DialogDetailsAuthorizationRequest
{
ClaimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(allClaims, "test")),
Claims = allClaims,
ServiceResource = "urn:altinn:resource:some-service",
DialogId = Guid.NewGuid(),

Expand Down