diff --git a/.gitignore b/.gitignore index 04d68e97..5adf7963 100644 --- a/.gitignore +++ b/.gitignore @@ -227,7 +227,6 @@ ClientBin/ *.dbmdl *.dbproj.schemaview *.jfm -*.pfx *.publishsettings orleans.codegen.cs diff --git a/src/Altinn.Notifications/Altinn.Notifications.csproj b/src/Altinn.Notifications/Altinn.Notifications.csproj index 0e5c1db0..38276bd3 100644 --- a/src/Altinn.Notifications/Altinn.Notifications.csproj +++ b/src/Altinn.Notifications/Altinn.Notifications.csproj @@ -9,6 +9,8 @@ + + diff --git a/src/Altinn.Notifications/Authorization/CreateScopeOrAccessTokenRequirement.cs b/src/Altinn.Notifications/Authorization/CreateScopeOrAccessTokenRequirement.cs new file mode 100644 index 00000000..65adb866 --- /dev/null +++ b/src/Altinn.Notifications/Authorization/CreateScopeOrAccessTokenRequirement.cs @@ -0,0 +1,28 @@ +using Altinn.Common.AccessToken; +using Altinn.Common.PEP.Authorization; + +namespace Altinn.Notifications.Authorization; + +/// +/// This requirement was created to allow access if either Scope or AccessToken verification is successful. +/// It inherits from both and which +/// will trigger both and . If any of them +/// indicate success, authorization will succeed. +/// +public class CreateScopeOrAccessTokenRequirement : IAccessTokenRequirement, IScopeAccessRequirement +{ + /// + /// Initializes a new instance of the class with the given scope. + /// + public CreateScopeOrAccessTokenRequirement(string scope) + { + ApprovedIssuers = Array.Empty(); + Scope = new string[] { scope }; + } + + /// + public string[] ApprovedIssuers { get; set; } + + /// + public string[] Scope { get; set; } +} diff --git a/src/Altinn.Notifications/Configuration/AuthorizationConstants.cs b/src/Altinn.Notifications/Configuration/AuthorizationConstants.cs new file mode 100644 index 00000000..9f5ed09c --- /dev/null +++ b/src/Altinn.Notifications/Configuration/AuthorizationConstants.cs @@ -0,0 +1,18 @@ +namespace Altinn.Notifications.Configuration +{ + /// + /// Constants related to authorization of notifications + /// + public static class AuthorizationConstants + { + /// + /// Id for the policy requiring create scope or access platform access token + /// + public const string POLICY_CREATE_SCOPE_OR_PLATFORM_ACCESS = "CreateScopeOrPlatfomAccessToken"; + + /// + /// Scope for allowing access to creating notifications + /// + public const string SCOPE_NOTIFICATIONS_CREATE = "altinn:notifications.create"; + } +} diff --git a/src/Altinn.Notifications/Controllers/EmailNotificationOrdersController.cs b/src/Altinn.Notifications/Controllers/EmailNotificationOrdersController.cs index 93a499aa..36ba52b5 100644 --- a/src/Altinn.Notifications/Controllers/EmailNotificationOrdersController.cs +++ b/src/Altinn.Notifications/Controllers/EmailNotificationOrdersController.cs @@ -1,4 +1,5 @@ -using Altinn.Notifications.Core.Models; +using Altinn.Notifications.Configuration; +using Altinn.Notifications.Core.Models; using Altinn.Notifications.Core.Models.Orders; using Altinn.Notifications.Core.Services.Interfaces; using Altinn.Notifications.Extensions; @@ -21,7 +22,7 @@ namespace Altinn.Notifications.Controllers; /// [Route("notifications/api/v1/orders/email")] [ApiController] -[Authorize] +[Authorize(Policy = AuthorizationConstants.POLICY_CREATE_SCOPE_OR_PLATFORM_ACCESS)] [SwaggerResponse(401, "Caller is unauthorized")] [SwaggerResponse(403, "Caller is not authorized to access the requested resource")] @@ -60,7 +61,7 @@ public async Task> Post(EmailNotificationOrderRequestEx return ValidationProblem(ModelState); } - string? creator = User.GetOrg(); + string? creator = HttpContext.GetOrg(); if (creator == null) { diff --git a/src/Altinn.Notifications/Controllers/OrdersController.cs b/src/Altinn.Notifications/Controllers/OrdersController.cs index b8ccaf3d..ab09e8ad 100644 --- a/src/Altinn.Notifications/Controllers/OrdersController.cs +++ b/src/Altinn.Notifications/Controllers/OrdersController.cs @@ -1,4 +1,5 @@ -using Altinn.Notifications.Core.Services.Interfaces; +using Altinn.Notifications.Configuration; +using Altinn.Notifications.Core.Services.Interfaces; using Altinn.Notifications.Extensions; using Altinn.Notifications.Mappers; using Altinn.Notifications.Models; @@ -16,7 +17,7 @@ namespace Altinn.Notifications.Controllers; /// [Route("notifications/api/v1/orders")] [ApiController] -[Authorize] +[Authorize(Policy = AuthorizationConstants.POLICY_CREATE_SCOPE_OR_PLATFORM_ACCESS)] [SwaggerResponse(401, "Caller is unauthorized")] [SwaggerResponse(403, "Caller is not authorized to access the requested resource")] public class OrdersController : ControllerBase @@ -43,7 +44,8 @@ public OrdersController(IGetOrderService getOrderService) [SwaggerResponse(404, "No order with the provided id was found")] public async Task> GetById([FromRoute] Guid id) { - string? expectedCreator = User.GetOrg(); + string? expectedCreator = HttpContext.GetOrg(); + if (expectedCreator == null) { return Forbid(); @@ -69,7 +71,7 @@ public async Task> GetById([FromRoute] Guid i [SwaggerResponse(200, "The list of notification orders matching the provided senders ref was retrieved successfully", typeof(NotificationOrderListExt))] public async Task> GetBySendersRef([FromQuery, BindRequired] string sendersReference) { - string? expectedCreator = User.GetOrg(); + string? expectedCreator = HttpContext.GetOrg(); if (expectedCreator == null) { return Forbid(); @@ -93,11 +95,11 @@ public async Task> GetBySendersRef([FromQ [HttpGet] [Route("{id}/status")] [Produces("application/json")] - [SwaggerResponse(200, "The notification order matching the provided id was retrieved successfully", typeof(NotificationOrderExt))] + [SwaggerResponse(200, "The notification order matching the provided id was retrieved successfully", typeof(NotificationOrderWithStatusExt))] [SwaggerResponse(404, "No order with the provided id was found")] public async Task> GetWithStatusById([FromRoute] Guid id) { - string? expectedCreator = User.GetOrg(); + string? expectedCreator = HttpContext.GetOrg(); if (expectedCreator == null) { return Forbid(); diff --git a/src/Altinn.Notifications/Extensions/ClaimsPrincipalExtensions.cs b/src/Altinn.Notifications/Extensions/ClaimsPrincipalExtensions.cs deleted file mode 100644 index c4440aaf..00000000 --- a/src/Altinn.Notifications/Extensions/ClaimsPrincipalExtensions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Security.Claims; - -using AltinnCore.Authentication.Constants; - -namespace Altinn.Notifications.Extensions; - -/// -/// Extensions for claimsprincial -/// -public static class ClaimsPrincipalExtensions -{ - /// - /// Get the org identifier string or null if it is not an org. - /// - public static string? GetOrg(this ClaimsPrincipal user) - { - if (user.HasClaim(c => c.Type == AltinnCoreClaimTypes.Org)) - { - Claim? orgClaim = user.FindFirst(c => c.Type == AltinnCoreClaimTypes.Org); - if (orgClaim != null) - { - return orgClaim.Value; - } - } - - return null; - } -} diff --git a/src/Altinn.Notifications/Extensions/HttpContextExtensions.cs b/src/Altinn.Notifications/Extensions/HttpContextExtensions.cs new file mode 100644 index 00000000..4db71ded --- /dev/null +++ b/src/Altinn.Notifications/Extensions/HttpContextExtensions.cs @@ -0,0 +1,15 @@ +namespace Altinn.Notifications.Extensions; + +/// +/// Extensions for HTTP Context +/// +public static class HttpContextExtensions +{ + /// + /// Get the org string from the context items or null if it is not defined + /// + public static string? GetOrg(this HttpContext context) + { + return context.Items["Org"] as string; + } +} diff --git a/src/Altinn.Notifications/Middleware/OrgExtractorMiddleware.cs b/src/Altinn.Notifications/Middleware/OrgExtractorMiddleware.cs new file mode 100644 index 00000000..94a9972a --- /dev/null +++ b/src/Altinn.Notifications/Middleware/OrgExtractorMiddleware.cs @@ -0,0 +1,94 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; + +using AltinnCore.Authentication.Constants; + +namespace Altinn.Notifications.Middleware; + +/// +/// Middleware for extracting org information in an HTTP request +/// from either the issuer of PlatformAccessToken header or as +/// an org claim in the bearer token. +/// +public class OrgExtractorMiddleware +{ + private readonly RequestDelegate _next; + + /// + /// Initializes a new instance of the class. + /// + public OrgExtractorMiddleware(RequestDelegate next) + { + _next = next; + } + + /// + /// Retrieve org claim and save in httpContext as Creator item. + /// + public async Task InvokeAsync(HttpContext context) + { + if (ShouldApplyMiddleware(context.Request.Path)) + { + string? org = GetOrgFromHttpContext(context); + + if (org != null) + { + context.Items["Org"] = org; + } + } + + await _next(context); + } + + private static string? GetOrgFromHttpContext(HttpContext context) + { + string? accessToken = context.Request.Headers["PlatformAccessToken"]; + if (!string.IsNullOrEmpty(accessToken)) + { + return GetIssuerOfAccessToken(accessToken); + } + + return GetOrgFromClaim(context.User); + } + + private static string GetIssuerOfAccessToken(string accessToken) + { + JwtSecurityTokenHandler validator = new(); + JwtSecurityToken jwt = validator.ReadJwtToken(accessToken); + return jwt.Issuer; + } + + private static string? GetOrgFromClaim(ClaimsPrincipal user) + { + if (user.HasClaim(c => c.Type == AltinnCoreClaimTypes.Org)) + { + Claim? orgClaim = user.FindFirst(c => c.Type == AltinnCoreClaimTypes.Org); + if (orgClaim != null) + { + return orgClaim.Value; + } + } + + return null; + } + + private static bool ShouldApplyMiddleware(string path) + { + return !(path.Contains("/trigger") || path.Contains("/health")); + } +} + +/// +/// Static class for middleware registration +/// +public static class OrgExtractorMiddlewareExtensions +{ + /// + /// Registers the in the application + /// + public static IApplicationBuilder UseOrgExtractor( + this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } +} diff --git a/src/Altinn.Notifications/Program.cs b/src/Altinn.Notifications/Program.cs index 3619c3b1..8cf28fc5 100644 --- a/src/Altinn.Notifications/Program.cs +++ b/src/Altinn.Notifications/Program.cs @@ -3,11 +3,16 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Altinn.Common.AccessToken; +using Altinn.Common.AccessToken.Services; +using Altinn.Common.PEP.Authorization; +using Altinn.Notifications.Authorization; using Altinn.Notifications.Configuration; using Altinn.Notifications.Core.Extensions; using Altinn.Notifications.Extensions; using Altinn.Notifications.Health; using Altinn.Notifications.Integrations.Extensions; +using Altinn.Notifications.Middleware; using Altinn.Notifications.Models; using Altinn.Notifications.Persistence.Extensions; using Altinn.Notifications.Validators; @@ -23,6 +28,7 @@ using Microsoft.ApplicationInsights.Channel; using Microsoft.ApplicationInsights.Extensibility; using Microsoft.ApplicationInsights.WindowsServer.TelemetryChannel; +using Microsoft.AspNetCore.Authorization; using Microsoft.IdentityModel.Tokens; using Swashbuckle.AspNetCore.Filters; @@ -45,11 +51,12 @@ builder.Services.AddSwaggerGen(c => { IncludeXmlComments(c); - c.EnableAnnotations(); + c.EnableAnnotations(); c.OperationFilter(); }); var app = builder.Build(); + app.SetUpPostgreSql(builder.Environment.IsDevelopment(), builder.Configuration); // Configure the HTTP request pipeline. @@ -65,6 +72,8 @@ app.MapHealthChecks("/health"); +app.UseOrgExtractor(); + app.Run(); void ConfigureSetupLogging() @@ -144,7 +153,6 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) } GeneralSettings generalSettings = config.GetSection("GeneralSettings").Get(); - services.Configure(config.GetSection("GeneralSettings")); services.AddAuthentication(JwtCookieDefaults.AuthenticationScheme) .AddJwtCookie(JwtCookieDefaults.AuthenticationScheme, options => @@ -167,6 +175,8 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) } }); + AddAuthorizationRulesAndHandlers(services, config); + ResourceLinkExtensions.Initialize(generalSettings.BaseUri); AddInputModelValidators(services); services.AddCoreServices(config); @@ -174,10 +184,30 @@ void ConfigureServices(IServiceCollection services, IConfiguration config) services.AddKafkaServices(config); services.AddKafkaHealthChecks(config); - services.AddPostgresRepositories(config); + services.AddPostgresRepositories(config); services.AddPostgresHealthChecks(config); } +void AddAuthorizationRulesAndHandlers(IServiceCollection services, IConfiguration config) +{ + services.AddAuthorization(options => + { + options.AddPolicy(AuthorizationConstants.POLICY_CREATE_SCOPE_OR_PLATFORM_ACCESS, policy => + { + policy.Requirements.Add(new CreateScopeOrAccessTokenRequirement(AuthorizationConstants.SCOPE_NOTIFICATIONS_CREATE)); + }); + }); + + services.AddTransient(); + + // services required for access token handler + services.AddMemoryCache(); + services.AddSingleton(); + services.AddSingleton(); + services.Configure(config.GetSection("kvSetting")); + services.AddSingleton(); +} + async Task SetConfigurationProviders(ConfigurationManager config) { string basePath = Directory.GetParent(Directory.GetCurrentDirectory()).FullName; diff --git a/test/Altinn.Notifications.IntegrationTests/Altinn.Notifications.IntegrationTests.csproj b/test/Altinn.Notifications.IntegrationTests/Altinn.Notifications.IntegrationTests.csproj index 410ca393..c4c8612d 100644 --- a/test/Altinn.Notifications.IntegrationTests/Altinn.Notifications.IntegrationTests.csproj +++ b/test/Altinn.Notifications.IntegrationTests/Altinn.Notifications.IntegrationTests.csproj @@ -29,10 +29,6 @@ - - - - Always diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/EmailNotificationOrdersControllerTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/EmailNotificationOrdersControllerTests.cs index 7dda8d21..1f05b583 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/EmailNotificationOrdersControllerTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/EmailNotificationOrdersControllerTests.cs @@ -3,6 +3,7 @@ using System.Text; using System.Text.Json; +using Altinn.Common.AccessToken.Services; using Altinn.Notifications.Configuration; using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Models; @@ -86,11 +87,26 @@ public async Task Post_MissingBearerToken_Unauthorized() Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } + [Fact] + public async Task Post_InvalidScopeInToken_Forbidden() + { + // Arrange + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:dummmy.scope")); + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, _basePath); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + [Fact] public async Task Post_EmptyBody_BadRequest() { HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, _basePath) { @@ -116,7 +132,7 @@ public async Task Post_ValidationReturnsError_BadRequest() .Returns(new ValidationResult(new List { new ValidationFailure("SomeProperty", "SomeError") })); HttpClient client = GetTestClient(validator.Object); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, _basePath) { @@ -161,7 +177,7 @@ public async Task Post_ServiceReturnsError_ServerError() .ReturnsAsync((null, new ServiceError(500))); HttpClient client = GetTestClient(orderService: serviceMock.Object); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, _basePath) { @@ -177,7 +193,7 @@ public async Task Post_ServiceReturnsError_ServerError() } [Fact] - public async Task Post_ServiceReturnsOrder_Accepted() + public async Task Post_ValidScope_ServiceReturnsOrder_Accepted() { // Arrange Mock serviceMock = new(); @@ -194,7 +210,7 @@ public async Task Post_ServiceReturnsOrder_Accepted() .ReturnsAsync((_order, null)); HttpClient client = GetTestClient(orderService: serviceMock.Object); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, _basePath) { @@ -204,10 +220,49 @@ public async Task Post_ServiceReturnsOrder_Accepted() // Act HttpResponseMessage response = await client.SendAsync(httpRequestMessage); string respoonseString = await response.Content.ReadAsStringAsync(); - OrderIdExt? orderIdObjectExt = JsonSerializer.Deserialize(respoonseString); // Assert Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + OrderIdExt? orderIdObjectExt = JsonSerializer.Deserialize(respoonseString); + Assert.NotNull(orderIdObjectExt); + Assert.Equal(_order.Id, orderIdObjectExt.OrderId); + Assert.Equal("http://localhost:5090/notifications/api/v1/orders/" + _order.Id, response.Headers?.Location?.ToString()); + + serviceMock.VerifyAll(); + } + + [Fact] + public async Task Post_ValidAccessToken_ServiceReturnsOrder_Accepted() + { + // Arrange + Mock serviceMock = new(); + serviceMock.Setup(s => s.RegisterEmailNotificationOrder(It.IsAny())) + .Callback(orderRequest => + { + var emailTemplate = orderRequest.Templates + .OfType() + .FirstOrDefault(); + + Assert.NotNull(emailTemplate); + Assert.Empty(emailTemplate.FromAddress); + }) + .ReturnsAsync((_order, null)); + + HttpClient client = GetTestClient(orderService: serviceMock.Object); + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, _basePath) + { + Content = new StringContent(_orderRequestExt.Serialize(), Encoding.UTF8, "application/json") + }; + httpRequestMessage.Headers.Add("PlatformAccessToken", PrincipalUtil.GetAccessToken("ttd", "apps-test")); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + string respoonseString = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + OrderIdExt? orderIdObjectExt = JsonSerializer.Deserialize(respoonseString); Assert.NotNull(orderIdObjectExt); Assert.Equal(_order.Id, orderIdObjectExt.OrderId); Assert.Equal("http://localhost:5090/notifications/api/v1/orders/" + _order.Id, response.Headers?.Location?.ToString()); @@ -234,7 +289,7 @@ public async Task Post_OrderWithoutFromAddress_StringEmptyUsedAsServiceInput_Acc .ReturnsAsync((_order, null)); HttpClient client = GetTestClient(orderService: serviceMock.Object); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); EmailNotificationOrderRequestExt request = new() { @@ -296,8 +351,9 @@ private HttpClient GetTestClient(IValidator? v services.AddSingleton(validator); services.AddSingleton(orderService); - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/PostTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/PostTests.cs index 685ab806..63586f1f 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/PostTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/EmailNotificationsOrderController/PostTests.cs @@ -3,6 +3,7 @@ using System.Text; using System.Text.Json; +using Altinn.Common.AccessToken.Services; using Altinn.Notifications.Controllers; using Altinn.Notifications.Core.Enums; using Altinn.Notifications.IntegrationTests.Utils; @@ -66,7 +67,7 @@ public async Task Post_ServiceReturnsOrderWIthId_Accepted() { // Arrange HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, _basePath) { @@ -76,10 +77,10 @@ public async Task Post_ServiceReturnsOrderWIthId_Accepted() // Act HttpResponseMessage response = await client.SendAsync(httpRequestMessage); string respoonseString = await response.Content.ReadAsStringAsync(); - OrderIdExt? orderIdObjectExt = JsonSerializer.Deserialize(respoonseString); // Assert Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + OrderIdExt? orderIdObjectExt = JsonSerializer.Deserialize(respoonseString); Assert.NotNull(orderIdObjectExt); Assert.Equal("http://localhost:5090/notifications/api/v1/orders/" + orderIdObjectExt.OrderId, response.Headers?.Location?.ToString()); } @@ -89,7 +90,7 @@ public async Task Post_OrderWithoutSendersRef_Accepted() { // Arrange HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Post, _basePath) { @@ -99,10 +100,10 @@ public async Task Post_OrderWithoutSendersRef_Accepted() // Act HttpResponseMessage response = await client.SendAsync(httpRequestMessage); string respoonseString = await response.Content.ReadAsStringAsync(); - OrderIdExt? orderIdObjectExt = JsonSerializer.Deserialize(respoonseString); // Assert Assert.Equal(HttpStatusCode.Accepted, response.StatusCode); + OrderIdExt? orderIdObjectExt = JsonSerializer.Deserialize(respoonseString); Assert.NotNull(orderIdObjectExt); Assert.Equal("http://localhost:5090/notifications/api/v1/orders/" + orderIdObjectExt.OrderId, response.Headers?.Location?.ToString()); } @@ -128,8 +129,9 @@ private HttpClient GetTestClient() builder.ConfigureTestServices(services => { - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetByIdTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetByIdTests.cs index b6c8dea8..cebf5f4e 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetByIdTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetByIdTests.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Altinn.Common.AccessToken.Services; using Altinn.Notifications.Core.Models.Orders; using Altinn.Notifications.IntegrationTests.Utils; using Altinn.Notifications.Mappers; @@ -41,7 +42,7 @@ public async Task GetById_NoMatchInDb_ReturnsNotFound() string uri = $"{_basePath}/{Guid.NewGuid()}"; HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); @@ -85,7 +86,7 @@ public async Task GetById_SingleMatchInDb_ReturnsOk() string uri = $"{_basePath}/{persistedOrder.Id}"; HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); @@ -120,8 +121,9 @@ private HttpClient GetTestClient() builder.ConfigureTestServices(services => { - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetBySendersRefTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetBySendersRefTests.cs index 4e296b13..357b90ba 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetBySendersRefTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetBySendersRefTests.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Altinn.Common.AccessToken.Services; using Altinn.Notifications.Core.Models.Orders; using Altinn.Notifications.IntegrationTests.Utils; using Altinn.Notifications.Models; @@ -40,7 +41,7 @@ public async Task GetBySendersRef_NoMatchInDb_ReturnsOK_EmptyList() string sendersReference = $"{_sendersRefBase}-{Guid.NewGuid()}"; HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); string uri = $"{_basePath}?sendersReference={sendersReference}"; HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); @@ -64,7 +65,7 @@ public async Task GetBySendersRef_SingleMatchInDb_ReturnsOk_SingleElementInlList NotificationOrder persistedOrder = await PostgreUtil.PopulateDBWithOrder(sendersReference: sendersReference); HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); string uri = $"{_basePath}?sendersReference={sendersReference}"; HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); @@ -91,7 +92,7 @@ public async Task GetBySendersRef_MultipleMatchInDb_ReturnsOk_MultipleElementInl await PostgreUtil.PopulateDBWithOrder(sendersReference: sendersReference); HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); string uri = $"{_basePath}?sendersReference={sendersReference}"; HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); @@ -129,8 +130,9 @@ private HttpClient GetTestClient() builder.ConfigureTestServices(services => { - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetWithStatusById.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetWithStatusById.cs index d9aea92b..21e476d0 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetWithStatusById.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/GetWithStatusById.cs @@ -3,6 +3,7 @@ using System.Text.Json; using System.Text.Json.Serialization; +using Altinn.Common.AccessToken.Services; using Altinn.Notifications.Core.Models.Orders; using Altinn.Notifications.IntegrationTests.Utils; using Altinn.Notifications.Models; @@ -40,7 +41,7 @@ public async Task GetWithStatusById_NoMatchInDb_ReturnsNotFound() string uri = $"{_basePath}/{Guid.NewGuid()}/status"; HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); @@ -90,7 +91,7 @@ public async Task GetWithStatusById_SingleMatchInDbAndOneEmail_ReturnsOk() string uri = $"{_basePath}/{persistedOrder.Id}/status"; HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); @@ -129,7 +130,7 @@ public async Task GetWithStatusById_SingleMatchInDb_ReturnsOk() string uri = $"{_basePath}/{persistedOrder.Id}/status"; HttpClient client = GetTestClient(); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, uri); @@ -164,8 +165,9 @@ private HttpClient GetTestClient() builder.ConfigureTestServices(services => { - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/OrdersControllerTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/OrdersControllerTests.cs index f2f00233..e12aa17e 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/OrdersControllerTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/OrdersController/OrdersControllerTests.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Http.Headers; +using Altinn.Common.AccessToken.Services; using Altinn.Notifications.Core.Enums; using Altinn.Notifications.Core.Models; using Altinn.Notifications.Core.Models.NotificationTemplate; @@ -15,6 +16,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Logging; +using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Moq; @@ -28,6 +30,7 @@ public class OrdersControllerTests : IClassFixture _factory; private readonly NotificationOrder _order; + private readonly NotificationOrderWithStatus _orderWithStatus; public OrdersControllerTests(IntegrationTestWebApplicationFactory factory) { @@ -42,6 +45,15 @@ public OrdersControllerTests(IntegrationTestWebApplicationFactory()); + + _orderWithStatus = new( + Guid.NewGuid(), + "senders-reference", + DateTime.UtcNow, + new Creator("ttd"), + DateTime.UtcNow, + NotificationChannel.Email, + new ProcessingStatus()); } [Fact] @@ -76,6 +88,69 @@ public async Task GetBySendersRef_CalledByUser_ReturnsForbidden() Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } + [Fact] + public async Task GetBySendersRef_CalledWithInvalidScope_ReturnsForbidden() + { + // Arrange + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "dummy:scope")); + + string url = _basePath + "?sendersReference=" + "internal-ref"; + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task GetBySendersRef_ValidBearerToken_CorrespondingServiceMethodCalled() + { + // Arrange + var orderService = new Mock(); + orderService + .Setup(o => o.GetOrdersBySendersReference(It.Is(s => s.Equals("internal-ref")), It.Is(s => s.Equals("ttd")))) + .ReturnsAsync((new List() { _order }, null)); + + HttpClient client = GetTestClient(orderService.Object); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); + + string url = _basePath + "?sendersReference=" + "internal-ref"; + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + orderService.VerifyAll(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task GetBySendersRef_ValidPlatformAccessToken_CorrespondingServiceMethodCalled() + { + // Arrange + var orderService = new Mock(); + orderService + .Setup(o => o.GetOrdersBySendersReference(It.Is(s => s.Equals("internal-ref")), It.Is(s => s.Equals("ttd")))) + .ReturnsAsync((new List() { _order }, null)); + + HttpClient client = GetTestClient(orderService.Object); + + string url = _basePath + "?sendersReference=" + "internal-ref"; + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + httpRequestMessage.Headers.Add("PlatformAccessToken", PrincipalUtil.GetAccessToken("ttd", "apps-test")); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + orderService.VerifyAll(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + [Fact] public async Task GetById_MissingBearer_ReturnsUnauthorized() { @@ -109,18 +184,37 @@ public async Task GetById_CalledByUser_ReturnsForbidden() } [Fact] - public async Task GetBySendersRef_CorrespondingServiceMethodCalled() + public async Task GetById_CalledWithInvalidScope_ReturnsForbidden() + { + // Arrange + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "dummy:scope")); + + string url = _basePath + "/" + Guid.NewGuid(); + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task GetById_ValidBearerToken_CorrespondingServiceMethodCalled() { // Arrange + Guid orderId = Guid.NewGuid(); + var orderService = new Mock(); orderService - .Setup(o => o.GetOrdersBySendersReference(It.Is(s => s.Equals("internal-ref")), It.Is(s => s.Equals("ttd")))) - .ReturnsAsync((new List() { _order }, null)); + .Setup(o => o.GetOrderById(It.Is(g => g.Equals(orderId)), It.Is(s => s.Equals("ttd")))) + .ReturnsAsync((_order, null)); HttpClient client = GetTestClient(orderService.Object); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); - string url = _basePath + "?sendersReference=" + "internal-ref"; + string url = _basePath + "/" + orderId; HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); // Act @@ -132,7 +226,7 @@ public async Task GetBySendersRef_CorrespondingServiceMethodCalled() } [Fact] - public async Task GetById_CorrespondingServiceMethodCalled() + public async Task GetById_ValidPlatformAccessToken_CorrespondingServiceMethodCalled() { // Arrange Guid orderId = Guid.NewGuid(); @@ -143,10 +237,10 @@ public async Task GetById_CorrespondingServiceMethodCalled() .ReturnsAsync((_order, null)); HttpClient client = GetTestClient(orderService.Object); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); string url = _basePath + "/" + orderId; HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + httpRequestMessage.Headers.Add("PlatformAccessToken", PrincipalUtil.GetAccessToken("ttd", "apps-test")); // Act HttpResponseMessage response = await client.SendAsync(httpRequestMessage); @@ -157,7 +251,7 @@ public async Task GetById_CorrespondingServiceMethodCalled() } [Fact] - public async Task GetById_ServicerReturnsError_StatusCodeMatchesError() + public async Task GetById_ServiceReturnsError_StatusCodeMatchesError() { // Arrange Guid orderId = Guid.NewGuid(); @@ -168,7 +262,7 @@ public async Task GetById_ServicerReturnsError_StatusCodeMatchesError() .ReturnsAsync((null, new ServiceError(404))); HttpClient client = GetTestClient(orderService.Object); - client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd")); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); string url = _basePath + "/" + orderId; HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); @@ -181,6 +275,131 @@ public async Task GetById_ServicerReturnsError_StatusCodeMatchesError() Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } + [Fact] + public async Task GetWithStatusById_MissingBearer_ReturnsUnauthorized() + { + // Arrange + HttpClient client = GetTestClient(); + string url = _basePath + "/" + Guid.NewGuid() + "/status"; + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Fact] + public async Task GetWithStatusById_CalledByUser_ReturnsForbidden() + { + // Arrange + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetUserToken(1337)); + + string url = _basePath + "/" + Guid.NewGuid() + "/status"; + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task GetWithStatusById_CalledWithInvalidScope_ReturnsForbidden() + { + // Arrange + HttpClient client = GetTestClient(); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "dummy:scope")); + + string url = _basePath + "/" + Guid.NewGuid() + "/status"; + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task GetWithStatusById_ValidBearerToken_CorrespondingServiceMethodCalled() + { + // Arrange + Guid orderId = Guid.NewGuid(); + + var orderService = new Mock(); + orderService + .Setup(o => o.GetOrderWithStatuById(It.Is(g => g.Equals(orderId)), It.Is(s => s.Equals("ttd")))) + .ReturnsAsync((_orderWithStatus, null)); + + HttpClient client = GetTestClient(orderService.Object); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); + + string url = _basePath + "/" + orderId + "/status"; + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + orderService.VerifyAll(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task GetWithStatusById_ValidPlatformAccessToken_CorrespondingServiceMethodCalled() + { + // Arrange + Guid orderId = Guid.NewGuid(); + + var orderService = new Mock(); + orderService + .Setup(o => o.GetOrderWithStatuById(It.Is(g => g.Equals(orderId)), It.Is(s => s.Equals("ttd")))) + .ReturnsAsync((_orderWithStatus, null)); + + HttpClient client = GetTestClient(orderService.Object); + + string url = _basePath + "/" + orderId + "/status"; + + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + httpRequestMessage.Headers.Add("PlatformAccessToken", PrincipalUtil.GetAccessToken("ttd", "apps-test")); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + orderService.VerifyAll(); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task GetWithStatusById_ServiceReturnsError_StatusCodeMatchesError() + { + // Arrange + Guid orderId = Guid.NewGuid(); + + var orderService = new Mock(); + orderService + .Setup(o => o.GetOrderWithStatuById(It.Is(g => g.Equals(orderId)), It.Is(s => s.Equals("ttd")))) + .ReturnsAsync((null, new ServiceError(404))); + + HttpClient client = GetTestClient(orderService.Object); + client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", PrincipalUtil.GetOrgToken("ttd", scope: "altinn:notifications.create")); + + string url = _basePath + "/" + orderId + "/status"; + HttpRequestMessage httpRequestMessage = new(HttpMethod.Get, url); + + // Act + HttpResponseMessage response = await client.SendAsync(httpRequestMessage); + + // Assert + orderService.VerifyAll(); + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + private HttpClient GetTestClient(IGetOrderService? orderService = null) { if (orderService == null) @@ -205,8 +424,9 @@ private HttpClient GetTestClient(IGetOrderService? orderService = null) { services.AddSingleton(orderService); - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); + services.AddSingleton(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/TriggerControllerTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/TriggerControllerTests.cs index 2d5f06d5..cc3f11ba 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/TriggerControllerTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/TriggerControllerTests.cs @@ -89,7 +89,7 @@ private HttpClient GetTestClient(IOrderProcessingService? orderProcessingService services.AddSingleton(orderProcessingService); services.AddSingleton(emailNotificationService); - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/Trigger_PastDueOrdersTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/Trigger_PastDueOrdersTests.cs index 4bad8cef..12db96f6 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/Trigger_PastDueOrdersTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/Trigger_PastDueOrdersTests.cs @@ -85,7 +85,7 @@ private HttpClient GetTestClient() opts.PastDueOrdersTopicName = _topicName; }); - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/Trigger_SendEmailNotificationsTests.cs b/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/Trigger_SendEmailNotificationsTests.cs index 5081da24..d73f3282 100644 --- a/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/Trigger_SendEmailNotificationsTests.cs +++ b/test/Altinn.Notifications.IntegrationTests/Notifications/TriggerController/Trigger_SendEmailNotificationsTests.cs @@ -86,7 +86,7 @@ private HttpClient GetTestClient() opts.PastDueOrdersTopicName = _topicName; }); - // Set up mock authentication so that not well known endpoint is used + // Set up mock authentication and authorization services.AddSingleton, JwtCookiePostConfigureOptionsStub>(); }); }).CreateClient(); diff --git a/test/Altinn.Notifications.Tests/Altinn.Notifications.Tests.csproj b/test/Altinn.Notifications.Tests/Altinn.Notifications.Tests.csproj index cbc93649..df7fe583 100644 --- a/test/Altinn.Notifications.Tests/Altinn.Notifications.Tests.csproj +++ b/test/Altinn.Notifications.Tests/Altinn.Notifications.Tests.csproj @@ -56,6 +56,12 @@ PreserveNewest + + PreserveNewest + + + PreserveNewest + diff --git a/test/Altinn.Notifications.Tests/Notifications.Integrations/TestingExtensions/ServiceCollectionExtensionsTests.cs b/test/Altinn.Notifications.Tests/Notifications.Integrations/TestingExtensions/ServiceCollectionExtensionsTests.cs index 81c1288c..88d931b4 100644 --- a/test/Altinn.Notifications.Tests/Notifications.Integrations/TestingExtensions/ServiceCollectionExtensionsTests.cs +++ b/test/Altinn.Notifications.Tests/Notifications.Integrations/TestingExtensions/ServiceCollectionExtensionsTests.cs @@ -28,7 +28,7 @@ public void AddKafkaHealthChecks_KafkaSettingsMissing_ThrowsException() { Environment.SetEnvironmentVariable("KafkaSettings", null); - var config = new ConfigurationBuilder().Build(); + var config = new ConfigurationBuilder().AddEnvironmentVariables().Build(); IServiceCollection services = new ServiceCollection() .AddLogging(); diff --git a/test/Altinn.Notifications.Tests/Notifications/Mocks/Authentication/JwtTokenMock.cs b/test/Altinn.Notifications.Tests/Notifications/Mocks/Authentication/JwtTokenMock.cs index e790ef01..e181c04f 100644 --- a/test/Altinn.Notifications.Tests/Notifications/Mocks/Authentication/JwtTokenMock.cs +++ b/test/Altinn.Notifications.Tests/Notifications/Mocks/Authentication/JwtTokenMock.cs @@ -17,16 +17,16 @@ public static class JwtTokenMock /// Generates a token with a self signed certificate included in the integration test project. /// /// A new token. - public static string GenerateToken(ClaimsPrincipal principal, TimeSpan tokenExipry) + public static string GenerateToken(ClaimsPrincipal principal, TimeSpan tokenExipry, string issuer = "UnitTest") { JwtSecurityTokenHandler tokenHandler = new(); SecurityTokenDescriptor tokenDescriptor = new() { Subject = new ClaimsIdentity(principal.Identity), Expires = DateTime.UtcNow.AddSeconds(tokenExipry.TotalSeconds), - SigningCredentials = GetSigningCredentials(), + SigningCredentials = GetSigningCredentials(issuer), Audience = "altinn.no", - Issuer = "UnitTest" + Issuer = issuer }; SecurityToken token = tokenHandler.CreateToken(tokenDescriptor); @@ -35,9 +35,16 @@ public static string GenerateToken(ClaimsPrincipal principal, TimeSpan tokenExip return tokenstring; } - private static SigningCredentials GetSigningCredentials() + private static SigningCredentials GetSigningCredentials(string issuer) { string certPath = "jwtselfsignedcert.pfx"; + if (!issuer.Equals("UnitTest")) + { + certPath = $"{issuer}-org.pfx"; + + X509Certificate2 certIssuer = new(certPath); + return new X509SigningCredentials(certIssuer, SecurityAlgorithms.RsaSha256); + } X509Certificate2 cert = new(certPath, "qwer1234"); return new X509SigningCredentials(cert, SecurityAlgorithms.RsaSha256); diff --git a/test/Altinn.Notifications.Tests/Notifications/Mocks/Authentication/PublicSigningKeyProviderMock.cs b/test/Altinn.Notifications.Tests/Notifications/Mocks/Authentication/PublicSigningKeyProviderMock.cs new file mode 100644 index 00000000..833cf78b --- /dev/null +++ b/test/Altinn.Notifications.Tests/Notifications/Mocks/Authentication/PublicSigningKeyProviderMock.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography.X509Certificates; +using System.Threading.Tasks; + +using Altinn.Common.AccessToken.Services; + +using Microsoft.IdentityModel.Tokens; + +namespace Altinn.Notifications.Tests.Notifications.Mocks.Authentication +{ + public class PublicSigningKeyProviderMock : IPublicSigningKeyProvider + { + public SigningCredentials GetSigningCredentials() + { + throw new NotImplementedException(); + } + + public Task> GetSigningKeys(string issuer) + { + List signingKeys = new(); + + X509Certificate2 cert = new($"{issuer}-org.pem"); + SecurityKey key = new X509SecurityKey(cert); + + signingKeys.Add(key); + + return Task.FromResult(signingKeys.AsEnumerable()); + } + } +} diff --git a/test/Altinn.Notifications.Tests/Notifications/Utils/PrincipalUtil.cs b/test/Altinn.Notifications.Tests/Notifications/Utils/PrincipalUtil.cs index 85631d89..3aedcd95 100644 --- a/test/Altinn.Notifications.Tests/Notifications/Utils/PrincipalUtil.cs +++ b/test/Altinn.Notifications.Tests/Notifications/Utils/PrincipalUtil.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Security.Claims; +using Altinn.Common.AccessToken.Constants; using Altinn.Notifications.Tests.Notifications.Mocks.Authentication; using AltinnCore.Authentication.Constants; @@ -76,4 +77,19 @@ public static string GetUserToken(int userId, int authenticationLevel = 2, strin return token; } + + public static string GetAccessToken(string issuer, string app) + { + List claims = new() + { + new Claim(AccessTokenClaimTypes.App, app, ClaimValueTypes.String, issuer) + }; + + ClaimsIdentity identity = new("mock"); + identity.AddClaims(claims); + ClaimsPrincipal principal = new(identity); + string token = JwtTokenMock.GenerateToken(principal, new TimeSpan(0, 1, 5), issuer); + + return token; + } } diff --git a/test/Altinn.Notifications.Tests/ttd-org.pem b/test/Altinn.Notifications.Tests/ttd-org.pem new file mode 100644 index 00000000..f2a42d40 --- /dev/null +++ b/test/Altinn.Notifications.Tests/ttd-org.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDAzCCAeugAwIBAgIJANTdO8o3I8x5MA0GCSqGSIb3DQEBCwUAMA4xDDAKBgNV +BAMTA3R0ZDAeFw0yMDA1MjUxMjIxMzdaFw0zMDA1MjQxMjIxMzdaMA4xDDAKBgNV +BAMTA3R0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMcfTsXwwLyC +UkIz06eadWJvG3yrzT+ZB2Oy/WPaZosDnPcnZvCDueN+oy0zTx5TyH5gCi1FvzX2 +7G2eZEKwQaRPv0yuM+McHy1rXxMSOlH/ebP9KJj3FDMUgZl1DCAjJxSAANdTwdrq +ydVg1Crp37AQx8IIEjnBhXsfQh1uPGt1XwgeNyjl00IejxvQOPzd1CofYWwODVtQ +l3PKn1SEgOGcB6wuHNRlnZPCIelQmqxWkcEZiu/NU+kst3NspVUQG2Jf2AF8UWgC +rnrhMQR0Ra1Vi7bWpu6QIKYkN9q0NRHeRSsELOvTh1FgDySYJtNd2xDRSf6IvOiu +tSipl1NZlV0CAwEAAaNkMGIwIAYDVR0OAQH/BBYEFIwq/KbSMzLETdo9NNxj0rz4 +qMqVMAwGA1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgWgMCAGA1UdJQEB/wQWMBQG +CCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQsFAAOCAQEAE56UmH5gEYbe +1kVw7nrfH0R9FyVZGeQQWBn4/6Ifn+eMS9mxqe0Lq74Ue1zEzvRhRRqWYi9JlKNf +7QQNrc+DzCceIa1U6cMXgXKuXquVHLmRfqvKHbWHJfIkaY8Mlfy++77UmbkvIzly +T1HVhKKp6Xx0r5koa6frBh4Xo/vKBlEyQxWLWF0RPGpGErnYIosJ41M3Po3nw3lY +f7lmH47cdXatcntj2Ho/b2wGi9+W29teVCDfHn2/0oqc7K0EOY9c2ODLjUvQyPZR +OD2yykpyh9x/YeYHFDYdLDJ76/kIdxN43kLU4/hTrh9tMb1PZF+/4DshpAlRoQuL +o8I8avQm/A== +-----END CERTIFICATE----- diff --git a/test/Altinn.Notifications.Tests/ttd-org.pfx b/test/Altinn.Notifications.Tests/ttd-org.pfx new file mode 100644 index 00000000..6da835fb Binary files /dev/null and b/test/Altinn.Notifications.Tests/ttd-org.pfx differ diff --git a/test/k6/src/tests/orders_email.js b/test/k6/src/tests/orders_email.js index 93ebe629..393adac2 100644 --- a/test/k6/src/tests/orders_email.js +++ b/test/k6/src/tests/orders_email.js @@ -20,7 +20,7 @@ const orderRequestJson = JSON.parse( ); import { generateJUnitXML, reportPath } from "../report.js"; import { addErrorCount, stopIterationOnFail } from "../errorhandler.js"; -const scopes = "none"; +const scopes = "altinn:notifications.create"; const emailRecipient = __ENV.emailRecipient.toLowerCase(); export const options = {