From 51462d55a783adcf538d642ec5bca2a14d5717d1 Mon Sep 17 00:00:00 2001 From: Roar Mjelde <36594318+Ceredron@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:44:22 +0100 Subject: [PATCH] Instance authorization (#531) * Clean up code for getting organization id and remove prefix where necessary * We do not need to check IsRecipient/IsSender explicitly because the PDP verifies whether the caller is allowed to receive/send on behalf of recipient/sender * Re-write all authorization code to use pre-defined "simple" methods to hide authorization logic/complexity. Make the "raw" function private. * Remove now unused dependencies * Add hotfix to main as a fix is not in priority * Bug: Purge needs CORS support to be used from Arbeidsflate * As we no longer use OnBehalfOf directly in the logic, we no longer need it anywhere aside from GetCorrespondences * Had to change logic in GetCorrespondenceOverview/Details because someone may have access both as a sender and recipient, but calling the API as a recipient, hence fetched should still be set. * Suggestions from CodeRabbit * Fix tests with authorization override * Also need override for legacy authentication * Smart coderabbit * Return org number without prefix --- Altinn.Correspondence.sln | 6 ++ .../Helpers/AuthorizationOverride.cs | 90 +++++++++++++++++++ .../Helpers/CustomWebApplicationFactory.cs | 6 +- .../Helpers/MockPolicyEvaluator.cs | 2 +- .../Attachment/AttachmentDeletionTests.cs | 4 +- .../Attachment/AttachmentDownloadTests.cs | 2 +- .../AttachmentInitializationTests.cs | 4 +- .../Attachment/AttachmentRetrievalTests.cs | 13 +-- .../Attachment/AttachmentUploadTests.cs | 2 +- .../Attachment/Base/AttachmentTestBase.cs | 5 +- .../Base/CorrespondenceTestBase.cs | 12 ++- .../CorrespondenceAttachmentTests.cs | 6 +- .../CorrespondenceNotificationTests.cs | 2 +- .../CorrespondenceRetrievalTests.cs | 34 +++---- .../CorrespondenceSearchTests.cs | 6 +- .../Legacy/Base/LegacyTestBase.cs | 2 +- .../Legacy/LegacyDeletionTests.cs | 2 +- .../Legacy/LegacyRetrievalTests.cs | 2 +- .../Legacy/LegacySearchTests.cs | 2 +- .../TestingFeature/DialogportenTests.cs | 2 +- .../MalwareScanResultControllerTests.cs | 2 +- .../TestingFeature/NotificationTests.cs | 2 +- .../Auth/CascadeAuthenticationHandler.cs | 2 +- .../Auth/DependencyInjection.cs | 2 +- .../Controllers/AttachmentController.cs | 7 +- .../Controllers/CorrespondenceController.cs | 27 ++---- .../LegacyCorrespondenceController.cs | 2 +- .../Controllers/MigrationController.cs | 2 +- src/Altinn.Correspondence.API/Program.cs | 2 +- .../CancelNotificationHandler.cs | 5 +- .../DownloadAttachmentHandler.cs | 16 +--- ...DownloadCorrespondenceAttachmentHandler.cs | 24 +---- ...DownloadCorrespondenceAttachmentRequest.cs | 1 - .../GetAttachmentDetailsHandler.cs | 15 ++-- .../GetAttachmentOverviewHandler.cs | 16 ++-- .../GetCorrespondencesHandler.cs | 17 ++-- .../GetCorrespondenceDetailsHandler.cs | 37 +++----- .../GetCorrespondenceDetailsRequest.cs | 1 - .../GetCorrespondenceOverviewHandler.cs | 35 ++------ .../GetCorrespondenceOverviewRequest.cs | 1 - .../Helpers/UserClaimsHelper.cs | 84 +---------------- .../InitializeAttachmentHandler.cs | 16 ++-- .../InitializeCorrespondencesHandler.cs | 16 ++-- .../MigrateCorrespondenceRequest.cs | 1 - .../PurgeAttachment/PurgeAttachmentHandler.cs | 15 ++-- .../PurgeCorrespondenceHandler.cs | 44 ++++----- .../PurgeCorrespondenceRequest.cs | 1 - ...LegacyUpdateCorrespondenceStatusHandler.cs | 8 +- .../UpdateCorrespondenceStatusHandler.cs | 21 +---- .../UpdateCorrespondenceStatusRequest.cs | 1 - .../UploadAttachmentHandler.cs | 16 ++-- .../Altinn.Correspondence.Common.csproj | 4 + .../Constants}/AuthorizationConstants.cs | 3 +- .../Helpers/ClaimsPrincipalExtensions.cs | 47 ++++++++++ .../Models/SystemUserAuthorizationDetails.cs | 33 +++++++ .../Helpers/Models/TokenConsumer.cs | 12 +++ .../Helpers/StringExtensions.cs | 16 ++-- .../IAltinnAuthorizationService.cs | 8 +- .../AltinnAuthorizationDevService.cs | 22 ++++- .../AltinnAuthorizationService.cs | 76 +++++++++++----- .../Authorization/AltinnTokenXacmlMapper..cs | 2 +- .../Mappers/DialogTokenXacmlMapper.cs | 26 +++--- 62 files changed, 487 insertions(+), 405 deletions(-) create mode 100644 Test/Altinn.Correspondence.Tests/Helpers/AuthorizationOverride.cs rename src/{Altinn.Correspondence.Application/Configuration => Altinn.Correspondence.Common/Constants}/AuthorizationConstants.cs (91%) create mode 100644 src/Altinn.Correspondence.Common/Helpers/ClaimsPrincipalExtensions.cs create mode 100644 src/Altinn.Correspondence.Common/Helpers/Models/SystemUserAuthorizationDetails.cs create mode 100644 src/Altinn.Correspondence.Common/Helpers/Models/TokenConsumer.cs diff --git a/Altinn.Correspondence.sln b/Altinn.Correspondence.sln index 5ba0770b..a945714f 100644 --- a/Altinn.Correspondence.sln +++ b/Altinn.Correspondence.sln @@ -17,6 +17,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Correspondence.Tests EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Correspondence.Integrations", "src\Altinn.Correspondence.Integrations\Altinn.Correspondence.Integrations.csproj", "{5C88BAAD-CF82-4D7A-8FB6-879605EF4E03}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Altinn.Correspondence.Common", "src\Altinn.Correspondence.Common\Altinn.Correspondence.Common.csproj", "{AC3CF570-F9E1-4616-A56B-FF94C89CBC0B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -47,6 +49,10 @@ Global {5C88BAAD-CF82-4D7A-8FB6-879605EF4E03}.Debug|Any CPU.Build.0 = Debug|Any CPU {5C88BAAD-CF82-4D7A-8FB6-879605EF4E03}.Release|Any CPU.ActiveCfg = Release|Any CPU {5C88BAAD-CF82-4D7A-8FB6-879605EF4E03}.Release|Any CPU.Build.0 = Release|Any CPU + {AC3CF570-F9E1-4616-A56B-FF94C89CBC0B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AC3CF570-F9E1-4616-A56B-FF94C89CBC0B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AC3CF570-F9E1-4616-A56B-FF94C89CBC0B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AC3CF570-F9E1-4616-A56B-FF94C89CBC0B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Test/Altinn.Correspondence.Tests/Helpers/AuthorizationOverride.cs b/Test/Altinn.Correspondence.Tests/Helpers/AuthorizationOverride.cs new file mode 100644 index 00000000..61450e74 --- /dev/null +++ b/Test/Altinn.Correspondence.Tests/Helpers/AuthorizationOverride.cs @@ -0,0 +1,90 @@ +using Altinn.Correspondence.Core.Models.Entities; +using Altinn.Correspondence.Core.Models.Enums; +using Altinn.Correspondence.Core.Repositories; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using System.Security.Claims; + +namespace Altinn.Correspondence.Tests.Helpers; + +public static class AuthorizationOverride +{ + public static IServiceCollection OverrideAuthorization(this IServiceCollection services) + { + var altinnAuthorizationService = new Mock(); + altinnAuthorizationService + .Setup(x => x.CheckAccessAsRecipient( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((ClaimsPrincipal? user, CorrespondenceEntity corr, CancellationToken token) => { + return Task.FromResult(NotRecipient(user)); + }); + + altinnAuthorizationService + .Setup(x => x.CheckAccessAsSender( + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((ClaimsPrincipal? user, CorrespondenceEntity corr, CancellationToken token) => { + return Task.FromResult(NotSender(user)); + }); + + altinnAuthorizationService + .Setup(x => x.CheckAccessAsSender( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((ClaimsPrincipal? user, string resourceId, string sender, string? instance, CancellationToken token) => { + return Task.FromResult(NotSender(user)); + }); + + altinnAuthorizationService + .Setup(x => x.CheckAccessAsAny( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns((ClaimsPrincipal? user, string resource, string party, CancellationToken token) => { + return Task.FromResult(!NotRecipient(user) || !NotSender(user)); + }); + + altinnAuthorizationService + .Setup(x => x.CheckMigrationAccess( + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns((string resourceId, IEnumerable levels, CancellationToken token) => + { + return Task.FromResult(true); + }); + + altinnAuthorizationService + .Setup(x => x.CheckUserAccessAndGetMinimumAuthLevel( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny(), + It.IsAny())) + .Returns((ClaimsPrincipal? user, string ssn, string resourceId, List rights, string recipientOrgNo, CancellationToken token) => + { + return Task.FromResult(3); + }); + + return services.AddScoped(_ => altinnAuthorizationService.Object); + } + + private static bool NotSender(ClaimsPrincipal? user) + { + return !user?.Claims.Any(c => + c.Type == "notSender") ?? true; + } + private static bool NotRecipient(ClaimsPrincipal? user) + { + return !user?.Claims.Any(c => + c.Type == "notRecipient") ?? true; + } +} diff --git a/Test/Altinn.Correspondence.Tests/Helpers/CustomWebApplicationFactory.cs b/Test/Altinn.Correspondence.Tests/Helpers/CustomWebApplicationFactory.cs index 0035b7e6..6ff99bbd 100644 --- a/Test/Altinn.Correspondence.Tests/Helpers/CustomWebApplicationFactory.cs +++ b/Test/Altinn.Correspondence.Tests/Helpers/CustomWebApplicationFactory.cs @@ -1,7 +1,6 @@ using Altinn.Correspondence.Core.Repositories; using Altinn.Correspondence.Core.Services; using Altinn.Correspondence.Integrations.Altinn.AccessManagement; -using Altinn.Correspondence.Integrations.Altinn.Authorization; using Altinn.Correspondence.Integrations.Altinn.Events; using Altinn.Correspondence.Integrations.Altinn.Notifications; using Altinn.Correspondence.Integrations.Altinn.Register; @@ -25,6 +24,7 @@ public class CustomWebApplicationFactory : WebApplicationFactory { internal Mock? HangfireBackgroundJobClient; + protected override void ConfigureWebHost( IWebHostBuilder builder) { @@ -46,7 +46,7 @@ protected override void ConfigureWebHost( services.AddScoped(); services.AddScoped(); services.AddScoped(); - services.AddScoped(); + services.OverrideAuthorization(); services.AddScoped(); services.AddScoped(); var resourceRightsService = new Mock(); @@ -54,6 +54,8 @@ protected override void ConfigureWebHost( services.AddScoped(_ => resourceRightsService.Object); }); } + + public HttpClient CreateClientWithAddedClaims(params (string type, string value)[] claims) { var defaultClaims = new List diff --git a/Test/Altinn.Correspondence.Tests/Helpers/MockPolicyEvaluator.cs b/Test/Altinn.Correspondence.Tests/Helpers/MockPolicyEvaluator.cs index 1c99c2e0..d1f57227 100644 --- a/Test/Altinn.Correspondence.Tests/Helpers/MockPolicyEvaluator.cs +++ b/Test/Altinn.Correspondence.Tests/Helpers/MockPolicyEvaluator.cs @@ -1,5 +1,5 @@ using Altinn.Common.PEP.Authorization; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization.Infrastructure; diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentDeletionTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentDeletionTests.cs index a7254a71..039bc010 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentDeletionTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentDeletionTests.cs @@ -100,13 +100,13 @@ public async Task DeleteAttachment_AsRecipient_ReturnsForbidden() Assert.Equal(HttpStatusCode.Forbidden, deleteResponse.StatusCode); } [Fact] - public async Task DeleteAttachment_AsWrongSender_ReturnsBadRequest() + public async Task DeleteAttachment_AsWrongSender_ReturnsUnauthorized() { // Arrange var attachmentId = await AttachmentHelper.GetPublishedAttachment(_senderClient, _responseSerializerOptions); var deleteResponse = await _wrongSenderClient.DeleteAsync($"correspondence/api/v1/attachment/{attachmentId}"); // Assert failure before correspondence is created - Assert.Equal(HttpStatusCode.BadRequest, deleteResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, deleteResponse.StatusCode); } } } diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentDownloadTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentDownloadTests.cs index 4fbfadbe..83d3d330 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentDownloadTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentDownloadTests.cs @@ -37,7 +37,7 @@ public async Task DownloadAttachment_AsWrongSender_ReturnsBadRequest() var data = await downloadResponse.Content.ReadFromJsonAsync(); // Assert - Assert.Equal(HttpStatusCode.BadRequest, downloadResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, downloadResponse.StatusCode); Assert.NotNull(data?.Title); } } diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentInitializationTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentInitializationTests.cs index db5538a9..2e28a760 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentInitializationTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentInitializationTests.cs @@ -31,11 +31,11 @@ public async Task InitializeAttachment_AsRecipient_ReturnsForbidden() } [Fact] - public async Task InitializeAttachment_As_Different_Sender_As_Token_ReturnsBadRequest() + public async Task InitializeAttachment_As_Different_Sender_As_Token_ReturnsUnauthorized() { var attachment = new AttachmentBuilder().CreateAttachment().Build(); var initializeAttachmentResponse = await _wrongSenderClient.PostAsJsonAsync("correspondence/api/v1/attachment", attachment); - Assert.Equal(HttpStatusCode.BadRequest, initializeAttachmentResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, initializeAttachmentResponse.StatusCode); } [Fact] diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentRetrievalTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentRetrievalTests.cs index 0a94c0b0..e7b51e89 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentRetrievalTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentRetrievalTests.cs @@ -1,11 +1,6 @@ using Altinn.Correspondence.Tests.Helpers; using Altinn.Correspondence.Tests.TestingController.Attachment.Base; -using System; -using System.Collections.Generic; -using System.Linq; using System.Net; -using System.Text; -using System.Threading.Tasks; namespace Altinn.Correspondence.Tests.TestingController.Attachment { @@ -31,11 +26,11 @@ public async Task GetAttachmentOverview_AsRecipient_ReturnsForbidden() } [Fact] - public async Task GetAttachmentOverview_As_Different_sender_ReturnsBadRequest() + public async Task GetAttachmentOverview_As_Different_sender_ReturnsUnauthorized() { var attachmentId = await AttachmentHelper.GetInitializedAttachment(_senderClient, _responseSerializerOptions); var getAttachmentOverviewResponse = await _wrongSenderClient.GetAsync($"correspondence/api/v1/attachment/{attachmentId}"); - Assert.Equal(HttpStatusCode.BadRequest, getAttachmentOverviewResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, getAttachmentOverviewResponse.StatusCode); } [Fact] @@ -54,11 +49,11 @@ public async Task GetAttachmentDetails_AsRecipient_ReturnsForbidden() } [Fact] - public async Task GetAttachmentDetails_As_Different_sender_ReturnsBadRequest() + public async Task GetAttachmentDetails_As_Different_sender_ReturnsUnauthorized() { var attachmentId = await AttachmentHelper.GetInitializedAttachment(_senderClient, _responseSerializerOptions); var getAttachmentOverviewResponse = await _wrongSenderClient.GetAsync($"correspondence/api/v1/attachment/{attachmentId}/details"); - Assert.Equal(HttpStatusCode.BadRequest, getAttachmentOverviewResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, getAttachmentOverviewResponse.StatusCode); } } } diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentUploadTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentUploadTests.cs index 5102d785..935acb1c 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentUploadTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/AttachmentUploadTests.cs @@ -39,7 +39,7 @@ public async Task UploadAttachmentData_WrongSender_Fails() { var attachmentId = await AttachmentHelper.GetInitializedAttachment(_senderClient, _responseSerializerOptions); var uploadResponse = await AttachmentHelper.UploadAttachment(attachmentId, _wrongSenderClient); - Assert.Equal(HttpStatusCode.BadRequest, uploadResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, uploadResponse.StatusCode); } [Fact] diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/Base/AttachmentTestBase.cs b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/Base/AttachmentTestBase.cs index 01863906..b55e87df 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Attachment/Base/AttachmentTestBase.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Attachment/Base/AttachmentTestBase.cs @@ -1,4 +1,4 @@ -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using System.Text.Json; namespace Altinn.Correspondence.Tests.TestingController.Attachment.Base @@ -18,8 +18,7 @@ public AttachmentTestBase(CustomWebApplicationFactory factory) _recipientClient = factory.CreateClientWithAddedClaims(("scope", AuthorizationConstants.RecipientScope)); _wrongSenderClient = factory.CreateClientWithAddedClaims( ("scope", AuthorizationConstants.SenderScope), - ("client_orgnr", "123456789"), - ("consumer", "{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"0192:123456789\"}") + ("notSender", "true") ); _responseSerializerOptions = new JsonSerializerOptions() { diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/Base/CorrespondenceTestBase.cs b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/Base/CorrespondenceTestBase.cs index 7b306910..4f89d390 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/Base/CorrespondenceTestBase.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/Base/CorrespondenceTestBase.cs @@ -1,4 +1,4 @@ -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using System; using System.Collections.Generic; using System.Linq; @@ -18,8 +18,14 @@ public class CorrespondenceTestBase : IClassFixture public CorrespondenceTestBase(CustomWebApplicationFactory factory) { _factory = factory; - _senderClient = _factory.CreateClientWithAddedClaims(("scope", AuthorizationConstants.SenderScope)); - _recipientClient = _factory.CreateClientWithAddedClaims(("scope", AuthorizationConstants.RecipientScope)); + _senderClient = _factory.CreateClientWithAddedClaims( + ("notRecipient", "true"), + ("scope", AuthorizationConstants.SenderScope) + ); + _recipientClient = _factory.CreateClientWithAddedClaims( + ("notSender", "true"), + ("scope", AuthorizationConstants.RecipientScope) + ); _responseSerializerOptions = new JsonSerializerOptions(new JsonSerializerOptions() { PropertyNameCaseInsensitive = true diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceAttachmentTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceAttachmentTests.cs index 79d4e179..18173f4d 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceAttachmentTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceAttachmentTests.cs @@ -190,7 +190,7 @@ public async Task DownloadCorrespondenceAttachment_AsRecipient_Succeeds() } [Fact] - public async Task DownloadCorrespondenceAttachment_WhenNotARecipient_Returns404() + public async Task DownloadCorrespondenceAttachment_WhenNotARecipient_Returns401() { // Arrange var attachmentId = await AttachmentHelper.GetPublishedAttachment(_senderClient, _responseSerializerOptions); @@ -203,10 +203,10 @@ public async Task DownloadCorrespondenceAttachment_WhenNotARecipient_Returns404( // Act var initializeCorrespondenceResponse = await _senderClient.PostAsJsonAsync("correspondence/api/v1/correspondence", payload, _responseSerializerOptions); var response = await initializeCorrespondenceResponse.Content.ReadFromJsonAsync(_responseSerializerOptions); - var downloadResponse = await _recipientClient.GetAsync($"correspondence/api/v1/correspondence/{response?.Correspondences.FirstOrDefault().CorrespondenceId}/attachment/{attachmentId}/download"); + var downloadResponse = await _senderClient.GetAsync($"correspondence/api/v1/correspondence/{response?.Correspondences.FirstOrDefault().CorrespondenceId}/attachment/{attachmentId}/download"); // Assert - Assert.Equal(HttpStatusCode.NotFound, downloadResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, downloadResponse.StatusCode); } [Fact] diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceNotificationTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceNotificationTests.cs index f8979369..26894c4a 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceNotificationTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceNotificationTests.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.API.Models.Enums; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Core.Models.Notifications; using Altinn.Correspondence.Core.Repositories; using Altinn.Correspondence.Tests.Factories; diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceRetrievalTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceRetrievalTests.cs index 3c997862..6d3a8ce2 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceRetrievalTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceRetrievalTests.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.API.Models.Enums; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Tests.Factories; using Altinn.Correspondence.Tests.TestingController.Correspondence.Base; using System.Net; @@ -30,23 +30,23 @@ public async Task GetCorrespondenceOverview() } [Fact] - public async Task GetCorrespondenceOverview_WhenNotSenderOrRecipient_Returns404() + public async Task GetCorrespondenceOverview_WhenNotSenderOrRecipient_Returns401() { // Arrange var payload = new CorrespondenceBuilder().CreateCorrespondence().Build(); var initializeCorrespondenceResponse = await _senderClient.PostAsJsonAsync("correspondence/api/v1/correspondence", payload); initializeCorrespondenceResponse.EnsureSuccessStatusCode(); var correspondence = await initializeCorrespondenceResponse.Content.ReadFromJsonAsync(_responseSerializerOptions); - var invalidSenderClient = _factory.CreateClientWithAddedClaims(("consumer", "{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"0192:123456789\"}"), ("scope", AuthorizationConstants.SenderScope)); - var invalidRecipientClient = _factory.CreateClientWithAddedClaims(("consumer", "{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"0192:123456789\"}"), ("scope", AuthorizationConstants.RecipientScope)); + var invalidClient = _factory.CreateClientWithAddedClaims( + ("notSender", "true"), + ("notRecipient", "true"), + ("scope", AuthorizationConstants.SenderScope)); // Act - var invalidRecipientResponse = await invalidRecipientClient.GetAsync($"correspondence/api/v1/correspondence/{correspondence?.Correspondences.FirstOrDefault().CorrespondenceId}"); - var invalidSenderResponse = await invalidSenderClient.GetAsync($"correspondence/api/v1/correspondence/{correspondence?.Correspondences.FirstOrDefault().CorrespondenceId}"); + var invalidSenderResponse = await invalidClient.GetAsync($"correspondence/api/v1/correspondence/{correspondence?.Correspondences.FirstOrDefault().CorrespondenceId}"); // Assert - Assert.Equal(HttpStatusCode.NotFound, invalidRecipientResponse.StatusCode); - Assert.Equal(HttpStatusCode.NotFound, invalidSenderResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, invalidSenderResponse.StatusCode); } [Fact] @@ -127,23 +127,23 @@ public async Task GetCorrespondenceDetails() } [Fact] - public async Task GetCorrespondenceDetails_WhenNotSenderOrRecipient_Returns404() + public async Task GetCorrespondenceDetails_WhenNotSenderOrRecipient_Returns401() { // Arrange var payload = new CorrespondenceBuilder().CreateCorrespondence().Build(); var initializeCorrespondenceResponse = await _senderClient.PostAsJsonAsync("correspondence/api/v1/correspondence", payload); initializeCorrespondenceResponse.EnsureSuccessStatusCode(); var correspondence = await initializeCorrespondenceResponse.Content.ReadFromJsonAsync(_responseSerializerOptions); - var invalidSenderClient = _factory.CreateClientWithAddedClaims(("consumer", "{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"0192:123456789\"}"), ("scope", AuthorizationConstants.SenderScope)); - var invalidRecipientClient = _factory.CreateClientWithAddedClaims(("consumer", "{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"0192:123456789\"}"), ("scope", AuthorizationConstants.RecipientScope)); + var invalidClient = _factory.CreateClientWithAddedClaims( + ("notSender", "true"), + ("notRecipient", "true"), + ("scope", AuthorizationConstants.SenderScope)); // Act - var invalidRecipientResponse = await invalidRecipientClient.GetAsync($"correspondence/api/v1/correspondence/{correspondence?.Correspondences.FirstOrDefault().CorrespondenceId}/details"); - var invalidSenderResponse = await invalidSenderClient.GetAsync($"correspondence/api/v1/correspondence/{correspondence?.Correspondences.FirstOrDefault().CorrespondenceId}/details"); + var invalidResponse = await invalidClient.GetAsync($"correspondence/api/v1/correspondence/{correspondence?.Correspondences.FirstOrDefault().CorrespondenceId}/details"); // Assert - Assert.Equal(HttpStatusCode.NotFound, invalidRecipientResponse.StatusCode); - Assert.Equal(HttpStatusCode.NotFound, invalidSenderResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, invalidResponse.StatusCode); } [Fact] @@ -242,12 +242,14 @@ public async Task PersonalCorrespondence_NotRetrievableWithWrongRecipientToken() var wrongRecipientClient = _factory.CreateClientWithAddedClaims( ("pid", "wrong-personal-id"), + ("notRecipient", "true"), + ("notSender", "true"), ("scope", AuthorizationConstants.RecipientScope)); var getCorrespondenceResponse = await wrongRecipientClient.GetAsync($"correspondence/api/v1/correspondence/{correspondenceId}"); // Assert - Assert.Equal(HttpStatusCode.NotFound, getCorrespondenceResponse.StatusCode); + Assert.Equal(HttpStatusCode.Unauthorized, getCorrespondenceResponse.StatusCode); } } } diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceSearchTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceSearchTests.cs index 3c4291aa..75ebc39f 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceSearchTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Correspondence/CorrespondenceSearchTests.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.API.Models.Enums; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Application.GetCorrespondences; using Altinn.Correspondence.Tests.Factories; using Altinn.Correspondence.Tests.Helpers; @@ -88,6 +88,7 @@ public async Task GetCorrespondences_When_IsSender_Or_IsRecipient_Specified_Retu .Build(); var senderClient = _factory.CreateClientWithAddedClaims( ("consumer", $"{{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"{senderId}\"}}"), + ("notRecipient", "true"), ("scope", AuthorizationConstants.SenderScope) ); var initResponse = await senderClient.PostAsJsonAsync("correspondence/api/v1/correspondence", senderPayload); @@ -102,6 +103,7 @@ public async Task GetCorrespondences_When_IsSender_Or_IsRecipient_Specified_Retu .Build(); var externalClient = _factory.CreateClientWithAddedClaims( ("consumer", $"{{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"{externalId}\"}}"), + ("notRecipient", "true"), ("scope", AuthorizationConstants.SenderScope) ); var externalInitResponse = await externalClient.PostAsJsonAsync("correspondence/api/v1/correspondence", externalPayload); @@ -110,6 +112,7 @@ public async Task GetCorrespondences_When_IsSender_Or_IsRecipient_Specified_Retu // Create recipient client to retrieve correspondences with correct ID var recipientIdClient = _factory.CreateClientWithAddedClaims( ("consumer", $"{{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"{recipientId}\"}}"), + ("notSender", "true"), ("scope", AuthorizationConstants.RecipientScope) ); @@ -168,6 +171,7 @@ public async Task GetCorrespondences_WithoutStatusSpecified_AsReceiver_ReturnsAl var recipientId = "0192:000000000"; var recipientClient = _factory.CreateClientWithAddedClaims( ("consumer", $"{{\"authority\":\"iso6523-actorid-upis\",\"ID\":\"{recipientId}\"}}"), + ("notSender", "true"), ("scope", AuthorizationConstants.RecipientScope) ); diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Legacy/Base/LegacyTestBase.cs b/Test/Altinn.Correspondence.Tests/TestingController/Legacy/Base/LegacyTestBase.cs index aa7cf219..e66c35d2 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Legacy/Base/LegacyTestBase.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Legacy/Base/LegacyTestBase.cs @@ -1,4 +1,4 @@ -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using System.Text.Json; using System.Text.Json.Serialization; diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacyDeletionTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacyDeletionTests.cs index b3638615..6a93594b 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacyDeletionTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacyDeletionTests.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.API.Models.Enums; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Core.Models.Entities; using Altinn.Correspondence.Core.Services; using Altinn.Correspondence.Tests.Factories; diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacyRetrievalTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacyRetrievalTests.cs index 3330ddea..cd0ca530 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacyRetrievalTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacyRetrievalTests.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models.Enums; using Altinn.Correspondence.API.Models; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Application.GetCorrespondenceHistory; using Altinn.Correspondence.Core.Models.Enums; using Altinn.Correspondence.Tests.Factories; diff --git a/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacySearchTests.cs b/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacySearchTests.cs index a158ad4e..13b7a8d3 100644 --- a/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacySearchTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingController/Legacy/LegacySearchTests.cs @@ -1,5 +1,5 @@ using Altinn.Correspondence.API.Models.Enums; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Application.GetCorrespondences; using Altinn.Correspondence.Tests.Factories; using Altinn.Correspondence.Tests.Helpers; diff --git a/Test/Altinn.Correspondence.Tests/TestingFeature/DialogportenTests.cs b/Test/Altinn.Correspondence.Tests/TestingFeature/DialogportenTests.cs index 908d5d5b..bfd3b9f1 100644 --- a/Test/Altinn.Correspondence.Tests/TestingFeature/DialogportenTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingFeature/DialogportenTests.cs @@ -1,5 +1,5 @@ using Altinn.Correspondence.API.Models; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Core.Options; using Altinn.Correspondence.Core.Repositories; using Altinn.Correspondence.Core.Services; diff --git a/Test/Altinn.Correspondence.Tests/TestingFeature/MalwareScanResultControllerTests.cs b/Test/Altinn.Correspondence.Tests/TestingFeature/MalwareScanResultControllerTests.cs index 5eb6de01..27681b46 100644 --- a/Test/Altinn.Correspondence.Tests/TestingFeature/MalwareScanResultControllerTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingFeature/MalwareScanResultControllerTests.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.API.Models.Enums; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Tests.Helpers; using System.Net.Http.Json; using System.Text; diff --git a/Test/Altinn.Correspondence.Tests/TestingFeature/NotificationTests.cs b/Test/Altinn.Correspondence.Tests/TestingFeature/NotificationTests.cs index 43aab030..526a3947 100644 --- a/Test/Altinn.Correspondence.Tests/TestingFeature/NotificationTests.cs +++ b/Test/Altinn.Correspondence.Tests/TestingFeature/NotificationTests.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.Application.CheckNotification; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Tests.Factories; using System.Net.Http.Json; using System.Text.Json; diff --git a/src/Altinn.Correspondence.API/Auth/CascadeAuthenticationHandler.cs b/src/Altinn.Correspondence.API/Auth/CascadeAuthenticationHandler.cs index 83034f84..88069e0d 100644 --- a/src/Altinn.Correspondence.API/Auth/CascadeAuthenticationHandler.cs +++ b/src/Altinn.Correspondence.API/Auth/CascadeAuthenticationHandler.cs @@ -1,5 +1,5 @@ using Altinn.Correspondence.API.Auth; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Core.Options; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; diff --git a/src/Altinn.Correspondence.API/Auth/DependencyInjection.cs b/src/Altinn.Correspondence.API/Auth/DependencyInjection.cs index 2f790e0c..79e8182a 100644 --- a/src/Altinn.Correspondence.API/Auth/DependencyInjection.cs +++ b/src/Altinn.Correspondence.API/Auth/DependencyInjection.cs @@ -1,6 +1,6 @@ using Altinn.Common.PEP.Authorization; using Altinn.Correspondence.API.Helpers; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Core.Options; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.JwtBearer; diff --git a/src/Altinn.Correspondence.API/Controllers/AttachmentController.cs b/src/Altinn.Correspondence.API/Controllers/AttachmentController.cs index 23809587..f442356a 100644 --- a/src/Altinn.Correspondence.API/Controllers/AttachmentController.cs +++ b/src/Altinn.Correspondence.API/Controllers/AttachmentController.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.Application; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Application.DownloadAttachment; using Altinn.Correspondence.Application.GetAttachmentDetails; using Altinn.Correspondence.Application.GetAttachmentOverview; @@ -29,7 +29,10 @@ public class AttachmentController(ILogger logger) : Co [Consumes("application/json")] [Produces("application/json")] [Authorize(Policy = AuthorizationConstants.Sender)] - public async Task> InitializeAttachment(InitializeAttachmentExt InitializeAttachmentExt, [FromServices] InitializeAttachmentHandler handler, CancellationToken cancellationToken) + public async Task> InitializeAttachment( + InitializeAttachmentExt InitializeAttachmentExt, + [FromServices] InitializeAttachmentHandler handler, + CancellationToken cancellationToken) { var commandRequest = InitializeAttachmentMapper.MapToRequest(InitializeAttachmentExt); var commandResult = await handler.Process(commandRequest, HttpContext.User, cancellationToken); diff --git a/src/Altinn.Correspondence.API/Controllers/CorrespondenceController.cs b/src/Altinn.Correspondence.API/Controllers/CorrespondenceController.cs index cf577870..70b81e97 100644 --- a/src/Altinn.Correspondence.API/Controllers/CorrespondenceController.cs +++ b/src/Altinn.Correspondence.API/Controllers/CorrespondenceController.cs @@ -2,7 +2,7 @@ using Altinn.Correspondence.API.Models.Enums; using Altinn.Correspondence.Application; using Altinn.Correspondence.Application.CheckNotification; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Application.DownloadCorrespondenceAttachment; using Altinn.Correspondence.Application.GetCorrespondenceDetails; using Altinn.Correspondence.Application.GetCorrespondenceOverview; @@ -103,7 +103,6 @@ public async Task> UploadCorr [Authorize(Policy = AuthorizationConstants.SenderOrRecipient, AuthenticationSchemes = AuthorizationConstants.AltinnTokenOrDialogportenScheme)] public async Task> GetCorrespondenceOverview( Guid correspondenceId, - [FromQuery] string? onBehalfOf, [FromServices] GetCorrespondenceOverviewHandler handler, CancellationToken cancellationToken) { @@ -111,8 +110,7 @@ public async Task> GetCorrespondenceOver var commandResult = await handler.Process(new GetCorrespondenceOverviewRequest() { - CorrespondenceId = correspondenceId, - OnBehalfOf = onBehalfOf + CorrespondenceId = correspondenceId }, HttpContext.User, cancellationToken); return commandResult.Match( @@ -134,7 +132,6 @@ public async Task> GetCorrespondenceOver [Authorize(Policy = AuthorizationConstants.SenderOrRecipient, AuthenticationSchemes = AuthorizationConstants.AltinnTokenOrDialogportenScheme)] public async Task> GetCorrespondenceDetails( Guid correspondenceId, - [FromQuery] string? onBehalfOf, [FromServices] GetCorrespondenceDetailsHandler handler, CancellationToken cancellationToken) { @@ -143,7 +140,6 @@ public async Task> GetCorrespondenceDetai var commandResult = await handler.Process(new GetCorrespondenceDetailsRequest() { CorrespondenceId = correspondenceId, - OnBehalfOf = onBehalfOf }, HttpContext.User, cancellationToken); return commandResult.Match( @@ -239,7 +235,6 @@ public async Task> GetCorrespondences( [Route("{correspondenceId}/markasread")] public async Task MarkAsRead( Guid correspondenceId, - [FromQuery] string? onBehalfOf, [FromServices] UpdateCorrespondenceStatusHandler handler, CancellationToken cancellationToken) { @@ -248,8 +243,7 @@ public async Task MarkAsRead( var commandResult = await handler.Process(new UpdateCorrespondenceStatusRequest { CorrespondenceId = correspondenceId, - Status = CorrespondenceStatus.Read, - OnBehalfOf = onBehalfOf, + Status = CorrespondenceStatus.Read }, HttpContext.User, cancellationToken); return commandResult.Match( @@ -271,7 +265,6 @@ public async Task MarkAsRead( [Route("{correspondenceId}/confirm")] public async Task Confirm( Guid correspondenceId, - [FromQuery] string? onBehalfOf, [FromServices] UpdateCorrespondenceStatusHandler handler, CancellationToken cancellationToken) { @@ -280,8 +273,7 @@ public async Task Confirm( var commandResult = await handler.Process(new UpdateCorrespondenceStatusRequest { CorrespondenceId = correspondenceId, - Status = CorrespondenceStatus.Confirmed, - OnBehalfOf = onBehalfOf, + Status = CorrespondenceStatus.Confirmed }, HttpContext.User, cancellationToken); return commandResult.Match( @@ -303,7 +295,6 @@ public async Task Confirm( [Route("{correspondenceId}/archive")] public async Task Archive( Guid correspondenceId, - [FromQuery] string? onBehalfOf, [FromServices] UpdateCorrespondenceStatusHandler handler, CancellationToken cancellationToken) { @@ -313,7 +304,6 @@ public async Task Archive( { CorrespondenceId = correspondenceId, Status = CorrespondenceStatus.Archived, - OnBehalfOf = onBehalfOf, }, HttpContext.User, cancellationToken); return commandResult.Match( @@ -332,9 +322,9 @@ public async Task Archive( [HttpDelete] [Route("{correspondenceId}/purge")] [Authorize(Policy = AuthorizationConstants.SenderOrRecipient)] + [EnableCors(AuthorizationConstants.ArbeidsflateCors)] public async Task Purge( Guid correspondenceId, - [FromQuery] string? onBehalfOf, [FromServices] PurgeCorrespondenceHandler handler, CancellationToken cancellationToken) { @@ -342,8 +332,7 @@ public async Task Purge( var commandResult = await handler.Process(new PurgeCorrespondenceRequest() { - CorrespondenceId = correspondenceId, - OnBehalfOf = onBehalfOf + CorrespondenceId = correspondenceId }, HttpContext.User, cancellationToken); return commandResult.Match( @@ -364,15 +353,13 @@ public async Task Purge( public async Task DownloadCorrespondenceAttachmentData( Guid correspondenceId, Guid attachmentId, - [FromQuery] string? onBehalfOf, [FromServices] DownloadCorrespondenceAttachmentHandler handler, CancellationToken cancellationToken) { var commandResult = await handler.Process(new DownloadCorrespondenceAttachmentRequest() { CorrespondenceId = correspondenceId, - AttachmentId = attachmentId, - OnBehalfOf = onBehalfOf + AttachmentId = attachmentId }, HttpContext.User, cancellationToken); return commandResult.Match( result => File(result.Stream, "application/octet-stream", result.FileName), diff --git a/src/Altinn.Correspondence.API/Controllers/LegacyCorrespondenceController.cs b/src/Altinn.Correspondence.API/Controllers/LegacyCorrespondenceController.cs index 328df8d5..7b33b191 100644 --- a/src/Altinn.Correspondence.API/Controllers/LegacyCorrespondenceController.cs +++ b/src/Altinn.Correspondence.API/Controllers/LegacyCorrespondenceController.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.Application; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Application.DownloadCorrespondenceAttachment; using Altinn.Correspondence.Application.GetCorrespondenceHistory; using Altinn.Correspondence.Application.GetCorrespondenceOverview; diff --git a/src/Altinn.Correspondence.API/Controllers/MigrationController.cs b/src/Altinn.Correspondence.API/Controllers/MigrationController.cs index ed06b019..45607a77 100644 --- a/src/Altinn.Correspondence.API/Controllers/MigrationController.cs +++ b/src/Altinn.Correspondence.API/Controllers/MigrationController.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Models; using Altinn.Correspondence.Application; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Application.GetAttachmentOverview; using Altinn.Correspondence.Application.InitializeAttachment; using Altinn.Correspondence.Application.InitializeCorrespondence; diff --git a/src/Altinn.Correspondence.API/Program.cs b/src/Altinn.Correspondence.API/Program.cs index 14608857..9b8d0716 100644 --- a/src/Altinn.Correspondence.API/Program.cs +++ b/src/Altinn.Correspondence.API/Program.cs @@ -1,6 +1,6 @@ using Altinn.Correspondence.API.Auth; using Altinn.Correspondence.Application; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Constants; using Altinn.Correspondence.Core.Options; using Altinn.Correspondence.Integrations; using Altinn.Correspondence.Integrations.Hangfire; diff --git a/src/Altinn.Correspondence.Application/CancelNotification/CancelNotificationHandler.cs b/src/Altinn.Correspondence.Application/CancelNotification/CancelNotificationHandler.cs index 81b50983..ff9164ee 100644 --- a/src/Altinn.Correspondence.Application/CancelNotification/CancelNotificationHandler.cs +++ b/src/Altinn.Correspondence.Application/CancelNotification/CancelNotificationHandler.cs @@ -1,6 +1,3 @@ -using System.Runtime.CompilerServices; -using System.Security.Claims; -using System.Threading; using Altinn.Correspondence.Core.Models.Entities; using Altinn.Correspondence.Core.Repositories; using Altinn.Correspondence.Core.Services; @@ -10,6 +7,8 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Slack.Webhooks; +using System.Runtime.CompilerServices; +using System.Security.Claims; [assembly: InternalsVisibleTo("Altinn.Correspondence.Tests")] namespace Altinn.Correspondence.Application.CancelNotification diff --git a/src/Altinn.Correspondence.Application/DownloadAttachment/DownloadAttachmentHandler.cs b/src/Altinn.Correspondence.Application/DownloadAttachment/DownloadAttachmentHandler.cs index a8d3553c..f2e6eedd 100644 --- a/src/Altinn.Correspondence.Application/DownloadAttachment/DownloadAttachmentHandler.cs +++ b/src/Altinn.Correspondence.Application/DownloadAttachment/DownloadAttachmentHandler.cs @@ -1,16 +1,14 @@ -using System.Security.Claims; -using Altinn.Correspondence.Application.Helpers; -using Altinn.Correspondence.Core.Models.Enums; +using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Repositories; using OneOf; +using System.Security.Claims; namespace Altinn.Correspondence.Application.DownloadAttachment; public class DownloadAttachmentHandler( IAltinnAuthorizationService altinnAuthorizationService, IStorageRepository storageRepository, - IAttachmentRepository attachmentRepository, - UserClaimsHelper userClaimsHelper) : IHandler + IAttachmentRepository attachmentRepository) : IHandler { public async Task> Process(DownloadAttachmentRequest request, ClaimsPrincipal? user, CancellationToken cancellationToken) { @@ -19,17 +17,11 @@ public async Task> Process(DownloadAttachmentRequest reques { return Errors.AttachmentNotFound; } - var hasAccess = await altinnAuthorizationService.CheckUserAccess(user, attachment.ResourceId, attachment.Sender, attachment.Id.ToString(), new List { ResourceAccessLevel.Write }, cancellationToken); + var hasAccess = await altinnAuthorizationService.CheckAccessAsSender(user, attachment.ResourceId, attachment.Sender.WithoutPrefix(), null, cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; } - - if (!userClaimsHelper.IsSender(attachment.Sender)) - { - return Errors.InvalidSender; - } - var attachmentStream = await storageRepository.DownloadAttachment(attachment.Id, cancellationToken); return attachmentStream; } diff --git a/src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentHandler.cs b/src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentHandler.cs index 0076d306..c17a5cf6 100644 --- a/src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentHandler.cs +++ b/src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentHandler.cs @@ -1,6 +1,4 @@ using Altinn.Correspondence.Application.Helpers; -using Altinn.Correspondence.Common.Helpers; -using Altinn.Correspondence.Core.Models.Enums; using Altinn.Correspondence.Core.Repositories; using Altinn.Correspondence.Core.Services; using Altinn.Correspondence.Core.Services.Enums; @@ -15,7 +13,6 @@ public class DownloadCorrespondenceAttachmentHandler( IStorageRepository storageRepository, IAttachmentRepository attachmentRepository, ICorrespondenceRepository correspondenceRepository, - UserClaimsHelper userClaimsHelper, IBackgroundJobClient backgroundJobClient) : IHandler { @@ -31,35 +28,18 @@ public async Task> Proces { return Errors.AttachmentNotFound; } - string? onBehalfOf = request.OnBehalfOf; - bool isOnBehalfOfRecipient = false; - if (!string.IsNullOrEmpty(onBehalfOf)) - { - isOnBehalfOfRecipient = correspondence.Recipient.GetOrgNumberWithoutPrefix() == onBehalfOf.GetOrgNumberWithoutPrefix(); - } - var hasAccess = await altinnAuthorizationService.CheckUserAccess( - user, - correspondence.ResourceId, - request.OnBehalfOf ?? correspondence.Recipient, - correspondence.Id.ToString(), - [ResourceAccessLevel.Read], - cancellationToken); + var hasAccess = await altinnAuthorizationService.CheckAccessAsRecipient(user, correspondence, cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; } - var isRecipient = userClaimsHelper.IsRecipient(correspondence.Recipient) || isOnBehalfOfRecipient; - if (!isRecipient) - { - return Errors.CorrespondenceNotFound; - } var latestStatus = correspondence.GetHighestStatus(); if (!latestStatus.Status.IsAvailableForRecipient()) { return Errors.CorrespondenceNotFound; } var attachmentStream = await storageRepository.DownloadAttachment(attachment.Id, cancellationToken); - backgroundJobClient.Enqueue((dialogportenService) => dialogportenService.CreateInformationActivity(request.CorrespondenceId, Core.Services.Enums.DialogportenActorType.Recipient, DialogportenTextType.DownloadStarted, attachment.FileName ?? attachment.Name)); + backgroundJobClient.Enqueue((dialogportenService) => dialogportenService.CreateInformationActivity(request.CorrespondenceId, DialogportenActorType.Recipient, DialogportenTextType.DownloadStarted, attachment.FileName ?? attachment.Name)); return new DownloadCorrespondenceAttachmentResponse(){ FileName = attachment.FileName, Stream = attachmentStream diff --git a/src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentRequest.cs b/src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentRequest.cs index eab156ac..8750c43c 100644 --- a/src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentRequest.cs +++ b/src/Altinn.Correspondence.Application/DownloadCorrespondenceAttachment/DownloadCorrespondenceAttachmentRequest.cs @@ -4,5 +4,4 @@ public class DownloadCorrespondenceAttachmentRequest { public required Guid CorrespondenceId { get; set; } public required Guid AttachmentId { get; set; } - public string? OnBehalfOf { get; set; } } diff --git a/src/Altinn.Correspondence.Application/GetAttachmentDetails/GetAttachmentDetailsHandler.cs b/src/Altinn.Correspondence.Application/GetAttachmentDetails/GetAttachmentDetailsHandler.cs index 2be0bea5..9cedea09 100644 --- a/src/Altinn.Correspondence.Application/GetAttachmentDetails/GetAttachmentDetailsHandler.cs +++ b/src/Altinn.Correspondence.Application/GetAttachmentDetails/GetAttachmentDetailsHandler.cs @@ -1,4 +1,5 @@ using Altinn.Correspondence.Application.Helpers; +using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Models.Enums; using Altinn.Correspondence.Core.Repositories; using OneOf; @@ -9,8 +10,7 @@ namespace Altinn.Correspondence.Application.GetAttachmentDetails; public class GetAttachmentDetailsHandler( IAttachmentRepository attachmentRepository, ICorrespondenceRepository correspondenceRepository, - IAltinnAuthorizationService altinnAuthorizationService, - UserClaimsHelper userClaimsHelper) : IHandler + IAltinnAuthorizationService altinnAuthorizationService) : IHandler { public async Task> Process(Guid attachmentId, ClaimsPrincipal? user, CancellationToken cancellationToken) @@ -20,15 +20,16 @@ public async Task> Process(Guid attac { return Errors.AttachmentNotFound; } - var hasAccess = await altinnAuthorizationService.CheckUserAccess(user, attachment.ResourceId, attachment.Sender, attachment.Id.ToString(), new List { ResourceAccessLevel.Write }, cancellationToken); + var hasAccess = await altinnAuthorizationService.CheckAccessAsSender( + user, + attachment.ResourceId, + attachment.Sender.WithoutPrefix(), + attachment.Id.ToString(), + cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; } - if (!userClaimsHelper.IsSender(attachment.Sender)) - { - return Errors.InvalidSender; - } var correspondenceIds = await correspondenceRepository.GetCorrespondenceIdsByAttachmentId(attachmentId, cancellationToken); var attachmentStatus = attachment.GetLatestStatus(); diff --git a/src/Altinn.Correspondence.Application/GetAttachmentOverview/GetAttachmentOverviewHandler.cs b/src/Altinn.Correspondence.Application/GetAttachmentOverview/GetAttachmentOverviewHandler.cs index 96250404..adba3bd8 100644 --- a/src/Altinn.Correspondence.Application/GetAttachmentOverview/GetAttachmentOverviewHandler.cs +++ b/src/Altinn.Correspondence.Application/GetAttachmentOverview/GetAttachmentOverviewHandler.cs @@ -1,5 +1,5 @@ using Altinn.Correspondence.Application.Helpers; -using Altinn.Correspondence.Core.Models.Enums; +using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Repositories; using OneOf; using System.Security.Claims; @@ -9,8 +9,7 @@ namespace Altinn.Correspondence.Application.GetAttachmentOverview; public class GetAttachmentOverviewHandler( IAltinnAuthorizationService altinnAuthorizationService, IAttachmentRepository attachmentRepository, - ICorrespondenceRepository correspondenceRepository, - UserClaimsHelper userClaimsHelper) : IHandler + ICorrespondenceRepository correspondenceRepository) : IHandler { public async Task> Process(Guid attachmentId, ClaimsPrincipal? user, CancellationToken cancellationToken) { @@ -19,15 +18,16 @@ public async Task> Process(Guid atta { return Errors.AttachmentNotFound; } - var hasAccess = await altinnAuthorizationService.CheckUserAccess(user, attachment.ResourceId, attachment.Sender, attachment.Id.ToString(), new List { ResourceAccessLevel.Write }, cancellationToken); + var hasAccess = await altinnAuthorizationService.CheckAccessAsSender( + user, + attachment.ResourceId, + attachment.Sender.WithoutPrefix(), + attachment.Id.ToString(), + cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; } - if (!userClaimsHelper.IsSender(attachment.Sender)) - { - return Errors.InvalidSender; - } var attachmentStatus = attachment.GetLatestStatus(); var correspondenceIds = await correspondenceRepository.GetCorrespondenceIdsByAttachmentId(attachmentId, cancellationToken); diff --git a/src/Altinn.Correspondence.Application/GetCorespondences/GetCorrespondencesHandler.cs b/src/Altinn.Correspondence.Application/GetCorespondences/GetCorrespondencesHandler.cs index b66d70d7..6c1cb932 100644 --- a/src/Altinn.Correspondence.Application/GetCorespondences/GetCorrespondencesHandler.cs +++ b/src/Altinn.Correspondence.Application/GetCorespondences/GetCorrespondencesHandler.cs @@ -1,6 +1,6 @@ -using Altinn.Correspondence.Application.Helpers; -using Altinn.Correspondence.Core.Models.Enums; +using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Repositories; +using Microsoft.AspNetCore.Http; using OneOf; using System.Security.Claims; @@ -9,7 +9,7 @@ namespace Altinn.Correspondence.Application.GetCorrespondences; public class GetCorrespondencesHandler( IAltinnAuthorizationService altinnAuthorizationService, ICorrespondenceRepository correspondenceRepository, - UserClaimsHelper userClaimsHelper) : IHandler + IHttpContextAccessor httpContextAccessor) : IHandler { public async Task> Process(GetCorrespondencesRequest request, ClaimsPrincipal? user, CancellationToken cancellationToken) { @@ -24,27 +24,24 @@ public async Task> Process(GetCorrespon { return Errors.InvalidDateRange; } - string? onBehalfOf = request.OnBehalfOf; if (onBehalfOf is null) { - onBehalfOf = userClaimsHelper.GetUserID(); + onBehalfOf = "0192:" + httpContextAccessor.HttpContext?.User.GetCallerOrganizationId(); } if (onBehalfOf is null) { return Errors.CouldNotDetermineCaller; } - var hasAccess = await altinnAuthorizationService.CheckUserAccess( + var hasAccess = await altinnAuthorizationService.CheckAccessAsAny( user, request.ResourceId, - onBehalfOf, - null, - [ResourceAccessLevel.Read, ResourceAccessLevel.Write], + onBehalfOf.WithoutPrefix(), cancellationToken); - if (!hasAccess) { return Errors.NoAccessToResource; } + // TODO: Add implementation to retrieve instances delegated to the user var correspondences = await correspondenceRepository.GetCorrespondences( request.ResourceId, diff --git a/src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsHandler.cs b/src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsHandler.cs index caca2ef2..3be27973 100644 --- a/src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsHandler.cs +++ b/src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsHandler.cs @@ -17,7 +17,6 @@ public class GetCorrespondenceDetailsHandler( IAltinnRegisterService altinnRegisterService, ICorrespondenceRepository correspondenceRepository, ICorrespondenceStatusRepository correspondenceStatusRepository, - UserClaimsHelper userClaimsHelper, ILogger logger) : IHandler { public async Task> Process(GetCorrespondenceDetailsRequest request, ClaimsPrincipal? user, CancellationToken cancellationToken) @@ -27,40 +26,24 @@ public async Task> Process(GetCor { return Errors.CorrespondenceNotFound; } - string? onBehalfOf = request.OnBehalfOf; - bool isOnBehalfOfRecipient = false; - bool isOnBehalfOfSender = false; - - if (!string.IsNullOrEmpty(onBehalfOf)) - { - isOnBehalfOfRecipient = correspondence.Recipient.GetOrgNumberWithoutPrefix() == onBehalfOf.GetOrgNumberWithoutPrefix(); - isOnBehalfOfSender = correspondence.Sender.GetOrgNumberWithoutPrefix() == onBehalfOf.GetOrgNumberWithoutPrefix(); - } - var hasAccess = await altinnAuthorizationService.CheckUserAccess( + var hasAccessAsRecipient = await altinnAuthorizationService.CheckAccessAsRecipient( + user, + correspondence, + cancellationToken); + var hasAccessAsSender = await altinnAuthorizationService.CheckAccessAsSender( user, - correspondence.ResourceId, - request.OnBehalfOf ?? correspondence.Recipient, - correspondence.Id.ToString(), - [ResourceAccessLevel.Read, ResourceAccessLevel.Write], + correspondence, cancellationToken); - if (!hasAccess) + if (!hasAccessAsRecipient && !hasAccessAsSender) { return Errors.NoAccessToResource; } - - bool isRecipient = userClaimsHelper.IsRecipient(correspondence.Recipient) || isOnBehalfOfRecipient; - bool isSender = userClaimsHelper.IsSender(correspondence.Sender) || isOnBehalfOfSender; - - if (!isRecipient && !isSender) - { - return Errors.CorrespondenceNotFound; - } var latestStatus = correspondence.GetHighestStatus(); if (latestStatus == null) { return Errors.CorrespondenceNotFound; } - var party = await altinnRegisterService.LookUpPartyById(userClaimsHelper.GetUserID(), cancellationToken); + var party = await altinnRegisterService.LookUpPartyById(user.GetCallerOrganizationId(), cancellationToken); if (party?.PartyUuid is not Guid partyUuid) { return Errors.CouldNotFindPartyUuid; @@ -68,7 +51,7 @@ public async Task> Process(GetCor return await TransactionWithRetriesPolicy.Execute(async (cancellationToken) => { - if (isRecipient) + if (hasAccessAsRecipient && !user.CallingAsSender()) { if (!latestStatus.Status.IsAvailableForRecipient()) { @@ -89,7 +72,7 @@ await correspondenceStatusRepository.AddCorrespondenceStatus(new CorrespondenceS { if (notification.NotificationOrderId != null) { - var notificationSummary = await altinnNotificationService.GetNotificationDetails(notification.NotificationOrderId.ToString()); + var notificationSummary = await altinnNotificationService.GetNotificationDetails(notification.NotificationOrderId.ToString(), cancellationToken); notificationSummary.IsReminder = notification.IsReminder; notificationHistory.Add(notificationSummary); } diff --git a/src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsRequest.cs b/src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsRequest.cs index 94508977..3ae00901 100644 --- a/src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsRequest.cs +++ b/src/Altinn.Correspondence.Application/GetCorrespondenceDetails/GetCorrespondenceDetailsRequest.cs @@ -3,5 +3,4 @@ namespace Altinn.Correspondence.Application.GetCorrespondenceDetails; public class GetCorrespondenceDetailsRequest { public required Guid CorrespondenceId { get; set; } - public string? OnBehalfOf { get; set; } } \ No newline at end of file diff --git a/src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewHandler.cs b/src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewHandler.cs index 88637b84..bd884079 100644 --- a/src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewHandler.cs +++ b/src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewHandler.cs @@ -15,7 +15,6 @@ public class GetCorrespondenceOverviewHandler( IAltinnRegisterService altinnRegisterService, ICorrespondenceRepository correspondenceRepository, ICorrespondenceStatusRepository correspondenceStatusRepository, - UserClaimsHelper userClaimsHelper, ILogger logger) : IHandler { public async Task> Process(GetCorrespondenceOverviewRequest request, ClaimsPrincipal? user, CancellationToken cancellationToken) @@ -25,41 +24,25 @@ public async Task> Process(GetCo { return Errors.CorrespondenceNotFound; } - string? onBehalfOf = request.OnBehalfOf; - bool isOnBehalfOfRecipient = false; - bool isOnBehalfOfSender = false; - if (!string.IsNullOrEmpty(onBehalfOf)) - { - isOnBehalfOfRecipient = correspondence.Recipient.GetOrgNumberWithoutPrefix() == onBehalfOf.GetOrgNumberWithoutPrefix(); - isOnBehalfOfSender = correspondence.Sender.GetOrgNumberWithoutPrefix() == onBehalfOf.GetOrgNumberWithoutPrefix(); - } - var hasAccess = await altinnAuthorizationService.CheckUserAccess( + var hasAccessAsRecipient = await altinnAuthorizationService.CheckAccessAsRecipient( user, - correspondence.ResourceId, - request.OnBehalfOf ?? correspondence.Recipient, - correspondence.Id.ToString(), - [ResourceAccessLevel.Read, ResourceAccessLevel.Write], + correspondence, cancellationToken); - if (!hasAccess) + var hasAccessAsSender = await altinnAuthorizationService.CheckAccessAsSender( + user, + correspondence, + cancellationToken); + if (!hasAccessAsRecipient && !hasAccessAsSender) { return Errors.NoAccessToResource; } - - bool isRecipient = userClaimsHelper.IsRecipient(correspondence.Recipient) || isOnBehalfOfRecipient; - bool isSender = userClaimsHelper.IsSender(correspondence.Sender) || isOnBehalfOfSender; - - if (!isRecipient && !isSender) - { - logger.LogWarning("Caller not affiliated with correspondence"); - return Errors.CorrespondenceNotFound; - } var latestStatus = correspondence.GetHighestStatus(); if (latestStatus == null) { logger.LogWarning("Latest status not found for correspondence"); return Errors.CorrespondenceNotFound; } - var party = await altinnRegisterService.LookUpPartyById(userClaimsHelper.GetUserID(), cancellationToken); + var party = await altinnRegisterService.LookUpPartyById(user.GetCallerOrganizationId(), cancellationToken); if (party?.PartyUuid is not Guid partyUuid) { return Errors.CouldNotFindPartyUuid; @@ -67,7 +50,7 @@ public async Task> Process(GetCo return await TransactionWithRetriesPolicy.Execute(async (cancellationToken) => { - if (isRecipient) + if (hasAccessAsRecipient && !user.CallingAsSender()) { if (!latestStatus.Status.IsAvailableForRecipient()) { diff --git a/src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewRequest.cs b/src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewRequest.cs index 6bf66fc4..c27835ee 100644 --- a/src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewRequest.cs +++ b/src/Altinn.Correspondence.Application/GetCorrespondenceOverview/GetCorrespondenceOverviewRequest.cs @@ -3,5 +3,4 @@ namespace Altinn.Correspondence.Application.GetCorrespondenceOverview; public class GetCorrespondenceOverviewRequest { public required Guid CorrespondenceId { get; set; } - public string? OnBehalfOf { get; set; } } \ No newline at end of file diff --git a/src/Altinn.Correspondence.Application/Helpers/UserClaimsHelper.cs b/src/Altinn.Correspondence.Application/Helpers/UserClaimsHelper.cs index 68c9b1c9..4aa56b68 100644 --- a/src/Altinn.Correspondence.Application/Helpers/UserClaimsHelper.cs +++ b/src/Altinn.Correspondence.Application/Helpers/UserClaimsHelper.cs @@ -1,10 +1,9 @@ -using System.Security.Claims; -using System.Text.Json; using Altinn.Common.PEP.Constants; -using Altinn.Correspondence.Application.Configuration; +using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Options; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; +using System.Security.Claims; namespace Altinn.Correspondence.Application.Helpers { @@ -12,22 +11,12 @@ public class UserClaimsHelper { private readonly ClaimsPrincipal _user; private readonly IEnumerable _claims; - private readonly DialogportenSettings _dialogportenSettings; - private readonly IdportenSettings _idportenSettings; - private const string _scopeClaim = "scope"; - private const string _consumerClaim = "consumer"; - private const string _IdProperty = "ID"; - private const string _dialogportenOrgClaim = "p"; - private const string _personId = "pid"; private const string _minAuthLevelClaim = "urn:altinn:authlevel"; - private const string _altinnUrnPersonIdentifier = "urn:altinn:person:identifier-no"; - public UserClaimsHelper(IHttpContextAccessor httpContextAccessor, IOptions dialogportenSettings, IOptions idportenSettings) + public UserClaimsHelper(IHttpContextAccessor httpContextAccessor) { _user = httpContextAccessor?.HttpContext?.User ?? new ClaimsPrincipal(); _claims = _user.Claims ?? []; - _dialogportenSettings = dialogportenSettings.Value; - _idportenSettings = idportenSettings.Value; } public int? GetPartyId() { @@ -44,72 +33,5 @@ public int GetMinimumAuthenticationLevel() if (int.TryParse(authLevelClaim.Value, out int level)) return level; return 0; } - public bool IsRecipient(string recipientId) - { - if (_claims.Any(c => c.Issuer == _dialogportenSettings.Issuer)) return MatchesDialogTokenOrganization(recipientId) || GetPersonID() == recipientId; - if (_claims.Any(c => c.Issuer == _idportenSettings.Issuer)) return true; // Idporten tokens are always recipients, verified by altinn authorization - if (GetUserID() != recipientId && GetPersonID() != recipientId) return false; - if (!GetUserScope().Any(scope => scope == AuthorizationConstants.RecipientScope)) return false; - return true; - } - - - public bool IsSender(string senderId) - { - if (_claims.Any(c => c.Issuer == _dialogportenSettings.Issuer)) return MatchesDialogTokenOrganization(senderId) || GetPersonID() == senderId; - if (_claims.Any(c => c.Issuer == _idportenSettings.Issuer)) return false; - if (GetUserID() != senderId && GetPersonID() != senderId) return false; - if (!GetUserScope().Any(scope => scope == AuthorizationConstants.SenderScope)) return false; - return true; - } - private bool MatchesDialogTokenOrganization(string organizationId) - { - var orgClaim = _claims.FirstOrDefault(c => c.Type == _dialogportenOrgClaim); - if (orgClaim is null) - { - return false; - } - var orgValue = orgClaim.Value; - return orgValue.Replace(AltinnXacmlUrns.OrganizationNumberAttribute, "0192") == organizationId; - } - public string? GetUserID() - { - var consumer = _claims.FirstOrDefault(c => c.Type == _consumerClaim)?.Value; - if (consumer is null) return GetDialogportenTokenUserId(); - - JsonDocument jsonDoc = JsonDocument.Parse(consumer); - string? id = jsonDoc.RootElement.GetProperty(_IdProperty).GetString(); - return id; - } - private string? GetPersonID() - { - if (_claims.Any(c => c.Issuer == _dialogportenSettings.Issuer)) - { - var personidClaimValue = _claims.FirstOrDefault(c => c.Type == "p")?.Value; - if (!personidClaimValue.StartsWith(_altinnUrnPersonIdentifier)) - { - return null; - } - return personidClaimValue.Replace(_altinnUrnPersonIdentifier + ":", ""); - } - else if (_claims.Any(c => c.Type == _personId)) - { - return _claims.FirstOrDefault(c => c.Type == _personId)?.Value; - } - else - { - return null; - } - } - private string? GetDialogportenTokenUserId() - { - return _claims.FirstOrDefault(c => c.Type == _IdProperty)?.Value; - } - private IEnumerable GetUserScope() - { - var scopeClaims = _claims.Where(c => c.Type == _scopeClaim) ?? []; - var scopes = scopeClaims.SelectMany(c => c.Value.Split(" ")); - return scopes; - } } } \ No newline at end of file diff --git a/src/Altinn.Correspondence.Application/InitializeAttachment/InitializeAttachmentHandler.cs b/src/Altinn.Correspondence.Application/InitializeAttachment/InitializeAttachmentHandler.cs index 67b9f52c..eb04c04a 100644 --- a/src/Altinn.Correspondence.Application/InitializeAttachment/InitializeAttachmentHandler.cs +++ b/src/Altinn.Correspondence.Application/InitializeAttachment/InitializeAttachmentHandler.cs @@ -1,10 +1,10 @@ using Altinn.Correspondence.Application.Helpers; +using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Models.Entities; using Altinn.Correspondence.Core.Models.Enums; using Altinn.Correspondence.Core.Repositories; using Altinn.Correspondence.Core.Services; using Altinn.Correspondence.Core.Services.Enums; -using Altinn.Correspondence.Integrations.Altinn.Authorization; using Microsoft.Extensions.Logging; using OneOf; using System.Security.Claims; @@ -17,22 +17,22 @@ public class InitializeAttachmentHandler( IAttachmentStatusRepository attachmentStatusRepository, IEventBus eventBus, IAltinnAuthorizationService altinnAuthorizationService, - UserClaimsHelper userClaimsHelper, ILogger logger) : IHandler { public async Task> Process(InitializeAttachmentRequest request, ClaimsPrincipal? user, CancellationToken cancellationToken) { - var hasAccess = await altinnAuthorizationService.CheckUserAccess(user, request.Attachment.ResourceId, request.Attachment.Sender, null, new List { ResourceAccessLevel.Write }, cancellationToken); + var hasAccess = await altinnAuthorizationService.CheckAccessAsSender( + user, + request.Attachment.ResourceId, + request.Attachment.Sender.WithoutPrefix(), + null, + cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; } - if (!userClaimsHelper.IsSender(request.Attachment.Sender)) - { - return Errors.InvalidSender; - } - var party = await altinnRegisterService.LookUpPartyById(userClaimsHelper.GetUserID(), cancellationToken); + var party = await altinnRegisterService.LookUpPartyById(user.GetCallerOrganizationId(), cancellationToken); if (party?.PartyUuid is not Guid partyUuid) { return Errors.CouldNotFindPartyUuid; diff --git a/src/Altinn.Correspondence.Application/InitializeCorrespondences/InitializeCorrespondencesHandler.cs b/src/Altinn.Correspondence.Application/InitializeCorrespondences/InitializeCorrespondencesHandler.cs index b5b6c155..c0b5d1c6 100644 --- a/src/Altinn.Correspondence.Application/InitializeCorrespondences/InitializeCorrespondencesHandler.cs +++ b/src/Altinn.Correspondence.Application/InitializeCorrespondences/InitializeCorrespondencesHandler.cs @@ -1,6 +1,7 @@ using Altinn.Correspondence.Application.CorrespondenceDueDate; using Altinn.Correspondence.Application.Helpers; using Altinn.Correspondence.Application.PublishCorrespondence; +using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Models.Entities; using Altinn.Correspondence.Core.Models.Enums; using Altinn.Correspondence.Core.Models.Notifications; @@ -28,7 +29,6 @@ public class InitializeCorrespondencesHandler( INotificationTemplateRepository notificationTemplateRepository, IEventBus eventBus, IBackgroundJobClient backgroundJobClient, - UserClaimsHelper userClaimsHelper, IDialogportenService dialogportenService, IHostEnvironment hostEnvironment, IOptions generalSettings, @@ -38,17 +38,17 @@ public class InitializeCorrespondencesHandler( public async Task> Process(InitializeCorrespondencesRequest request, ClaimsPrincipal? user, CancellationToken cancellationToken) { - var hasAccess = await altinnAuthorizationService.CheckUserAccess(user, request.Correspondence.ResourceId, request.Correspondence.Sender, null, new List { ResourceAccessLevel.Write }, cancellationToken); + var hasAccess = await altinnAuthorizationService.CheckAccessAsSender( + user, + request.Correspondence.ResourceId, + request.Correspondence.Sender.WithoutPrefix(), + null, + cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; } - var isSender = userClaimsHelper.IsSender(request.Correspondence.Sender); - if (!isSender) - { - return Errors.InvalidSender; - } - var party = await altinnRegisterService.LookUpPartyById(userClaimsHelper.GetUserID(), cancellationToken); + var party = await altinnRegisterService.LookUpPartyById(user.GetCallerOrganizationId(), cancellationToken); if (party?.PartyUuid is not Guid partyUuid) { return Errors.CouldNotFindPartyUuid; diff --git a/src/Altinn.Correspondence.Application/MigrateCorrespondence/MigrateCorrespondenceRequest.cs b/src/Altinn.Correspondence.Application/MigrateCorrespondence/MigrateCorrespondenceRequest.cs index 6572f6d1..1cdec856 100644 --- a/src/Altinn.Correspondence.Application/MigrateCorrespondence/MigrateCorrespondenceRequest.cs +++ b/src/Altinn.Correspondence.Application/MigrateCorrespondence/MigrateCorrespondenceRequest.cs @@ -1,4 +1,3 @@ -using Altinn.Correspondence.Application.InitializeCorrespondences; using Altinn.Correspondence.Core.Models.Entities; namespace Altinn.Correspondence.Application.InitializeCorrespondence; diff --git a/src/Altinn.Correspondence.Application/PurgeAttachment/PurgeAttachmentHandler.cs b/src/Altinn.Correspondence.Application/PurgeAttachment/PurgeAttachmentHandler.cs index aea18866..e7c7b5a4 100644 --- a/src/Altinn.Correspondence.Application/PurgeAttachment/PurgeAttachmentHandler.cs +++ b/src/Altinn.Correspondence.Application/PurgeAttachment/PurgeAttachmentHandler.cs @@ -7,6 +7,7 @@ using OneOf; using System.Security.Claims; using Microsoft.Extensions.Logging; +using Altinn.Correspondence.Common.Helpers; namespace Altinn.Correspondence.Application.PurgeAttachment; @@ -18,7 +19,6 @@ public class PurgeAttachmentHandler( IStorageRepository storageRepository, ICorrespondenceRepository correspondenceRepository, IEventBus eventBus, - UserClaimsHelper userClaimsHelper, ILogger logger) : IHandler { public async Task> Process(Guid attachmentId, ClaimsPrincipal? user, CancellationToken cancellationToken) @@ -28,11 +28,12 @@ public async Task> Process(Guid attachmentId, ClaimsPrincipal { return Errors.AttachmentNotFound; } - if (!userClaimsHelper.IsSender(attachment.Sender)) - { - return Errors.InvalidSender; - } - var hasAccess = await altinnAuthorizationService.CheckUserAccess(user, attachment.ResourceId, attachment.Sender, attachment.Id.ToString(), new List { ResourceAccessLevel.Write }, cancellationToken); + var hasAccess = await altinnAuthorizationService.CheckAccessAsSender( + user, + attachment.ResourceId, + attachment.Sender.WithoutPrefix(), + attachment.Id.ToString(), + cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; @@ -54,7 +55,7 @@ public async Task> Process(Guid attachmentId, ClaimsPrincipal { return Errors.PurgeAttachmentWithExistingCorrespondence; } - var party = await altinnRegisterService.LookUpPartyById(userClaimsHelper.GetUserID(), cancellationToken); + var party = await altinnRegisterService.LookUpPartyById(user.GetCallerOrganizationId(), cancellationToken); if (party?.PartyUuid is not Guid partyUuid) { return Errors.CouldNotFindPartyUuid; diff --git a/src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceHandler.cs b/src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceHandler.cs index dd7a1212..dbb735b6 100644 --- a/src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceHandler.cs +++ b/src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceHandler.cs @@ -17,7 +17,6 @@ public class PurgeCorrespondenceHandler( ICorrespondenceRepository correspondenceRepository, ICorrespondenceStatusRepository correspondenceStatusRepository, IEventBus eventBus, - UserClaimsHelper userClaimsHelper, PurgeCorrespondenceHelper purgeCorrespondenceHelper, ILogger logger) : IHandler { @@ -29,33 +28,19 @@ public async Task> Process(PurgeCorrespondenceRequest request { return Errors.CorrespondenceNotFound; } - string? onBehalfOf = request.OnBehalfOf; - bool isOnBehalfOfRecipient = false; - bool isOnBehalfOfSender = false; - if (!string.IsNullOrEmpty(onBehalfOf)) - { - isOnBehalfOfRecipient = correspondence.Recipient.GetOrgNumberWithoutPrefix() == onBehalfOf.GetOrgNumberWithoutPrefix(); - isOnBehalfOfSender = correspondence.Sender.GetOrgNumberWithoutPrefix() == onBehalfOf.GetOrgNumberWithoutPrefix(); - } - var hasAccess = await altinnAuthorizationService.CheckUserAccess( + var hasAccessAsSender = await altinnAuthorizationService.CheckAccessAsSender( + user, + correspondence, + cancellationToken); + var hasAccessAsRecipient = await altinnAuthorizationService.CheckAccessAsRecipient( user, - correspondence.ResourceId, - request.OnBehalfOf ?? correspondence.Recipient, - correspondence.Id.ToString(), - [ResourceAccessLevel.Read, ResourceAccessLevel.Write], + correspondence, cancellationToken); - if (!hasAccess) + if (!hasAccessAsSender && !hasAccessAsRecipient) { return Errors.NoAccessToResource; } - var currentStatusError = purgeCorrespondenceHelper.ValidateCurrentStatus(correspondence); - if (currentStatusError is not null) - { - return currentStatusError; - } - bool isRecipient = userClaimsHelper.IsRecipient(correspondence.Recipient) || isOnBehalfOfRecipient; - bool isSender = userClaimsHelper.IsSender(correspondence.Sender) || isOnBehalfOfSender; - if (isSender) + if (hasAccessAsSender) { var senderRecipientPurgeError = purgeCorrespondenceHelper.ValidatePurgeRequestSender(correspondence); if (senderRecipientPurgeError is not null) @@ -63,7 +48,7 @@ public async Task> Process(PurgeCorrespondenceRequest request return senderRecipientPurgeError; } } - else if (isRecipient) + else if (hasAccessAsRecipient) { var recipientPurgeError = purgeCorrespondenceHelper.ValidatePurgeRequestRecipient(correspondence); if (recipientPurgeError is not null) @@ -71,11 +56,12 @@ public async Task> Process(PurgeCorrespondenceRequest request return recipientPurgeError; } } - if (!isRecipient && !isSender) + var currentStatusError = purgeCorrespondenceHelper.ValidateCurrentStatus(correspondence); + if (currentStatusError is not null) { - return Errors.CorrespondenceNotFound; + return currentStatusError; } - var party = await altinnRegisterService.LookUpPartyById(userClaimsHelper.GetUserID(), cancellationToken); + var party = await altinnRegisterService.LookUpPartyById(user.GetCallerOrganizationId(), cancellationToken); if (party?.PartyUuid is not Guid partyUuid) { return Errors.CouldNotFindPartyUuid; @@ -83,7 +69,7 @@ public async Task> Process(PurgeCorrespondenceRequest request return await TransactionWithRetriesPolicy.Execute(async (cancellationToken) => { - var status = isSender ? CorrespondenceStatus.PurgedByAltinn : CorrespondenceStatus.PurgedByRecipient; + var status = hasAccessAsSender ? CorrespondenceStatus.PurgedByAltinn : CorrespondenceStatus.PurgedByRecipient; await correspondenceStatusRepository.AddCorrespondenceStatus(new CorrespondenceStatusEntity() { CorrespondenceId = correspondenceId, @@ -95,7 +81,7 @@ await correspondenceStatusRepository.AddCorrespondenceStatus(new CorrespondenceS await eventBus.Publish(AltinnEventType.CorrespondencePurged, correspondence.ResourceId, correspondenceId.ToString(), "correspondence", correspondence.Sender, cancellationToken); await purgeCorrespondenceHelper.CheckAndPurgeAttachments(correspondenceId, partyUuid, cancellationToken); - purgeCorrespondenceHelper.ReportActivityToDialogporten(isSender, correspondenceId); + purgeCorrespondenceHelper.ReportActivityToDialogporten(hasAccessAsSender && user.CallingAsSender(), correspondenceId); purgeCorrespondenceHelper.CancelNotification(correspondenceId, cancellationToken); return correspondenceId; }, logger, cancellationToken); diff --git a/src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceRequest.cs b/src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceRequest.cs index d50b6fd8..e2b84a21 100644 --- a/src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceRequest.cs +++ b/src/Altinn.Correspondence.Application/PurgeCorrespondence/PurgeCorrespondenceRequest.cs @@ -3,5 +3,4 @@ namespace Altinn.Correspondence.Application.PurgeCorrespondence; public class PurgeCorrespondenceRequest { public required Guid CorrespondenceId { get; set; } - public string? OnBehalfOf { get; set; } } \ No newline at end of file diff --git a/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/LegacyUpdateCorrespondenceStatusHandler.cs b/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/LegacyUpdateCorrespondenceStatusHandler.cs index bd5a4de9..d7135cb2 100644 --- a/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/LegacyUpdateCorrespondenceStatusHandler.cs +++ b/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/LegacyUpdateCorrespondenceStatusHandler.cs @@ -32,7 +32,13 @@ public async Task> Process(UpdateCorrespondenceStatusRequest { return Errors.CorrespondenceNotFound; } - var minimumAuthLevel = await altinnAuthorizationService.CheckUserAccessAndGetMinimumAuthLevel(user, party.SSN, correspondence.ResourceId, new List { ResourceAccessLevel.Read }, correspondence.Recipient, cancellationToken); + var minimumAuthLevel = await altinnAuthorizationService.CheckUserAccessAndGetMinimumAuthLevel( + user, + party.SSN, + correspondence.ResourceId, + new List { ResourceAccessLevel.Read }, + correspondence.Recipient, + cancellationToken); if (minimumAuthLevel == null) { return Errors.LegacyNoAccessToCorrespondence; diff --git a/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs b/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs index be28bef8..958d3388 100644 --- a/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs +++ b/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusHandler.cs @@ -14,7 +14,6 @@ public class UpdateCorrespondenceStatusHandler( IAltinnRegisterService altinnRegisterService, ICorrespondenceRepository correspondenceRepository, IEventBus eventBus, - UserClaimsHelper userClaimsHelper, UpdateCorrespondenceStatusHelper updateCorrespondenceStatusHelper, ILogger logger) : IHandler { @@ -25,28 +24,14 @@ public async Task> Process(UpdateCorrespondenceStatusRequest { return Errors.CorrespondenceNotFound; } - string? onBehalfOf = request.OnBehalfOf; - bool isOnBehalfOfRecipient = false; - if (!string.IsNullOrEmpty(onBehalfOf)) - { - isOnBehalfOfRecipient = correspondence.Recipient.GetOrgNumberWithoutPrefix() == onBehalfOf.GetOrgNumberWithoutPrefix(); - } - var hasAccess = await altinnAuthorizationService.CheckUserAccess( + var hasAccess = await altinnAuthorizationService.CheckAccessAsRecipient( user, - correspondence.ResourceId, - request.OnBehalfOf ?? correspondence.Recipient, - correspondence.Id.ToString(), - [ResourceAccessLevel.Read], + correspondence, cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; } - var isRecipient = userClaimsHelper.IsRecipient(correspondence.Recipient) || isOnBehalfOfRecipient; - if (!isRecipient) - { - return Errors.CorrespondenceNotFound; - } var currentStatusError = updateCorrespondenceStatusHelper.ValidateCurrentStatus(correspondence); if (currentStatusError is not null) { @@ -57,7 +42,7 @@ public async Task> Process(UpdateCorrespondenceStatusRequest { return updateError; } - var party = await altinnRegisterService.LookUpPartyById(userClaimsHelper.GetUserID(), cancellationToken); + var party = await altinnRegisterService.LookUpPartyById(user.GetCallerOrganizationId(), cancellationToken); if (party?.PartyUuid is not Guid partyUuid) { return Errors.CouldNotFindPartyUuid; diff --git a/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusRequest.cs b/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusRequest.cs index c4ca3d7b..4af430af 100644 --- a/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusRequest.cs +++ b/src/Altinn.Correspondence.Application/UpdateCorrespondenceStatus/UpdateCorrespondenceStatusRequest.cs @@ -6,5 +6,4 @@ public class UpdateCorrespondenceStatusRequest { public required Guid CorrespondenceId { get; set; } public required CorrespondenceStatus Status { get; set; } - public string? OnBehalfOf { get; set; } } diff --git a/src/Altinn.Correspondence.Application/UploadAttachment/UploadAttachmentHandler.cs b/src/Altinn.Correspondence.Application/UploadAttachment/UploadAttachmentHandler.cs index 71c51313..9f329d5c 100644 --- a/src/Altinn.Correspondence.Application/UploadAttachment/UploadAttachmentHandler.cs +++ b/src/Altinn.Correspondence.Application/UploadAttachment/UploadAttachmentHandler.cs @@ -1,4 +1,5 @@ using Altinn.Correspondence.Application.Helpers; +using Altinn.Correspondence.Common.Helpers; using Altinn.Correspondence.Core.Models.Enums; using Altinn.Correspondence.Core.Repositories; using Altinn.Correspondence.Core.Services; @@ -14,7 +15,6 @@ public class UploadAttachmentHandler( IAttachmentRepository attachmentRepository, ICorrespondenceRepository correspondenceRepository, UploadHelper uploadHelper, - UserClaimsHelper userClaimsHelper, ILogger logger) : IHandler { @@ -25,15 +25,16 @@ public async Task> Process(UploadAttachme { return Errors.AttachmentNotFound; } - var hasAccess = await altinnAuthorizationService.CheckUserAccess(user, attachment.ResourceId, attachment.Sender, attachment.Id.ToString(), new List { ResourceAccessLevel.Write }, cancellationToken); + var hasAccess = await altinnAuthorizationService.CheckAccessAsSender( + user, + attachment.ResourceId, + attachment.Sender.WithoutPrefix(), + attachment.Id.ToString(), + cancellationToken); if (!hasAccess) { return Errors.NoAccessToResource; } - if (!userClaimsHelper.IsSender(attachment.Sender)) - { - return Errors.InvalidSender; - } var maxUploadSize = long.Parse(int.MaxValue.ToString()); if (request.ContentLength > maxUploadSize || request.ContentLength == 0) { @@ -43,14 +44,13 @@ public async Task> Process(UploadAttachme { return Errors.InvalidUploadAttachmentStatus; } - // Check if any correspondences are attached. var correspondences = await correspondenceRepository.GetCorrespondencesByAttachmentId(request.AttachmentId, false); if (correspondences.Count != 0) { return Errors.CantUploadToExistingCorrespondence; } - var party = await altinnRegisterService.LookUpPartyById(userClaimsHelper.GetUserID(), cancellationToken); + var party = await altinnRegisterService.LookUpPartyById(user.GetCallerOrganizationId(), cancellationToken); if (party?.PartyUuid is not Guid partyUuid) { return Errors.CouldNotFindPartyUuid; diff --git a/src/Altinn.Correspondence.Common/Altinn.Correspondence.Common.csproj b/src/Altinn.Correspondence.Common/Altinn.Correspondence.Common.csproj index bb23fb7d..334bff94 100644 --- a/src/Altinn.Correspondence.Common/Altinn.Correspondence.Common.csproj +++ b/src/Altinn.Correspondence.Common/Altinn.Correspondence.Common.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/src/Altinn.Correspondence.Application/Configuration/AuthorizationConstants.cs b/src/Altinn.Correspondence.Common/Constants/AuthorizationConstants.cs similarity index 91% rename from src/Altinn.Correspondence.Application/Configuration/AuthorizationConstants.cs rename to src/Altinn.Correspondence.Common/Constants/AuthorizationConstants.cs index 8dfb0144..7aff7cbb 100644 --- a/src/Altinn.Correspondence.Application/Configuration/AuthorizationConstants.cs +++ b/src/Altinn.Correspondence.Common/Constants/AuthorizationConstants.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; -using Microsoft.AspNetCore.Authentication.OpenIdConnect; -namespace Altinn.Correspondence.Application.Configuration; +namespace Altinn.Correspondence.Common.Constants; public static class AuthorizationConstants { diff --git a/src/Altinn.Correspondence.Common/Helpers/ClaimsPrincipalExtensions.cs b/src/Altinn.Correspondence.Common/Helpers/ClaimsPrincipalExtensions.cs new file mode 100644 index 00000000..59880aed --- /dev/null +++ b/src/Altinn.Correspondence.Common/Helpers/ClaimsPrincipalExtensions.cs @@ -0,0 +1,47 @@ +using Altinn.Correspondence.Common.Helpers.Models; +using System.Security.Claims; +using System.Text.Json; + +namespace Altinn.Correspondence.Common.Helpers +{ + public static class ClaimsPrincipalExtensions + { + public static string? GetCallerOrganizationId(this ClaimsPrincipal user) + { + var claims = user.Claims; + // System user token + var systemUserClaim = user.Claims.FirstOrDefault(c => c.Type == "authorization_details"); + if (systemUserClaim is not null) + { + var systemUserAuthorizationDetails = JsonSerializer.Deserialize(systemUserClaim.Value); + return systemUserAuthorizationDetails?.SystemUserOrg.ID.WithoutPrefix(); + } + // Enterprise token + var orgClaim = user.Claims.FirstOrDefault(c => c.Type == "urn:altinn:orgNumber"); + if (orgClaim is not null) + { + return orgClaim.Value.WithoutPrefix(); // Normalize to same format as elsewhere + } + // Personal token + var consumerClaim = user.Claims.FirstOrDefault(c => c.Type == "consumer"); + if (consumerClaim is not null) + { + var consumerObject = JsonSerializer.Deserialize(consumerClaim.Value); + return consumerObject.ID.WithoutPrefix(); + } + // DialogToken + var dialogportenTokenUserId = user.Claims.FirstOrDefault(c => c.Type == "p")?.Value; + if (dialogportenTokenUserId is not null) + { + return dialogportenTokenUserId.WithoutPrefix(); // Normalize to same format as elsewhere + } + return null; + } + + public static bool CallingAsSender(this ClaimsPrincipal user) + { + var scope = user.Claims.FirstOrDefault(c => c.Type == "scope")?.Value; + return scope?.Contains("altinn:correspondence.write") ?? false; + } + } +} diff --git a/src/Altinn.Correspondence.Common/Helpers/Models/SystemUserAuthorizationDetails.cs b/src/Altinn.Correspondence.Common/Helpers/Models/SystemUserAuthorizationDetails.cs new file mode 100644 index 00000000..3fc98636 --- /dev/null +++ b/src/Altinn.Correspondence.Common/Helpers/Models/SystemUserAuthorizationDetails.cs @@ -0,0 +1,33 @@ +using System.Text.Json.Serialization; + +namespace Altinn.Correspondence.Common.Helpers.Models; + +public class SystemUserAuthorizationDetails +{ + [JsonPropertyName("type")] + public string Type { get; set; } + + [JsonPropertyName("systemuser_id")] + public List SystemUserId { get; set; } + + [JsonPropertyName("systemuser_org")] + public SystemUserOrg SystemUserOrg { get; set; } + + [JsonPropertyName("system_id")] + public string SystemId { get; set; } +} + +public class SystemUserAuthorization +{ + [JsonPropertyName("authorization_details")] + public List AuthorizationDetails { get; set; } +} + +public class SystemUserOrg +{ + [JsonPropertyName("authority")] + public string Authority { get; set; } + + [JsonPropertyName("ID")] + public string ID { get; set; } +} diff --git a/src/Altinn.Correspondence.Common/Helpers/Models/TokenConsumer.cs b/src/Altinn.Correspondence.Common/Helpers/Models/TokenConsumer.cs new file mode 100644 index 00000000..82567a9b --- /dev/null +++ b/src/Altinn.Correspondence.Common/Helpers/Models/TokenConsumer.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace Altinn.Correspondence.Common.Helpers.Models; + +public class TokenConsumer +{ + [JsonPropertyName("authority")] + public string Authority { get; set; } + + [JsonPropertyName("ID")] + public string ID { get; set; } +} diff --git a/src/Altinn.Correspondence.Common/Helpers/StringExtensions.cs b/src/Altinn.Correspondence.Common/Helpers/StringExtensions.cs index eb299280..56888b7d 100644 --- a/src/Altinn.Correspondence.Common/Helpers/StringExtensions.cs +++ b/src/Altinn.Correspondence.Common/Helpers/StringExtensions.cs @@ -12,7 +12,7 @@ public static class StringExtensions /// True if the string matches a 11-digit format. public static bool IsSocialSecurityNumber(this string identifier) { - return (!string.IsNullOrEmpty(identifier) && SsnPattern.IsMatch(identifier)); + return (!string.IsNullOrWhiteSpace(identifier) && SsnPattern.IsMatch(identifier)); } /// @@ -22,17 +22,19 @@ public static bool IsSocialSecurityNumber(this string identifier) /// True if the string matches either a 9-digit format or a '4digits:9digits' format, false otherwise. public static bool IsOrganizationNumber(this string identifier) { - return (!string.IsNullOrEmpty(identifier) && OrgPattern.IsMatch(identifier)); + return (!string.IsNullOrWhiteSpace(identifier) && OrgPattern.IsMatch(identifier)); } - /// - /// Extracts the organization number from a string that may contain a prefix. + /// Extracts the identifier from a colon-separated string that may contain a prefix. /// /// The organization number to format /// Returns the last sequence succeeding a colon. - public static string GetOrgNumberWithoutPrefix(this string orgNumber) + public static string WithoutPrefix(this string orgOrSsnNumber) { - var parts = orgNumber.Split(':'); - return parts[^1]; + if (string.IsNullOrWhiteSpace(orgOrSsnNumber)) + { + return string.Empty; + } + return orgOrSsnNumber.Split(":").Last(); } } \ No newline at end of file diff --git a/src/Altinn.Correspondence.Core/Repositories/IAltinnAuthorizationService.cs b/src/Altinn.Correspondence.Core/Repositories/IAltinnAuthorizationService.cs index c5145b90..14596e1c 100644 --- a/src/Altinn.Correspondence.Core/Repositories/IAltinnAuthorizationService.cs +++ b/src/Altinn.Correspondence.Core/Repositories/IAltinnAuthorizationService.cs @@ -1,11 +1,15 @@ -using Altinn.Correspondence.Core.Models.Enums; +using Altinn.Correspondence.Core.Models.Entities; +using Altinn.Correspondence.Core.Models.Enums; using System.Security.Claims; namespace Altinn.Correspondence.Core.Repositories; public interface IAltinnAuthorizationService { - Task CheckUserAccess(ClaimsPrincipal? user, string resourceId, string party, string? correspondenceId, List rights, CancellationToken cancellationToken = default); + Task CheckAccessAsSender(ClaimsPrincipal? user, CorrespondenceEntity correspondence, CancellationToken cancellationToken = default); + Task CheckAccessAsSender(ClaimsPrincipal? user, string resourceId, string sender, string? instance, CancellationToken cancellationToken = default); + Task CheckAccessAsRecipient(ClaimsPrincipal? user, CorrespondenceEntity correspondence, CancellationToken cancellationToken = default); + Task CheckAccessAsAny(ClaimsPrincipal? user, string resource, string party, CancellationToken cancellationToken); Task CheckUserAccessAndGetMinimumAuthLevel(ClaimsPrincipal? user, string ssn, string resourceId, List rights, string recipientOrgNo, CancellationToken cancellationToken = default); Task CheckMigrationAccess(string resourceId, List rights, CancellationToken cancellationToken = default); } diff --git a/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnAuthorizationDevService.cs b/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnAuthorizationDevService.cs index 77154343..ab2c5832 100644 --- a/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnAuthorizationDevService.cs +++ b/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnAuthorizationDevService.cs @@ -1,4 +1,5 @@ -using Altinn.Correspondence.Core.Models.Enums; +using Altinn.Correspondence.Core.Models.Entities; +using Altinn.Correspondence.Core.Models.Enums; using Altinn.Correspondence.Core.Repositories; using System.Security.Claims; @@ -6,12 +7,27 @@ namespace Altinn.Correspondence.Integrations.Altinn.Authorization { public class AltinnAuthorizationDevService : IAltinnAuthorizationService { - public Task CheckMigrationAccess(string resourceId, List rights, CancellationToken cancellationToken = default) + public Task CheckAccessAsAny(ClaimsPrincipal? user, string resource, string party, CancellationToken cancellationToken) + { + return Task.FromResult(true); + } + + public Task CheckAccessAsRecipient(ClaimsPrincipal? user, CorrespondenceEntity correspondence, CancellationToken cancellationToken = default) + { + return Task.FromResult(true); + } + + public Task CheckAccessAsSender(ClaimsPrincipal? user, CorrespondenceEntity correspondence, CancellationToken cancellationToken = default) { return Task.FromResult(true); } - public Task CheckUserAccess(ClaimsPrincipal? user, string resourceId, string instanceOwner, string? correspondenceId, List rights, CancellationToken cancellationToken = default) + public Task CheckAccessAsSender(ClaimsPrincipal? user, string resourceId, string sender, string? instance, CancellationToken cancellationToken = default) + { + return Task.FromResult(true); + } + + public Task CheckMigrationAccess(string resourceId, List rights, CancellationToken cancellationToken = default) { return Task.FromResult(true); } diff --git a/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnAuthorizationService.cs b/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnAuthorizationService.cs index b87ed2d6..ba9246f0 100644 --- a/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnAuthorizationService.cs +++ b/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnAuthorizationService.cs @@ -1,5 +1,7 @@ using Altinn.Authorization.ABAC.Xacml.JsonProfile; using Altinn.Common.PEP.Helpers; +using Altinn.Correspondence.Common.Helpers; +using Altinn.Correspondence.Core.Models.Entities; using Altinn.Correspondence.Core.Models.Enums; using Altinn.Correspondence.Core.Options; using Altinn.Correspondence.Core.Repositories; @@ -38,26 +40,41 @@ public AltinnAuthorizationService(HttpClient httpClient, IOptions _logger = logger; } - /// - /// Checks if the user has access to the resource with any of the given rights - /// - public async Task CheckUserAccess(ClaimsPrincipal? user, string resourceId, string party, string? correspondenceId, List rights, CancellationToken cancellationToken = default) - { - if (user is null) - { - throw new InvalidOperationException("This operation cannot be called outside an authenticated HttpContext"); - } - var bypassDecision = await EvaluateBypassConditions(user, resourceId, cancellationToken); - if (bypassDecision is not null) - { - return bypassDecision.Value; - } - var actionIds = rights.Select(GetActionId).ToList(); - XacmlJsonRequestRoot jsonRequest = CreateDecisionRequest(user, resourceId, party, correspondenceId, actionIds); - var responseContent = await AuthorizeRequest(jsonRequest, cancellationToken); - var validationResult = ValidateAuthorizationResponse(responseContent, user); - return validationResult; - } + public Task CheckAccessAsSender(ClaimsPrincipal? user, string resourceId, string sender, string? instance, CancellationToken cancellationToken = default) + => CheckUserAccess( + user, + resourceId, + sender.WithoutPrefix(), + instance, + new List { ResourceAccessLevel.Write }, + cancellationToken); + + public Task CheckAccessAsSender(ClaimsPrincipal? user, CorrespondenceEntity correspondence, CancellationToken cancellationToken = default) => + CheckUserAccess( + user, + correspondence.ResourceId, + correspondence.Sender.WithoutPrefix(), + correspondence.Id.ToString(), + new List { ResourceAccessLevel.Write }, + cancellationToken); + + public Task CheckAccessAsRecipient(ClaimsPrincipal? user, CorrespondenceEntity correspondence, CancellationToken cancellationToken = default) => + CheckUserAccess( + user, + correspondence.ResourceId, + correspondence.Recipient.WithoutPrefix(), + correspondence.Id.ToString(), + new List { ResourceAccessLevel.Read }, + cancellationToken); + + public Task CheckAccessAsAny(ClaimsPrincipal? user, string resource, string party, CancellationToken cancellationToken) => + CheckUserAccess( + user, + resource, + party, + null, + new List { ResourceAccessLevel.Read, ResourceAccessLevel.Write }, + cancellationToken); public async Task CheckUserAccessAndGetMinimumAuthLevel(ClaimsPrincipal? user, string ssn, string resourceId, List rights, string onBehalfOf, CancellationToken cancellationToken = default) @@ -97,6 +114,25 @@ public async Task CheckMigrationAccess(string resourceId, List CheckUserAccess(ClaimsPrincipal? user, string resourceId, string party, string? correspondenceId, List rights, CancellationToken cancellationToken = default) + { + if (user is null) + { + throw new InvalidOperationException("This operation cannot be called outside an authenticated HttpContext"); + } + var bypassDecision = await EvaluateBypassConditions(user, resourceId, cancellationToken); + if (bypassDecision is not null) + { + return bypassDecision.Value; + } + var actionIds = rights.Select(GetActionId).ToList(); + XacmlJsonRequestRoot jsonRequest = CreateDecisionRequest(user, resourceId, party, correspondenceId, actionIds); + var responseContent = await AuthorizeRequest(jsonRequest, cancellationToken); + var validationResult = ValidateAuthorizationResponse(responseContent, user); + return validationResult; + } + private async Task EvaluateBypassConditions(ClaimsPrincipal? user, string resourceId, CancellationToken cancellationToken) { if (_httpClient.BaseAddress is null) diff --git a/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnTokenXacmlMapper..cs b/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnTokenXacmlMapper..cs index a2f98ebe..cbae99f7 100644 --- a/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnTokenXacmlMapper..cs +++ b/src/Altinn.Correspondence.Integrations/Altinn/Authorization/AltinnTokenXacmlMapper..cs @@ -52,7 +52,7 @@ private static XacmlJsonCategory CreateResourceCategory(string resourceId, Claim if (party.IsOrganizationNumber()) { - resourceCategory.Attribute.Add(DecisionHelper.CreateXacmlJsonAttribute(AltinnXacmlUrns.OrganizationNumberAttribute, party.GetOrgNumberWithoutPrefix(), DefaultType, DefaultIssuer)); + resourceCategory.Attribute.Add(DecisionHelper.CreateXacmlJsonAttribute(AltinnXacmlUrns.OrganizationNumberAttribute, party.WithoutPrefix(), DefaultType, DefaultIssuer)); } else if (party.IsSocialSecurityNumber()) { diff --git a/src/Altinn.Correspondence.Integrations/Dialogporten/Mappers/DialogTokenXacmlMapper.cs b/src/Altinn.Correspondence.Integrations/Dialogporten/Mappers/DialogTokenXacmlMapper.cs index 54fd8e8e..e1b10f05 100644 --- a/src/Altinn.Correspondence.Integrations/Dialogporten/Mappers/DialogTokenXacmlMapper.cs +++ b/src/Altinn.Correspondence.Integrations/Dialogporten/Mappers/DialogTokenXacmlMapper.cs @@ -2,6 +2,7 @@ using Altinn.Authorization.ABAC.Xacml.JsonProfile; using Altinn.Common.PEP.Constants; using Altinn.Common.PEP.Helpers; +using Altinn.Correspondence.Common.Helpers; using Microsoft.IdentityModel.Tokens; using System.Security.Claims; using System.Text.RegularExpressions; @@ -59,13 +60,14 @@ private static XacmlJsonCategory CreateResourceCategory(string resourceId, strin { XacmlJsonCategory resourceCategory = new() { Attribute = new List() }; resourceCategory.Attribute.Add(DecisionHelper.CreateXacmlJsonAttribute(AltinnXacmlUrns.ResourceId, resourceId, DefaultType, DefaultIssuer)); - if (party.Length == 9) + var partyWithoutPrefix = party.WithoutPrefix(); + if (partyWithoutPrefix.IsOrganizationNumber()) { - resourceCategory.Attribute.Add(DecisionHelper.CreateXacmlJsonAttribute(OrganizationAttributeId, party, DefaultType, DefaultIssuer)); + resourceCategory.Attribute.Add(DecisionHelper.CreateXacmlJsonAttribute(AltinnXacmlUrns.OrganizationNumberAttribute, partyWithoutPrefix, DefaultType, DefaultIssuer)); } - else if (party.Length == 11) + else if (partyWithoutPrefix.IsSocialSecurityNumber()) { - resourceCategory.Attribute.Add(DecisionHelper.CreateXacmlJsonAttribute(PersonAttributeId, party, DefaultType, DefaultIssuer)); + resourceCategory.Attribute.Add(DecisionHelper.CreateXacmlJsonAttribute(PersonAttributeId, partyWithoutPrefix, DefaultType, DefaultIssuer)); } else { @@ -93,7 +95,7 @@ private static XacmlJsonCategory CreateSubjectCategory(ClaimsPrincipal user) { list.Add(CreateXacmlJsonAttribute(claim.Type, claim.Value, "string", claim.Issuer)); } - else if (IsOnBehalfOfClaim(claim.Type)) + else if (IsConsumerClaim(claim.Type)) { list.Add(CreateXacmlJsonAttribute("urn:altinn:person:identifier-no", claim.Value.Replace("urn:altinn:person:identifier-no:", ""), "string", claim.Issuer)); } @@ -107,9 +109,9 @@ private static bool IsValidUrn(string value) return regex.Match(value).Success; } - private static bool IsOnBehalfOfClaim(string value) + private static bool IsConsumerClaim(string value) { - return value.Equals("p"); + return value.Equals("c"); } private static bool IsActionClaim(string value) @@ -136,9 +138,13 @@ public static bool ValidateDialogportenResult(XacmlJsonResponse response, Claims XacmlJsonAttributeAssignment obligation = GetObligation("urn:altinn:minimum-authenticationlevel", obligations); if (obligation != null) { - string value = obligation.Value; - string value2 = user.Claims.FirstOrDefault((Claim c) => c.Type.Equals("l")).Value; - if (Convert.ToInt32(value2) < Convert.ToInt32(value)) + string obligationRequiredLevel = obligation.Value; + string claimLevel = user.Claims.FirstOrDefault((Claim c) => c.Type.Equals("l")).Value; + if (claimLevel == "0") + { + return true; // Hotfix until Dialogporten starts sending correct level + } + if (Convert.ToInt32(claimLevel) < Convert.ToInt32(obligationRequiredLevel)) { return false; }