Skip to content

Commit

Permalink
IAuthenticationContext for current auth information, systemuser sup…
Browse files Browse the repository at this point in the history
…port (#880)
  • Loading branch information
martinothamar authored Feb 6, 2025
1 parent 6457d67 commit 60f6c96
Show file tree
Hide file tree
Showing 119 changed files with 5,254 additions and 1,185 deletions.
2 changes: 1 addition & 1 deletion src/Altinn.App.Api/Altinn.App.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

<ItemGroup>
<PackageReference Include="Altinn.Common.PEP" Version="4.1.2" />
<PackageReference Include="Altinn.Platform.Storage.Interface" Version="4.0.4" />
<PackageReference Include="Altinn.Platform.Storage.Interface" Version="4.0.6" />
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="4.0.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.11.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.11.0" />
Expand Down
15 changes: 8 additions & 7 deletions src/Altinn.App.Api/Controllers/ActionsController.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
using Altinn.App.Api.Extensions;
using Altinn.App.Api.Infrastructure.Filters;
using Altinn.App.Api.Models;
using Altinn.App.Core.Extensions;
using Altinn.App.Core.Features;
using Altinn.App.Core.Features.Action;
using Altinn.App.Core.Features.Auth;
using Altinn.App.Core.Helpers.Serialization;
using Altinn.App.Core.Internal.App;
using Altinn.App.Core.Internal.Data;
Expand Down Expand Up @@ -35,6 +35,7 @@ public class ActionsController : ControllerBase
private readonly IDataClient _dataClient;
private readonly IAppMetadata _appMetadata;
private readonly ModelSerializationService _modelSerialization;
private readonly IAuthenticationContext _authenticationContext;

/// <summary>
/// Create new instance of the <see cref="ActionsController"/> class
Expand All @@ -46,7 +47,8 @@ public ActionsController(
IValidationService validationService,
IDataClient dataClient,
IAppMetadata appMetadata,
ModelSerializationService modelSerialization
ModelSerializationService modelSerialization,
IAuthenticationContext authenticationContext
)
{
_authorization = authorization;
Expand All @@ -56,6 +58,7 @@ ModelSerializationService modelSerialization
_dataClient = dataClient;
_appMetadata = appMetadata;
_modelSerialization = modelSerialization;
_authenticationContext = authenticationContext;
}

/// <summary>
Expand Down Expand Up @@ -109,11 +112,9 @@ public async Task<ActionResult<UserActionResponse>> Perform(
return Conflict($"Process is ended.");
}

int? userId = HttpContext.User.GetUserIdAsInt();
if (userId == null)
{
var currentAuth = _authenticationContext.Current;
if (currentAuth is not Authenticated.User user)
return Unauthorized();
}

bool authorized = await _authorization.AuthorizeAction(
new AppIdentifier(org, app),
Expand All @@ -136,7 +137,7 @@ await _appMetadata.GetApplicationMetadata(),
);
UserActionContext userActionContext = new(
dataMutator,
userId.Value,
user.UserId,
actionRequest.ButtonId,
actionRequest.Metadata,
language
Expand Down
1 change: 0 additions & 1 deletion src/Altinn.App.Api/Controllers/AuthenticationController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
#nullable disable
using Altinn.App.Core.Configuration;
using Altinn.App.Core.Constants;
using Altinn.App.Core.Internal.Auth;
Expand Down
198 changes: 128 additions & 70 deletions src/Altinn.App.Api/Controllers/AuthorizationController.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
using System.Globalization;
using Altinn.App.Core.Configuration;
using Altinn.App.Core.Helpers;
using Altinn.App.Core.Features.Auth;
using Altinn.App.Core.Internal.Auth;
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;
Expand All @@ -19,22 +15,21 @@ namespace Altinn.App.Api.Controllers;
public class AuthorizationController : Controller
{
private readonly IAuthorizationClient _authorization;
private readonly UserHelper _userHelper;
private readonly GeneralSettings _settings;
private readonly IAuthenticationContext _authenticationContext;

/// <summary>
/// Initializes a new instance of the <see cref="AuthorizationController"/> class
/// </summary>
public AuthorizationController(
IAuthorizationClient authorization,
IProfileClient profileClient,
IAltinnPartyClient altinnPartyClientClient,
IOptions<GeneralSettings> settings
IOptions<GeneralSettings> settings,
IAuthenticationContext authenticationContext
)
{
_userHelper = new UserHelper(profileClient, altinnPartyClientClient, settings);
_authorization = authorization;
_settings = settings.Value;
_authenticationContext = authenticationContext;
}

/// <summary>
Expand All @@ -46,14 +41,89 @@ IOptions<GeneralSettings> settings
[HttpGet("{org}/{app}/api/authorization/parties/current")]
public async Task<ActionResult> GetCurrentParty(bool returnPartyObject = false)
{
(Party? currentParty, _) = await GetCurrentPartyAsync(HttpContext);

if (returnPartyObject)
var context = _authenticationContext.Current;
switch (context)
{
return Ok(currentParty);
}
case Authenticated.None:
return Unauthorized();
case Authenticated.User user:
{
var details = await user.LoadDetails(validateSelectedParty: true);
if (details.CanRepresent is not bool canRepresent)
throw new Exception("Couldn't validate selected party");

if (canRepresent)
{
if (returnPartyObject)
{
return Ok(details.SelectedParty);
}

return Ok(details.SelectedParty.PartyId);
}

// Now we know the user can't represent the selected party (reportee)
// so we will automatically switch to the user's own party (from the profile)
var reportee = details.Profile.Party;
if (user.SelectedPartyId != reportee.PartyId)
{
// Setting cookie to partyID of logged in user if it varies from previus value.
Response.Cookies.Append(
_settings.GetAltinnPartyCookieName,
reportee.PartyId.ToString(CultureInfo.InvariantCulture),
new CookieOptions { Domain = _settings.HostName }
);
}

if (returnPartyObject)
{
return Ok(reportee);
}
return Ok(reportee.PartyId);
}
case Authenticated.SelfIdentifiedUser selfIdentified:
{
var details = await selfIdentified.LoadDetails();
if (returnPartyObject)
{
return Ok(details.Party);
}

return Ok(details.Party.PartyId);
}
case Authenticated.Org org:
{
var details = await org.LoadDetails();
if (returnPartyObject)
{
return Ok(details.Party);
}

return Ok(details.Party.PartyId);
}
case Authenticated.ServiceOwner so:
{
var details = await so.LoadDetails();
if (returnPartyObject)
{
return Ok(details.Party);
}

return Ok(details.Party.PartyId);
}
case Authenticated.SystemUser su:
{
var details = await su.LoadDetails();
if (returnPartyObject)
{
return Ok(details.Party);
}

return Ok(currentParty?.PartyId ?? 0);
return Ok(details.Party.PartyId);
}
default:
throw new Exception($"Unknown authentication context: {context.GetType().Name}");
}
}

/// <summary>
Expand Down Expand Up @@ -97,65 +167,53 @@ public async Task<IActionResult> ValidateSelectedParty(int userId, int partyId)
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetRolesForCurrentParty()
{
(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.GetUserRoles(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)
var context = _authenticationContext.Current;
switch (context)
{
bool? isValid = await _authorization.ValidateSelectedParty(userId, userContext.PartyId);
if (isValid != true)
case Authenticated.None:
return Unauthorized();
case Authenticated.User user:
{
// Not valid, fall back to userParty if available
if (userContext.UserParty != null)
{
userContext.Party = userContext.UserParty;
userContext.PartyId = userContext.UserParty.PartyId;
}
else
var details = await user.LoadDetails(validateSelectedParty: true);
if (details.CanRepresent is not bool canRepresent)
throw new Exception("Couldn't validate selected party");
if (!canRepresent)
{
userContext.Party = null;
userContext.PartyId = 0;
// automatically switch to the user's own party
var reportee = details.Profile.Party;
if (user.SelectedPartyId != reportee.PartyId)
{
// Setting cookie to partyID of logged in user if it varies from previus value.
Response.Cookies.Append(
_settings.GetAltinnPartyCookieName,
reportee.PartyId.ToString(CultureInfo.InvariantCulture),
new CookieOptions { Domain = _settings.HostName }
);
}
return Unauthorized();
}
}
}

// 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 Ok(details.Roles);
}
case Authenticated.SelfIdentifiedUser:
{
return Ok(Array.Empty<Role>());
}
case Authenticated.Org:
{
return Ok(Array.Empty<Role>());
}
case Authenticated.ServiceOwner:
{
return Ok(Array.Empty<Role>());
}
case Authenticated.SystemUser:
{
// NOTE: system users can't have Altinn 2 roles, but they will get support for tilgangspakker, as of 26.01.2025
return Ok(Array.Empty<Role>());
}
default:
throw new Exception($"Unknown authentication context: {context.GetType().Name}");
}

return (userContext.Party, userContext);
}
}
Loading

0 comments on commit 60f6c96

Please sign in to comment.