diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AltinnAuthorizationClient.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AltinnAuthorizationClient.cs index b9891a82c..4ae95cb71 100644 --- a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AltinnAuthorizationClient.cs +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AltinnAuthorizationClient.cs @@ -171,71 +171,21 @@ private async Task PerformDialogSearchAuthoriza .ToDictionary(kv => kv.Key, kv => kv.Value), }; - await CollapseSubjectResources(dialogSearchAuthorizationResult, authorizedParties, request.ConstraintServiceResources, cancellationToken); + await AuthorizationHelper.CollapseSubjectResources( + dialogSearchAuthorizationResult, + authorizedParties, + request.ConstraintServiceResources, + GetAllSubjectResources, + cancellationToken); return dialogSearchAuthorizationResult; } - private async Task CollapseSubjectResources( - DialogSearchAuthorizationResult dialogSearchAuthorizationResult, - AuthorizedPartiesResult authorizedParties, - List constraintResources, - CancellationToken cancellationToken) - { - var authorizedPartiesWithRoles = authorizedParties.AuthorizedParties - .Where(p => p.AuthorizedRoles.Count != 0) - .ToList(); - - // Extract all unique subjects (authorized roles) from all parties. - var uniqueSubjects = authorizedPartiesWithRoles - .SelectMany(p => p.AuthorizedRoles) - .ToHashSet(); - - // Retrieve all subject resource mappings, considering any provided constraints - var subjectResources = await GetSubjectResources(uniqueSubjects, constraintResources, cancellationToken); - - // Group resources by subject for O(1) lookups when looping - var subjectToResources = subjectResources - .GroupBy(sr => sr.Subject) - .ToDictionary(g => g.Key, g => g.Select(sr => sr.Resource).ToHashSet()); - - foreach (var partyEntry in authorizedPartiesWithRoles) - { - // Get or create the resource list for the current party, so we can - // union the resource level accesses to those granted via roles - if (!dialogSearchAuthorizationResult.ResourcesByParties.TryGetValue(partyEntry.Party, out var resourceList)) - { - resourceList = new HashSet(); - dialogSearchAuthorizationResult.ResourcesByParties[partyEntry.Party] = resourceList; - } - - foreach (var subject in partyEntry.AuthorizedRoles) - { - if (subjectToResources.TryGetValue(subject, out var subjectResourceSet)) - { - resourceList.UnionWith(subjectResourceSet); - } - } - - // Remove the party if it has no authorized resources - if (resourceList.Count == 0) - { - dialogSearchAuthorizationResult.ResourcesByParties.Remove(partyEntry.Party); - } - } - } - - private async Task> GetSubjectResources(IEnumerable subjects, List resourceConstraints, CancellationToken cancellationToken) - { - // Fetch all subject resources from the database - var subjectResources = await _subjectResourcesCache.GetOrSetAsync(nameof(SubjectResource), async ct + private async Task> GetAllSubjectResources(CancellationToken cancellationToken) => + await _subjectResourcesCache.GetOrSetAsync(nameof(SubjectResource), async ct => await _dialogDbContext.SubjectResources.ToListAsync(cancellationToken: ct), token: cancellationToken); - // Return the subject resources matched with the subjects - return subjectResources.Where(x => subjects.Contains(x.Subject) && (resourceConstraints.Count == 0 || resourceConstraints.Contains(x.Resource))).ToList(); - } - private async Task PerformDialogDetailsAuthorization( DialogDetailsAuthorizationRequest request, CancellationToken cancellationToken) { diff --git a/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AuthorizationHelper.cs b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AuthorizationHelper.cs new file mode 100644 index 000000000..5eb395399 --- /dev/null +++ b/src/Digdir.Domain.Dialogporten.Infrastructure/Altinn/Authorization/AuthorizationHelper.cs @@ -0,0 +1,52 @@ +using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; +using Digdir.Domain.Dialogporten.Domain.SubjectResources; + +namespace Digdir.Domain.Dialogporten.Infrastructure.Altinn.Authorization; + +internal static class AuthorizationHelper +{ + public static async Task CollapseSubjectResources( + DialogSearchAuthorizationResult dialogSearchAuthorizationResult, + AuthorizedPartiesResult authorizedParties, + List constraintResources, + Func>> getAllSubjectResources, + CancellationToken cancellationToken) + { + var authorizedPartiesWithRoles = authorizedParties.AuthorizedParties + .Where(p => p.AuthorizedRoles.Count != 0) + .ToList(); + + var uniqueSubjects = authorizedPartiesWithRoles + .SelectMany(p => p.AuthorizedRoles) + .ToHashSet(); + + var subjectResources = (await getAllSubjectResources(cancellationToken)) + .Where(x => uniqueSubjects.Contains(x.Subject) && (constraintResources.Count == 0 || constraintResources.Contains(x.Resource))).ToList(); + + var subjectToResources = subjectResources + .GroupBy(sr => sr.Subject) + .ToDictionary(g => g.Key, g => g.Select(sr => sr.Resource).ToHashSet()); + + foreach (var partyEntry in authorizedPartiesWithRoles) + { + if (!dialogSearchAuthorizationResult.ResourcesByParties.TryGetValue(partyEntry.Party, out var resourceList)) + { + resourceList = new HashSet(); + dialogSearchAuthorizationResult.ResourcesByParties[partyEntry.Party] = resourceList; + } + + foreach (var subject in partyEntry.AuthorizedRoles) + { + if (subjectToResources.TryGetValue(subject, out var subjectResourceSet)) + { + resourceList.UnionWith(subjectResourceSet); + } + } + + if (resourceList.Count == 0) + { + dialogSearchAuthorizationResult.ResourcesByParties.Remove(partyEntry.Party); + } + } + } +} diff --git a/tests/Digdir.Domain.Dialogporten.Infrastructure.Unit.Tests/AuthorizationHelperTests.cs b/tests/Digdir.Domain.Dialogporten.Infrastructure.Unit.Tests/AuthorizationHelperTests.cs new file mode 100644 index 000000000..7559b83c3 --- /dev/null +++ b/tests/Digdir.Domain.Dialogporten.Infrastructure.Unit.Tests/AuthorizationHelperTests.cs @@ -0,0 +1,78 @@ +using Digdir.Domain.Dialogporten.Application.Externals.AltinnAuthorization; +using Digdir.Domain.Dialogporten.Domain.SubjectResources; +using Digdir.Domain.Dialogporten.Infrastructure.Altinn.Authorization; +using Xunit; + +namespace Digdir.Domain.Dialogporten.Infrastructure.Unit.Tests; + +public class AuthorizationHelperTests +{ + [Fact] + public async Task CollapseSubjectResources_ShouldCollapseCorrectly() + { + // Arrange + var dialogSearchAuthorizationResult = new DialogSearchAuthorizationResult + { + ResourcesByParties = new Dictionary>() + }; + var authorizedParties = new AuthorizedPartiesResult + { + AuthorizedParties = new List + { + new() + { + Party = "party1", + AuthorizedRoles = new List { "role1", "role2" } + }, + new() + { + Party = "party2", + AuthorizedRoles = new List { "role2" } + }, + new() + { + Party = "party3", + AuthorizedRoles = new List { "role3" } + } + } + }; + var constraintResources = new List { "resource1", "resource2", "resource4" }; + + // Simulate subject resources + var subjectResources = new List + { + new() { Subject = "role1", Resource = "resource1" }, + new() { Subject = "role1", Resource = "resource2" }, + new() { Subject = "role2", Resource = "resource2" }, + new() { Subject = "role2", Resource = "resource3" }, + new() { Subject = "role2", Resource = "resource4" }, + new() { Subject = "role3", Resource = "resource5" }, // Note: not in constraintResources + }; + + Task> GetSubjectResources(CancellationToken token) + { + return Task.FromResult(subjectResources); + } + + // Act + await AuthorizationHelper.CollapseSubjectResources( + dialogSearchAuthorizationResult, + authorizedParties, + constraintResources, + GetSubjectResources, + CancellationToken.None); + + // Assert + Assert.Equal(2, dialogSearchAuthorizationResult.ResourcesByParties.Count); + Assert.Contains("party1", dialogSearchAuthorizationResult.ResourcesByParties.Keys); + Assert.Contains("resource1", dialogSearchAuthorizationResult.ResourcesByParties["party1"]); + Assert.Contains("resource2", dialogSearchAuthorizationResult.ResourcesByParties["party1"]); + Assert.Contains("resource4", dialogSearchAuthorizationResult.ResourcesByParties["party1"]); + Assert.Equal(3, dialogSearchAuthorizationResult.ResourcesByParties["party1"].Count); + + Assert.Contains("party2", dialogSearchAuthorizationResult.ResourcesByParties.Keys); + Assert.Contains("resource2", dialogSearchAuthorizationResult.ResourcesByParties["party2"]); + Assert.Contains("resource4", dialogSearchAuthorizationResult.ResourcesByParties["party2"]); + Assert.Equal(2, dialogSearchAuthorizationResult.ResourcesByParties["party2"].Count); + } +}