Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Endpoint for fetching current party roles #983

Merged
merged 15 commits into from
Dec 20, 2024
121 changes: 75 additions & 46 deletions src/Altinn.App.Api/Controllers/AuthorizationController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -44,57 +46,14 @@ IOptions<GeneralSettings> settings
[HttpGet("{org}/{app}/api/authorization/parties/current")]
public async Task<ActionResult> 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);
}

/// <summary>
Expand Down Expand Up @@ -123,4 +82,74 @@ public async Task<IActionResult> ValidateSelectedParty(int userId, int partyId)
return StatusCode(500, $"Something went wrong when trying to validate party {partyId} for user {userId}");
}
}

/// <summary>
/// Fetches roles for current party.
/// </summary>
/// <returns>List of roles for the current user and party.</returns>
[Authorize]
[HttpGet("{org}/{app}/api/authorization/roles")]
public async Task<IActionResult> GetRolesForCurrentParty()
adamhaeger marked this conversation as resolved.
Show resolved Hide resolved
{
(Party? currentParty, UserContext userContext) = await GetCurrentPartyAsync(HttpContext);

if (currentParty == null)
{
return BadRequest("Both userId and partyId must be provided.");
}

int userId = userContext.UserId;
IEnumerable<Role> roles = await _authorization.GetUserRolesAsync(userId, currentParty.PartyId);

return Ok(roles);
}

/// <summary>
/// Helper method to retrieve the current party and user context from the HTTP context.
/// </summary>
/// <param name="context">The current HttpContext.</param>
/// <returns>A tuple containing the current party and user context.</returns>
private async Task<(Party? party, UserContext userContext)> 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, userContext);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ partial class Telemetry
return activity;
}

internal Activity? StartClientGetPartyRoleListActivity(int userId, int partyId)
{
var activity = ActivitySource.StartActivity($"{Prefix}.GetUserRolesAsync");
activity?.SetUserPartyId(partyId);
activity?.SetUserId(userId);

return activity;
}

internal Activity? StartClientValidateSelectedPartyActivity(int userId, int partyId)
{
var activity = ActivitySource.StartActivity($"{Prefix}.ValidateSelectedParty");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -179,4 +180,49 @@ List<string> actions
}
return MultiDecisionHelper.ValidatePdpMultiDecision(actionsResult, response.Response, user);
}

/// <summary>
/// Retrieves roles for a user on a specified party.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="userPartyId">The user party id.</param>
/// <returns>A list of roles for the user on the specified party.</returns>
public async Task<IEnumerable<Role>> GetUserRolesAsync(int userId, int userPartyId)
{
using var activity = _telemetry?.StartClientGetPartyRoleListActivity(userId, userPartyId);

List<Role> roles = new();
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();
var deserialized = JsonConvert.DeserializeObject<List<Role>>(responseContent);
if (deserialized is not null)
{
roles = deserialized;
}
}
else
{
throw new Exception("Unexpected response from auth API:" + response.StatusCode);
}
}
catch (Exception ex)
{
_logger.LogError(
ex,
"An error occurred while retrieving roles for userId {UserId} and partyId {PartyId}",
userId,
userPartyId
);
throw;
}

return roles;
}
}
9 changes: 9 additions & 0 deletions src/Altinn.App.Core/Internal/Auth/IAuthorizationClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -50,4 +51,12 @@ Task<bool> AuthorizeAction(
/// <param name="actions"></param>
/// <returns></returns>
Task<Dictionary<string, bool>> AuthorizeActions(Instance instance, ClaimsPrincipal user, List<string> actions);

/// <summary>
/// Retrieves roles for a user on a specified party.
/// </summary>
/// <param name="userId">The user id.</param>
/// <param name="userPartyId">The user party id.</param>
/// <returns>A list of roles for the user on the specified party.</returns>
Task<IEnumerable<Role>> GetUserRolesAsync(int userId, int userPartyId);
martinothamar marked this conversation as resolved.
Show resolved Hide resolved
}
14 changes: 14 additions & 0 deletions test/Altinn.App.Api.Tests/Mocks/AuthorizationMock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -69,4 +71,16 @@ List<string> actions

return authorizedActions;
}

public async Task<IEnumerable<Role>> GetUserRolesAsync(int userId, int userPartyId)
{
await Task.CompletedTask;
List<Role> roles = new List<Role>
{
new Role { Type = "altinn", Value = "bobet" },
new Role { Type = "altinn", Value = "bobes" },
};

return roles;
}
}
30 changes: 30 additions & 0 deletions test/Altinn.App.Api.Tests/OpenApi/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,36 @@
}
}
},
"/{org}/{app}/api/authorization/roles": {
"get": {
"tags": [
"Authorization"
],
"parameters": [
{
"name": "org",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "app",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "OK"
adamhaeger marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
},
"/{org}/{app}/instances/{instanceOwnerPartyId}/{instanceGuid}/data": {
"post": {
"tags": [
Expand Down
18 changes: 18 additions & 0 deletions test/Altinn.App.Api.Tests/OpenApi/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,24 @@ paths:
responses:
'200':
description: OK
'/{org}/{app}/api/authorization/roles':
get:
tags:
- Authorization
parameters:
- name: org
in: path
required: true
schema:
type: string
- name: app
in: path
required: true
schema:
type: string
responses:
'200':
description: OK
'/{org}/{app}/instances/{instanceOwnerPartyId}/{instanceGuid}/data':
post:
tags:
Expand Down
Loading
Loading