From 917989d6d94b39a300d2cb5ccb9e16e3a160c3b3 Mon Sep 17 00:00:00 2001 From: adamhaeger Date: Mon, 16 Dec 2024 16:26:11 +0100 Subject: [PATCH 1/6] Endpoint working --- .../Controllers/AuthorizationController.cs | 124 +++++++++++------- .../Authorization/AuthorizationClient.cs | 44 +++++++ .../Internal/Auth/IAuthorizationClient.cs | 9 ++ 3 files changed, 131 insertions(+), 46 deletions(-) diff --git a/src/Altinn.App.Api/Controllers/AuthorizationController.cs b/src/Altinn.App.Api/Controllers/AuthorizationController.cs index 7590e0dfc..6c8a5bf8e 100644 --- a/src/Altinn.App.Api/Controllers/AuthorizationController.cs +++ b/src/Altinn.App.Api/Controllers/AuthorizationController.cs @@ -5,6 +5,8 @@ using Altinn.App.Core.Internal.Profile; using Altinn.App.Core.Internal.Registers; using Altinn.App.Core.Models; +using Altinn.Platform.Register.Models; +using Authorization.Platform.Authorization.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; @@ -44,57 +46,14 @@ IOptions settings [HttpGet("{org}/{app}/api/authorization/parties/current")] public async Task GetCurrentParty(bool returnPartyObject = false) { - UserContext userContext = await _userHelper.GetUserContext(HttpContext); - int userId = userContext.UserId; - - // If selected party is different than party for user self need to verify - if (userContext.UserParty == null || userContext.PartyId != userContext.UserParty.PartyId) - { - bool? isValid = await _authorization.ValidateSelectedParty(userId, userContext.PartyId); - - if (isValid == true) - { - if (returnPartyObject) - { - return Ok(userContext.Party); - } - - return Ok(userContext.PartyId); - } - else if (userContext.UserParty != null) - { - userContext.Party = userContext.UserParty; - userContext.PartyId = userContext.UserParty.PartyId; - } - else - { - userContext.Party = null; - userContext.PartyId = 0; - } - } - - string? cookieValue = Request.Cookies[_settings.GetAltinnPartyCookieName]; - if (!int.TryParse(cookieValue, out int partyIdFromCookie)) - { - partyIdFromCookie = 0; - } - - // Setting cookie to partyID of logged in user if it varies from previus value. - if (partyIdFromCookie != userContext.PartyId) - { - Response.Cookies.Append( - _settings.GetAltinnPartyCookieName, - userContext.PartyId.ToString(CultureInfo.InvariantCulture), - new CookieOptions { Domain = _settings.HostName } - ); - } + Party? currentParty = await GetCurrentPartyAsync(HttpContext); if (returnPartyObject) { - return Ok(userContext.Party); + return Ok(currentParty); } - return Ok(userContext.PartyId); + return Ok(currentParty?.PartyId ?? 0); } /// @@ -123,4 +82,77 @@ public async Task ValidateSelectedParty(int userId, int partyId) return StatusCode(500, $"Something went wrong when trying to validate party {partyId} for user {userId}"); } } + + /// + /// Fetches roles for current party. + /// + /// Boolean indicating if the selected party is valid. + [Authorize] + [HttpGet("{org}/{app}/api/authorization/roles")] + public async Task FetchRolesForCurrentParty() + { + Party? currentParty = await GetCurrentPartyAsync(HttpContext); + UserContext userContext = await _userHelper.GetUserContext(HttpContext); + int userId = userContext.UserId; + + if (currentParty == null) + { + return BadRequest("Both userId and partyId must be provided."); + } + + List roles = await _authorization.GetUserRolesAsync(userId, currentParty.PartyId); + + // TODO: implement actual logic here using currentParty + // For now, return false as before + return Ok(roles); + } + + /// + /// Helper method to retrieve the current party from the HTTP context. + /// + /// The current HttpContext. + /// The current party or null if none could be determined. + private async Task GetCurrentPartyAsync(HttpContext context) + { + UserContext userContext = await _userHelper.GetUserContext(context); + int userId = userContext.UserId; + + // If selected party is different than party for user self need to verify + if (userContext.UserParty == null || userContext.PartyId != userContext.UserParty.PartyId) + { + bool? isValid = await _authorization.ValidateSelectedParty(userId, userContext.PartyId); + if (isValid != true) + { + // Not valid, fall back to userParty if available + if (userContext.UserParty != null) + { + userContext.Party = userContext.UserParty; + userContext.PartyId = userContext.UserParty.PartyId; + } + else + { + userContext.Party = null; + userContext.PartyId = 0; + } + } + } + + // Sync cookie if needed + string? cookieValue = Request.Cookies[_settings.GetAltinnPartyCookieName]; + if (!int.TryParse(cookieValue, out int partyIdFromCookie)) + { + partyIdFromCookie = 0; + } + + if (partyIdFromCookie != userContext.PartyId) + { + Response.Cookies.Append( + _settings.GetAltinnPartyCookieName, + userContext.PartyId.ToString(CultureInfo.InvariantCulture), + new CookieOptions { Domain = _settings.HostName } + ); + } + + return userContext.Party; + } } diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs index f86ad9976..9bb4f849f 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs @@ -13,6 +13,7 @@ using Altinn.Platform.Register.Models; using Altinn.Platform.Storage.Interface.Models; using AltinnCore.Authentication.Utils; +using Authorization.Platform.Authorization.Models; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -179,4 +180,47 @@ List actions } return MultiDecisionHelper.ValidatePdpMultiDecision(actionsResult, response.Response, user); } + + /// + /// Retrieves roles for a user on a specified party. + /// + /// The user id. + /// The user party id. + /// A list of roles for the user on the specified party. + public async Task?> GetUserRolesAsync(int userId, int userPartyId) + { + List? roles = null; + string apiUrl = $"roles?coveredByUserId={userId}&offeredByPartyId={userPartyId}"; + string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName); + + try + { + HttpResponseMessage response = await _client.GetAsync(token, apiUrl); + if (response.IsSuccessStatusCode) + { + string responseContent = await response.Content.ReadAsStringAsync(); + roles = JsonConvert.DeserializeObject>(responseContent); + } + else + { + _logger.LogError( + "Failed to retrieve roles for userId {UserId} and partyId {PartyId}. StatusCode: {StatusCode}", + userId, + userPartyId, + response.StatusCode + ); + } + } + catch (Exception ex) + { + _logger.LogError( + ex, + "An error occurred while retrieving roles for userId {UserId} and partyId {PartyId}", + userId, + userPartyId + ); + } + + return roles; + } } diff --git a/src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs b/src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs index 7cefe1a91..7dd2a9df0 100644 --- a/src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs +++ b/src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs @@ -2,6 +2,7 @@ using Altinn.App.Core.Models; using Altinn.Platform.Register.Models; using Altinn.Platform.Storage.Interface.Models; +using Authorization.Platform.Authorization.Models; namespace Altinn.App.Core.Internal.Auth; @@ -50,4 +51,12 @@ Task AuthorizeAction( /// /// Task> AuthorizeActions(Instance instance, ClaimsPrincipal user, List actions); + + /// + /// Retrieves roles for a user on a specified party. + /// + /// The user id. + /// The user party id. + /// A list of roles for the user on the specified party. + Task?> GetUserRolesAsync(int userId, int userPartyId); } From fbef215233911cb4a0d20d0aa039cc12c0d922b3 Mon Sep 17 00:00:00 2001 From: adamhaeger Date: Tue, 17 Dec 2024 09:42:02 +0100 Subject: [PATCH 2/6] Clenaup --- src/Altinn.App.Api/Controllers/AuthorizationController.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/Altinn.App.Api/Controllers/AuthorizationController.cs b/src/Altinn.App.Api/Controllers/AuthorizationController.cs index 6c8a5bf8e..91c381fbe 100644 --- a/src/Altinn.App.Api/Controllers/AuthorizationController.cs +++ b/src/Altinn.App.Api/Controllers/AuthorizationController.cs @@ -89,7 +89,7 @@ public async Task ValidateSelectedParty(int userId, int partyId) /// Boolean indicating if the selected party is valid. [Authorize] [HttpGet("{org}/{app}/api/authorization/roles")] - public async Task FetchRolesForCurrentParty() + public async Task GetRolesForCurrentParty() { Party? currentParty = await GetCurrentPartyAsync(HttpContext); UserContext userContext = await _userHelper.GetUserContext(HttpContext); @@ -101,9 +101,6 @@ public async Task FetchRolesForCurrentParty() } List roles = await _authorization.GetUserRolesAsync(userId, currentParty.PartyId); - - // TODO: implement actual logic here using currentParty - // For now, return false as before return Ok(roles); } From 1ca6d9a620389dd13738d3c5abd57ba8e5fc2f37 Mon Sep 17 00:00:00 2001 From: adamhaeger Date: Tue, 17 Dec 2024 10:05:55 +0100 Subject: [PATCH 3/6] returning userContext from getCurrentParty --- .../Controllers/AuthorizationController.cs | 18 +++--- .../Authorization/AuthorizationClient.cs | 55 ++++++++++--------- .../Internal/Auth/IAuthorizationClient.cs | 2 +- 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/Altinn.App.Api/Controllers/AuthorizationController.cs b/src/Altinn.App.Api/Controllers/AuthorizationController.cs index 91c381fbe..f6401ecd4 100644 --- a/src/Altinn.App.Api/Controllers/AuthorizationController.cs +++ b/src/Altinn.App.Api/Controllers/AuthorizationController.cs @@ -46,7 +46,7 @@ IOptions settings [HttpGet("{org}/{app}/api/authorization/parties/current")] public async Task GetCurrentParty(bool returnPartyObject = false) { - Party? currentParty = await GetCurrentPartyAsync(HttpContext); + (Party? currentParty, _) = await GetCurrentPartyAsync(HttpContext); if (returnPartyObject) { @@ -86,30 +86,30 @@ public async Task ValidateSelectedParty(int userId, int partyId) /// /// Fetches roles for current party. /// - /// Boolean indicating if the selected party is valid. + /// List of roles for the current user and party. [Authorize] [HttpGet("{org}/{app}/api/authorization/roles")] public async Task GetRolesForCurrentParty() { - Party? currentParty = await GetCurrentPartyAsync(HttpContext); - UserContext userContext = await _userHelper.GetUserContext(HttpContext); - int userId = userContext.UserId; + (Party? currentParty, UserContext userContext) = await GetCurrentPartyAsync(HttpContext); if (currentParty == null) { return BadRequest("Both userId and partyId must be provided."); } + int userId = userContext.UserId; List roles = await _authorization.GetUserRolesAsync(userId, currentParty.PartyId); + return Ok(roles); } /// - /// Helper method to retrieve the current party from the HTTP context. + /// Helper method to retrieve the current party and user context from the HTTP context. /// /// The current HttpContext. - /// The current party or null if none could be determined. - private async Task GetCurrentPartyAsync(HttpContext context) + /// A tuple containing the current party and user context. + private async Task<(Party? party, UserContext userContext)> GetCurrentPartyAsync(HttpContext context) { UserContext userContext = await _userHelper.GetUserContext(context); int userId = userContext.UserId; @@ -150,6 +150,6 @@ public async Task GetRolesForCurrentParty() ); } - return userContext.Party; + return (userContext.Party, userContext); } } diff --git a/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs b/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs index 9bb4f849f..e9c5af068 100644 --- a/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs +++ b/src/Altinn.App.Core/Infrastructure/Clients/Authorization/AuthorizationClient.cs @@ -187,40 +187,45 @@ List actions /// The user id. /// The user party id. /// A list of roles for the user on the specified party. - public async Task?> GetUserRolesAsync(int userId, int userPartyId) - { - List? roles = null; - string apiUrl = $"roles?coveredByUserId={userId}&offeredByPartyId={userPartyId}"; - string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName); +public async Task> GetUserRolesAsync(int userId, int userPartyId) +{ + List roles = new(); + string apiUrl = $"roles?coveredByUserId={userId}&offeredByPartyId={userPartyId}"; + string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _settings.RuntimeCookieName); - try + try + { + HttpResponseMessage response = await _client.GetAsync(token, apiUrl); + if (response.IsSuccessStatusCode) { - HttpResponseMessage response = await _client.GetAsync(token, apiUrl); - if (response.IsSuccessStatusCode) - { - string responseContent = await response.Content.ReadAsStringAsync(); - roles = JsonConvert.DeserializeObject>(responseContent); - } - else + string responseContent = await response.Content.ReadAsStringAsync(); + var deserialized = JsonConvert.DeserializeObject>(responseContent); + if (deserialized is not null) { - _logger.LogError( - "Failed to retrieve roles for userId {UserId} and partyId {PartyId}. StatusCode: {StatusCode}", - userId, - userPartyId, - response.StatusCode - ); + roles = deserialized; } } - catch (Exception ex) + else { _logger.LogError( - ex, - "An error occurred while retrieving roles for userId {UserId} and partyId {PartyId}", + "Failed to retrieve roles for userId {UserId} and partyId {PartyId}. StatusCode: {StatusCode}", userId, - userPartyId + userPartyId, + response.StatusCode ); } - - return roles; } + catch (Exception ex) + { + _logger.LogError( + ex, + "An error occurred while retrieving roles for userId {UserId} and partyId {PartyId}", + userId, + userPartyId + ); + } + + return roles; +} + } diff --git a/src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs b/src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs index 7dd2a9df0..e1eae0420 100644 --- a/src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs +++ b/src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs @@ -58,5 +58,5 @@ Task AuthorizeAction( /// The user id. /// The user party id. /// A list of roles for the user on the specified party. - Task?> GetUserRolesAsync(int userId, int userPartyId); + Task> GetUserRolesAsync(int userId, int userPartyId); } From c6780df1e014658384eb2d13a473c6c8028d5f05 Mon Sep 17 00:00:00 2001 From: adamhaeger Date: Tue, 17 Dec 2024 10:22:04 +0100 Subject: [PATCH 4/6] Added mockdata --- .../Mocks/AuthorizationMock.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs b/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs index 25c51b7da..98d1b7044 100644 --- a/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs +++ b/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs @@ -3,6 +3,8 @@ using Altinn.App.Core.Models; using Altinn.Platform.Register.Models; using Altinn.Platform.Storage.Interface.Models; +using Authorization.Platform.Authorization.Models; +using Microsoft.AspNetCore.Http.HttpResults; namespace Altinn.App.Api.Tests.Mocks; @@ -69,4 +71,16 @@ List actions return authorizedActions; } + + public async Task> GetUserRolesAsync(int userId, int userPartyId) + { + await Task.CompletedTask; + List roles = new List + { + new Role { Type = "altinn", Value = "bobet" }, + new Role { Type = "altinn", Value = "bobes" } + }; + + return roles; + } } From 944a197d037fea95b965e25a06266726364f5b34 Mon Sep 17 00:00:00 2001 From: adamhaeger Date: Tue, 17 Dec 2024 10:27:13 +0100 Subject: [PATCH 5/6] ran csharpier --- test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs b/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs index 98d1b7044..56edbcc5e 100644 --- a/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs +++ b/test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs @@ -78,7 +78,7 @@ public async Task> GetUserRolesAsync(int userId, int userPartyId) List roles = new List { new Role { Type = "altinn", Value = "bobet" }, - new Role { Type = "altinn", Value = "bobes" } + new Role { Type = "altinn", Value = "bobes" }, }; return roles; From bfed4bbe9d90fbd08ea2d171832a019c8c2fef08 Mon Sep 17 00:00:00 2001 From: adamhaeger Date: Tue, 17 Dec 2024 12:19:42 +0100 Subject: [PATCH 6/6] updated swagger defs --- .../Altinn.App.Api.Tests/OpenApi/swagger.json | 24 ++++++++++++------- .../Altinn.App.Api.Tests/OpenApi/swagger.yaml | 13 ++++++---- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/test/Altinn.App.Api.Tests/OpenApi/swagger.json b/test/Altinn.App.Api.Tests/OpenApi/swagger.json index 63f8ea339..0625e8143 100644 --- a/test/Altinn.App.Api.Tests/OpenApi/swagger.json +++ b/test/Altinn.App.Api.Tests/OpenApi/swagger.json @@ -1804,7 +1804,7 @@ "description": "OK" }, "425": { - "description": "Too Early" + "description": "Client Error" }, "500": { "description": "Internal Server Error" @@ -5259,7 +5259,8 @@ "actions": { "type": "object", "additionalProperties": { - "type": "boolean" + "type": "boolean", + "nullable": true }, "nullable": true }, @@ -5365,8 +5366,7 @@ "type": "boolean" }, "allowInSubform": { - "type": "boolean", - "deprecated": true + "type": "boolean" }, "shadowFields": { "$ref": "#/components/schemas/ShadowFields" @@ -5464,7 +5464,7 @@ "copyInstanceSettings": { "$ref": "#/components/schemas/CopyInstanceSettings" }, - "storageAccountNumber": { + "storageContainerNumber": { "type": "integer", "format": "int32", "nullable": true @@ -5479,7 +5479,8 @@ "features": { "type": "object", "additionalProperties": { - "type": "boolean" + "type": "boolean", + "nullable": true }, "nullable": true }, @@ -6502,8 +6503,12 @@ "JsonNodeOptions": { "type": "object", "properties": { - "propertyNameCaseInsensitive": { - "type": "boolean" + "hasValue": { + "type": "boolean", + "readOnly": true + }, + "value": { + "$ref": "#/components/schemas/JsonNodeOptions" } }, "additionalProperties": false @@ -7344,7 +7349,8 @@ "items": { "$ref": "#/components/schemas/ValidationIssueWithSource" } - } + }, + "nullable": true }, "nullable": true }, diff --git a/test/Altinn.App.Api.Tests/OpenApi/swagger.yaml b/test/Altinn.App.Api.Tests/OpenApi/swagger.yaml index b51ddaa7e..591b4f8cb 100644 --- a/test/Altinn.App.Api.Tests/OpenApi/swagger.yaml +++ b/test/Altinn.App.Api.Tests/OpenApi/swagger.yaml @@ -1101,7 +1101,7 @@ paths: '200': description: OK '425': - description: Too Early + description: Client Error '500': description: Internal Server Error '401': @@ -3215,6 +3215,7 @@ components: type: object additionalProperties: type: boolean + nullable: true nullable: true userActions: type: array @@ -3290,7 +3291,6 @@ components: type: boolean allowInSubform: type: boolean - deprecated: true shadowFields: $ref: '#/components/schemas/ShadowFields' additionalProperties: false @@ -3361,7 +3361,7 @@ components: $ref: '#/components/schemas/MessageBoxConfig' copyInstanceSettings: $ref: '#/components/schemas/CopyInstanceSettings' - storageAccountNumber: + storageContainerNumber: type: integer format: int32 nullable: true @@ -3374,6 +3374,7 @@ components: type: object additionalProperties: type: boolean + nullable: true nullable: true logo: $ref: '#/components/schemas/Logo' @@ -4114,8 +4115,11 @@ components: JsonNodeOptions: type: object properties: - propertyNameCaseInsensitive: + hasValue: type: boolean + readOnly: true + value: + $ref: '#/components/schemas/JsonNodeOptions' additionalProperties: false JsonPatch: type: object @@ -4723,6 +4727,7 @@ components: type: array items: $ref: '#/components/schemas/ValidationIssueWithSource' + nullable: true nullable: true clientActions: type: array