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

Add support for system user #599

Merged
merged 17 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 2 additions & 6 deletions src/Storage/Altinn.Platform.Storage.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Altinn.Common.AccessToken" Version="4.5.4" />

<PackageReference Include="Altinn.Common.AccessTokenClient" Version="3.0.10" />
<PackageReference Include="Altinn.Common.AccessToken" Version="4.5.5" />
<PackageReference Include="Altinn.Common.AccessTokenClient" Version="3.0.11" />
<PackageReference Include="Altinn.Platform.Models" Version="1.6.1" />
<PackageReference Include="Altinn.Platform.Storage.Interface" Version="4.0.5" />
<PackageReference Include="Azure.Extensions.AspNetCore.Configuration.Secrets" Version="1.3.2" />
Expand All @@ -20,11 +19,8 @@
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
<PackageReference Include="Altinn.Common.PEP" Version="4.1.2" />
<PackageReference Include="Azure.Storage.Queues" Version="12.21.0" />

<PackageReference Include="JWTCookieAuthentication" Version="3.0.1" />

<PackageReference Include="Microsoft.ApplicationInsights.AspNetCore" Version="2.22.0" />

<PackageReference Include="PDFsharp" Version="6.1.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.2.0" />
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="7.2.0" />
Expand Down
611 changes: 301 additions & 310 deletions src/Storage/Authorization/AuthorizationService.cs

Large diffs are not rendered by default.

40 changes: 17 additions & 23 deletions src/Storage/Authorization/ClaimsPrincipalProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,24 @@

using Microsoft.AspNetCore.Http;

namespace Altinn.Platform.Storage.Authorization
{
/// <summary>
/// Represents an implementation of <see cref="IClaimsPrincipalProvider"/> using the HttpContext to obtain
/// the current claims principal needed for the application to make calls to other services.
/// </summary>
[ExcludeFromCodeCoverage]
public class ClaimsPrincipalProvider : IClaimsPrincipalProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
namespace Altinn.Platform.Storage.Authorization;

/// <summary>
/// Initializes a new instance of the <see cref="ClaimsPrincipalProvider"/> class.
/// </summary>
/// <param name="httpContextAccessor">The http context accessor</param>
public ClaimsPrincipalProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
/// <summary>
/// Represents an implementation of <see cref="IClaimsPrincipalProvider"/> using the HttpContext to obtain
/// the current claims principal needed for the application to make calls to other services.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="ClaimsPrincipalProvider"/> class.
/// </remarks>
/// <param name="httpContextAccessor">The http context accessor</param>
[ExcludeFromCodeCoverage]
public class ClaimsPrincipalProvider(IHttpContextAccessor httpContextAccessor) : IClaimsPrincipalProvider
{
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;

/// <inheritdoc/>
public ClaimsPrincipal GetUser()
{
return _httpContextAccessor.HttpContext.User;
}
/// <inheritdoc/>
public ClaimsPrincipal GetUser()
{
return _httpContextAccessor.HttpContext.User;
}
}
255 changes: 120 additions & 135 deletions src/Storage/Authorization/StorageAccessHandler.cs
Original file line number Diff line number Diff line change
@@ -1,190 +1,175 @@
using System;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Altinn.Authorization.ABAC.Xacml.JsonProfile;

using Altinn.Common.PEP.Authorization;
using Altinn.Common.PEP.Configuration;
using Altinn.Common.PEP.Constants;
using Altinn.Common.PEP.Helpers;
using Altinn.Common.PEP.Interfaces;

using Altinn.Platform.Storage.Interface.Models;
using Altinn.Platform.Storage.Repository;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

using Newtonsoft.Json;

namespace Altinn.Platform.Storage.Authorization
namespace Altinn.Platform.Storage.Authorization;

/// <summary>
/// AuthorizationHandler that is created for handling access to storage and supporting caching of decisions from PDP
/// Authorizes based om AppAccessRequirement and app id from route
/// <see href="https://docs.asp.net/en/latest/security/authorization/policies.html"/> for details about authorization
/// in asp.net core
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="StorageAccessHandler"/> class.
/// </remarks>
/// <param name="httpContextAccessor">The http context accessor</param>
/// <param name="pdp">The pdp</param>
/// <param name="pepSettings">The settings for pep</param>
/// <param name="logger">The logger. </param>
/// <param name="instanceRepository">The instance repository</param>
/// <param name="memoryCache">The memory cache</param>
public class StorageAccessHandler(
IHttpContextAccessor httpContextAccessor,
IPDP pdp,
IOptions<PepSettings> pepSettings,
ILogger<StorageAccessHandler> logger,
IInstanceRepository instanceRepository,
IMemoryCache memoryCache) : AuthorizationHandler<AppAccessRequirement>
{
private readonly IInstanceRepository _instanceRepository = instanceRepository;
private readonly IHttpContextAccessor _httpContextAccessor = httpContextAccessor;
private readonly IPDP _pdp = pdp;
private readonly ILogger _logger = logger;
private readonly IMemoryCache _memoryCache = memoryCache;
private readonly PepSettings _pepSettings = pepSettings.Value;

/// <summary>
/// AuthorizationHandler that is created for handling access to storage and supporting caching of decisions from PDP
/// Authorizes based om AppAccessRequirement and app id from route
/// <see href="https://docs.asp.net/en/latest/security/authorization/policies.html"/> for details about authorization
/// in asp.net core
/// This method authorize access bases on context and requirement
/// Is triggered by annotation on MVC action and setup in startup.
/// </summary>
public class StorageAccessHandler : AuthorizationHandler<AppAccessRequirement>
/// <param name="context">The context</param>
/// <param name="requirement">The requirement</param>
/// <returns>A Task</returns>
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AppAccessRequirement requirement)
{
private readonly IInstanceRepository _instanceRepository;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IPDP _pdp;
private readonly ILogger _logger;
private readonly IMemoryCache _memoryCache;
private readonly PepSettings _pepSettings;

/// <summary>
/// Initializes a new instance of the <see cref="StorageAccessHandler"/> class.
/// </summary>
/// <param name="httpContextAccessor">The http context accessor</param>
/// <param name="pdp">The pdp</param>
/// <param name="pepSettings">The settings for pep</param>
/// <param name="logger">The logger. </param>
/// <param name="instanceRepository">The instance repository</param>
/// <param name="memoryCache">The memory cache</param>
public StorageAccessHandler(
IHttpContextAccessor httpContextAccessor,
IPDP pdp,
IOptions<PepSettings> pepSettings,
ILogger<StorageAccessHandler> logger,
IInstanceRepository instanceRepository,
IMemoryCache memoryCache)
{
_httpContextAccessor = httpContextAccessor;
_pdp = pdp;
_logger = logger;
_pepSettings = pepSettings.Value;
_instanceRepository = instanceRepository;
_memoryCache = memoryCache;
}
XacmlJsonRequestRoot request =
DecisionHelper.CreateDecisionRequest(context, requirement, _httpContextAccessor.HttpContext.GetRouteData());

/// <summary>
/// This method authorize access bases on context and requirement
/// Is triggered by annotation on MVC action and setup in startup.
/// </summary>
/// <param name="context">The context</param>
/// <param name="requirement">The requirement</param>
/// <returns>A Task</returns>
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AppAccessRequirement requirement)
{
XacmlJsonRequestRoot request = DecisionHelper.CreateDecisionRequest(context, requirement, _httpContextAccessor.HttpContext.GetRouteData());

_logger.LogInformation("// Storage PEP // AppAccessHandler // Request sent: {request}", JsonConvert.SerializeObject(request));
_logger.LogInformation("// Storage PEP // AppAccessHandler // Request sent: {Request}", JsonConvert.SerializeObject(request));

XacmlJsonResponse response;

// Get The instance to enrich the request
Instance instance = await GetInstance(request);
if (instance != null)
{
AuthorizationService.EnrichXacmlJsonRequest(request, instance);
response = await GetDecisionForRequest(request);
}
else
{
response = await _pdp.GetDecisionForRequest(request);
}
XacmlJsonResponse response;

if (response?.Response == null)
{
throw new Exception("Response is null from PDP");
}

if (!DecisionHelper.ValidatePdpDecision(response.Response, context.User))
{
context.Fail();
}

context.Succeed(requirement);
await Task.CompletedTask;
Instance instance = await GetInstance(request);
if (instance != null)
{
AuthorizationService.EnrichXacmlJsonRequest(request, instance);
response = await GetDecisionForRequest(request);
}
else
{
response = await _pdp.GetDecisionForRequest(request);
}

private async Task<XacmlJsonResponse> GetDecisionForRequest(XacmlJsonRequestRoot request)
if (!DecisionHelper.ValidatePdpDecision(response?.Response, context.User))
{
string cacheKey = GetCacheKeyForDecisionRequest(request);
context.Fail();
}

if (!_memoryCache.TryGetValue(cacheKey, out XacmlJsonResponse response))
{
// Key not in cache, so get decisin from PDP.
response = await _pdp.GetDecisionForRequest(request);
context.Succeed(requirement);
}

// Set the cache options
MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
.SetAbsoluteExpiration(new TimeSpan(0, _pepSettings.PdpDecisionCachingTimeout, 0));
private async Task<XacmlJsonResponse> GetDecisionForRequest(XacmlJsonRequestRoot request)
{
string cacheKey = GetCacheKeyForDecisionRequest(request);

_memoryCache.Set(cacheKey, response, cacheEntryOptions);
}
if (!_memoryCache.TryGetValue(cacheKey, out XacmlJsonResponse response))
{
response = await _pdp.GetDecisionForRequest(request);

MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
.SetAbsoluteExpiration(new TimeSpan(0, _pepSettings.PdpDecisionCachingTimeout, 0));

return response;
_memoryCache.Set(cacheKey, response, cacheEntryOptions);
}

/// <summary>
/// Get the instance from database based on request
/// </summary>
/// <param name="request">The request</param>
/// <returns>The instance identified by information in the request.</returns>
private async Task<Instance> GetInstance(XacmlJsonRequestRoot request)
return response;
}

/// <summary>
/// Get the instance from database based on request
/// </summary>
/// <param name="request">The request</param>
/// <returns>The instance identified by information in the request.</returns>
private async Task<Instance> GetInstance(XacmlJsonRequestRoot request)
{
string instanceId = string.Empty;
foreach (XacmlJsonCategory category in request.Request.Resource)
{
string instanceId = string.Empty;
foreach (XacmlJsonCategory category in request.Request.Resource)
foreach (var atr in category.Attribute)

Check warning on line 120 in src/Storage/Authorization/StorageAccessHandler.cs

View workflow job for this annotation

GitHub Actions / Build, test & analyze

Loops should be simplified using the "Where" LINQ method (https://rules.sonarsource.com/csharp/RSPEC-3267)
{
foreach (var atr in category.Attribute)
if (atr.AttributeId.Equals(AltinnXacmlUrns.InstanceId))
{
if (atr.AttributeId.Equals(AltinnXacmlUrns.InstanceId))
{
instanceId = atr.Value;
break;
}
instanceId = atr.Value;
break;
}
}
}

if (string.IsNullOrEmpty(instanceId))
{
return null;
}

(Instance instance, _) = await _instanceRepository.GetOne(Guid.Parse(instanceId.Split("/")[1]), false);
return instance;
if (string.IsNullOrEmpty(instanceId))
{
return null;
}

/// <summary>
/// This method creates a uniqe cache key based on all relevant attributes in a decision request
/// </summary>
/// <param name="request">The decision requonst</param>
/// <returns>The cache key</returns>
private static string GetCacheKeyForDecisionRequest(XacmlJsonRequestRoot request)
(Instance instance, _) = await _instanceRepository.GetOne(Guid.Parse(instanceId.Split("/")[1]), false);
return instance;
}

/// <summary>
/// This method creates a uniqe cache key based on all relevant attributes in a decision request
/// </summary>
/// <param name="request">The decision requonst</param>
/// <returns>The cache key</returns>
private static string GetCacheKeyForDecisionRequest(XacmlJsonRequestRoot request)
{
StringBuilder resourceKey = new StringBuilder();
foreach (XacmlJsonCategory category in request.Request.Resource)
{
StringBuilder resourceKey = new StringBuilder();
foreach (XacmlJsonCategory category in request.Request.Resource)
foreach (XacmlJsonAttribute atr in category.Attribute)
{
foreach (XacmlJsonAttribute atr in category.Attribute)
{
resourceKey.Append(atr.AttributeId + ":" + atr.Value + ";");
}
resourceKey.Append(atr.AttributeId + ":" + atr.Value + ";");
}
}

StringBuilder subjectKey = new StringBuilder();
foreach (XacmlJsonCategory category in request.Request.AccessSubject)
StringBuilder subjectKey = new StringBuilder();
foreach (XacmlJsonCategory category in request.Request.AccessSubject)
{
foreach (XacmlJsonAttribute atr in category.Attribute)
{
foreach (XacmlJsonAttribute atr in category.Attribute)
{
subjectKey.Append(atr.AttributeId + ":" + atr.Value + ";");
}
subjectKey.Append(atr.AttributeId + ":" + atr.Value + ";");
}
}

StringBuilder actionKey = new StringBuilder();
foreach (XacmlJsonCategory category in request.Request.Action)
StringBuilder actionKey = new StringBuilder();
foreach (XacmlJsonCategory category in request.Request.Action)
{
foreach (XacmlJsonAttribute atr in category.Attribute)
{
foreach (XacmlJsonAttribute atr in category.Attribute)
{
actionKey.Append(atr.AttributeId + ":" + atr.Value + ";");
}
actionKey.Append(atr.AttributeId + ":" + atr.Value + ";");
}

return subjectKey.ToString() + actionKey.ToString() + resourceKey.ToString();
}

return subjectKey.ToString() + actionKey.ToString() + resourceKey.ToString();
}
}
Loading
Loading