diff --git a/src/Authorization/Altinn.Platform.Authorization.csproj b/src/Authorization/Altinn.Platform.Authorization.csproj
index 8b183ccc..87aba3db 100644
--- a/src/Authorization/Altinn.Platform.Authorization.csproj
+++ b/src/Authorization/Altinn.Platform.Authorization.csproj
@@ -8,14 +8,21 @@
+
+
+
+
+
+
+
@@ -28,6 +35,8 @@
+
+
diff --git a/src/Authorization/Configuration/TokenGeneratorSettings.cs b/src/Authorization/Configuration/TokenGeneratorSettings.cs
new file mode 100644
index 00000000..a6a2656a
--- /dev/null
+++ b/src/Authorization/Configuration/TokenGeneratorSettings.cs
@@ -0,0 +1,27 @@
+namespace Altinn.Platform.Authorization.Configuration;
+
+///
+/// Represents a set of configuration options needed for using the Altinn Platform Token Generator.
+///
+public class TokenGeneratorSettings
+{
+ ///
+ /// Gets or sets the url for the token generator.
+ ///
+ public string Url { get; set; }
+
+ ///
+ /// Gets or sets user for authorized access to the token generator.
+ ///
+ public string User { get; set; }
+
+ ///
+ /// Gets or sets password for authorized access to the token generator.
+ ///
+ public string Password { get; set; }
+
+ ///
+ /// Gets or sets the environment to use for the token generator.
+ ///
+ public string Env { get; set; }
+}
diff --git a/src/Authorization/Constants/AuthzConstants.cs b/src/Authorization/Constants/AuthzConstants.cs
index f4557ec2..c63d90dc 100644
--- a/src/Authorization/Constants/AuthzConstants.cs
+++ b/src/Authorization/Constants/AuthzConstants.cs
@@ -10,6 +10,16 @@ public static class AuthzConstants
///
public const string POLICY_STUDIO_DESIGNER = "StudioDesignerAccess";
+ ///
+ /// Policy tag for authorizing PlatformAccessTokens issued by Platform
+ ///
+ public const string POLICY_PLATFORMISSUER_ACCESSTOKEN = "PlatformIssuedAccessToken";
+
+ ///
+ /// The issuer of access tokens for the platform cluster
+ ///
+ public const string PLATFORM_ACCESSTOKEN_ISSUER = "platform";
+
///
/// Policy tag for authorizing Altinn.Platform.Authorization API access from AltinnII Authorization
///
@@ -25,11 +35,6 @@ public static class AuthzConstants
///
public const string AUTHORIZESCOPEACCESS = "AuthorizeScopeAccess";
- ///
- /// (deprecated) Scope that gives access to Authorize API
- ///
- public const string PDP_SCOPE = "altinn:authorization:pdp";
-
///
/// Scope that gives access to external Authorize API for service/resource owners
///
diff --git a/src/Authorization/Controllers/AccessListAuthorizationController.cs b/src/Authorization/Controllers/AccessListAuthorizationController.cs
new file mode 100644
index 00000000..a000fbd7
--- /dev/null
+++ b/src/Authorization/Controllers/AccessListAuthorizationController.cs
@@ -0,0 +1,66 @@
+using System.Linq;
+using System.Net.Mime;
+using System.Threading;
+using System.Threading.Tasks;
+using Altinn.Authorization.Errors;
+using Altinn.Authorization.Models.ResourceRegistry;
+using Altinn.Authorization.ProblemDetails;
+using Altinn.Platform.Authorization.Constants;
+using Altinn.Platform.Authorization.Models;
+using Altinn.Platform.Authorization.Services.Interface;
+using AltinnCore.Authentication.Constants;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+
+namespace Altinn.Platform.Authorization.Controllers;
+
+///
+/// Contains all actions related to the roles model
+///
+[Route("authorization/api/v1/accesslist")]
+[ApiController]
+public class AccessListAuthorizationController : ControllerBase
+{
+ private readonly IAccessListAuthorization _accessListAuthorization;
+ private readonly IResourceRegistry _resourceRegistry;
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ public AccessListAuthorizationController(IAccessListAuthorization accessListAuthorization, IResourceRegistry resourceRegistry)
+ {
+ _accessListAuthorization = accessListAuthorization;
+ _resourceRegistry = resourceRegistry;
+ }
+
+ ///
+ /// Internal API for local cluster requests only.
+ /// Authorization of a given subject for resource access through access lists for any service owner organization's access lists and resources.
+ ///
+ /// Access list authorization request model
+ /// The
+ /// AccessListAuthorizationResponse
+ [HttpPost]
+ [Route("accessmanagement/authorization")]
+ [ApiExplorerSettings(IgnoreApi = true)]
+ [Authorize(Policy = AuthzConstants.POLICY_PLATFORMISSUER_ACCESSTOKEN)]
+ [Consumes(MediaTypeNames.Application.Json)]
+ [Produces(MediaTypeNames.Application.Json)]
+ [ProducesResponseType(typeof(AccessListAuthorizationResponse), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
+ [ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
+ [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
+ public async Task AuthorizeInternal(AccessListAuthorizationRequest accessListAuthorizationRequest, CancellationToken cancellationToken = default)
+ {
+ Result result = await _accessListAuthorization.Authorize(accessListAuthorizationRequest, cancellationToken);
+
+ if (result.IsProblem)
+ {
+ return result.Problem.ToActionResult();
+ }
+
+ return Ok(result.Value);
+ }
+}
diff --git a/src/Authorization/Controllers/DecisionController.cs b/src/Authorization/Controllers/DecisionController.cs
index 945069dd..88f3196c 100644
--- a/src/Authorization/Controllers/DecisionController.cs
+++ b/src/Authorization/Controllers/DecisionController.cs
@@ -10,7 +10,13 @@
using Altinn.Authorization.ABAC.Utils;
using Altinn.Authorization.ABAC.Xacml;
using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Authorization.Enums;
+using Altinn.Authorization.Models;
+using Altinn.Authorization.Models.Register;
+using Altinn.Authorization.Models.ResourceRegistry;
+using Altinn.Authorization.ProblemDetails;
using Altinn.Platform.Authorization.Constants;
+using Altinn.Platform.Authorization.Helpers;
using Altinn.Platform.Authorization.ModelBinding;
using Altinn.Platform.Authorization.Models;
using Altinn.Platform.Authorization.Models.AccessManagement;
@@ -18,6 +24,7 @@
using Altinn.Platform.Authorization.Repositories.Interface;
using Altinn.Platform.Authorization.Services.Interface;
using Altinn.Platform.Authorization.Services.Interfaces;
+using Altinn.Platform.Register.Models;
using Altinn.Platform.Storage.Interface.Models;
using AutoMapper;
using Azure.Core;
@@ -47,6 +54,9 @@ public class DecisionController : ControllerBase
private readonly IEventLog _eventLog;
private readonly IFeatureManager _featureManager;
private readonly IAccessManagementWrapper _accessManagement;
+ private readonly IResourceRegistry _resourceRegistry;
+ private readonly IRegisterService _registerService;
+ private readonly IAccessListAuthorization _accessListAuthorization;
private readonly IMapper _mapper;
private readonly SortedDictionary _appInstanceInfo = new();
@@ -55,6 +65,9 @@ public class DecisionController : ControllerBase
/// Initializes a new instance of the class.
///
/// Service for making request the to Access Management API (PIP)
+ /// Service for making requests to the Resource Registry API
+ /// Service for making requests to the Register API
+ /// Service for authorization of subjects based on Resource Registry access lists
/// The Context handler
/// The delegation context handler
/// The policy Retrieval point
@@ -64,7 +77,20 @@ public class DecisionController : ControllerBase
/// the authorization event logger
/// the feature manager
/// The model mapper
- public DecisionController(IAccessManagementWrapper accessManagement, IContextHandler contextHandler, IDelegationContextHandler delegationContextHandler, IPolicyRetrievalPoint policyRetrievalPoint, IDelegationMetadataRepository delegationRepository, ILogger logger, IMemoryCache memoryCache, IEventLog eventLog, IFeatureManager featureManager, IMapper mapper)
+ public DecisionController(
+ IAccessManagementWrapper accessManagement,
+ IResourceRegistry resourceRegistry,
+ IRegisterService registerService,
+ IAccessListAuthorization accessListAuthorization,
+ IContextHandler contextHandler,
+ IDelegationContextHandler delegationContextHandler,
+ IPolicyRetrievalPoint policyRetrievalPoint,
+ IDelegationMetadataRepository delegationRepository,
+ ILogger logger,
+ IMemoryCache memoryCache,
+ IEventLog eventLog,
+ IFeatureManager featureManager,
+ IMapper mapper)
{
_pdp = new PolicyDecisionPoint();
_prp = policyRetrievalPoint;
@@ -75,6 +101,9 @@ public DecisionController(IAccessManagementWrapper accessManagement, IContextHan
_eventLog = eventLog;
_featureManager = featureManager;
_accessManagement = accessManagement;
+ _resourceRegistry = resourceRegistry;
+ _registerService = registerService;
+ _accessListAuthorization = accessListAuthorization;
_mapper = mapper;
}
@@ -267,28 +296,17 @@ private async Task Authorize(XacmlContextRequest decisionR
{
decisionRequest = await this._contextHandler.Enrich(decisionRequest, isExernalRequest, _appInstanceInfo);
- ////_logger.LogInformation($"// DecisionController // Authorize // Roles // Enriched request: {JsonConvert.SerializeObject(decisionRequest)}.");
XacmlPolicy policy = await _prp.GetPolicyAsync(decisionRequest);
XacmlContextResponse rolesContextResponse = _pdp.Authorize(decisionRequest, policy);
- ////_logger.LogInformation($"// DecisionController // Authorize // Roles // XACML ContextResponse: {JsonConvert.SerializeObject(rolesContextResponse)}.");
-
XacmlContextResult roleResult = rolesContextResponse.Results.First();
+
+ XacmlContextResponse delegationContextResponse = null;
if (roleResult.Decision.Equals(XacmlContextDecision.NotApplicable))
{
try
{
- XacmlContextResponse delegationContextResponse = await AuthorizeUsingDelegations(decisionRequest, policy, cancellationToken);
- XacmlContextResult delegationResult = delegationContextResponse.Results.First();
- if (delegationResult.Decision.Equals(XacmlContextDecision.Permit))
- {
- if (logEvent)
- {
- await _eventLog.CreateAuthorizationEvent(_featureManager, decisionRequest, HttpContext, delegationContextResponse, cancellationToken);
- }
-
- return delegationContextResponse;
- }
+ delegationContextResponse = await AuthorizeUsingDelegations(decisionRequest, policy, cancellationToken);
}
catch (Exception ex)
{
@@ -296,12 +314,27 @@ private async Task Authorize(XacmlContextRequest decisionR
}
}
+ XacmlContextResponse finalResponse = delegationContextResponse ?? rolesContextResponse;
+ XacmlContextResult finalResult = finalResponse.Results.First();
+ if (finalResult.Decision.Equals(XacmlContextDecision.Permit) && !await IsAccessListAuthorized(decisionRequest, cancellationToken))
+ {
+ return new XacmlContextResponse(new XacmlContextResult(XacmlContextDecision.Deny)
+ {
+ Status = new XacmlContextStatus(XacmlContextStatusCode.Success)
+ {
+ StatusMessage = "Access list authorization of resource party required. Access list authorization is controlled by the service owner of the resource/service of the authorization request.",
+ }
+ });
+ }
+
+ // If the delegation context response is NOT permit, the final response should be the roles context response
+ finalResponse = finalResult.Decision.Equals(XacmlContextDecision.Permit) ? finalResponse : rolesContextResponse;
if (logEvent)
{
- await _eventLog.CreateAuthorizationEvent(_featureManager, decisionRequest, HttpContext, rolesContextResponse, cancellationToken);
+ await _eventLog.CreateAuthorizationEvent(_featureManager, decisionRequest, HttpContext, finalResponse, cancellationToken);
}
- return rolesContextResponse;
+ return finalResponse;
}
private async Task ProcessDelegationResult(XacmlContextRequest decisionRequest, XacmlPolicy resourcePolicy, IEnumerable delegations, CancellationToken cancellationToken = default)
@@ -324,6 +357,46 @@ private async Task ProcessDelegationResult(XacmlContextReq
});
}
+ private async Task IsAccessListAuthorized(XacmlContextRequest decisionRequest, CancellationToken cancellationToken = default)
+ {
+ PolicyResourceType policyResourceType = PolicyHelper.GetPolicyResourceType(decisionRequest, out string resourceId);
+ if (!policyResourceType.Equals(PolicyResourceType.ResourceRegistry))
+ {
+ return true;
+ }
+
+ ServiceResource resource = await _resourceRegistry.GetResourceAsync(resourceId, cancellationToken);
+ if (resource != null && resource.AccessListMode == ResourceAccessListMode.Enabled)
+ {
+ var resourceAttributes = _delegationContextHandler.GetResourceAttributes(decisionRequest);
+
+ Party party = await _registerService.GetParty(int.Parse(resourceAttributes.ResourcePartyValue));
+ if (party?.PartyTypeName != Register.Enums.PartyType.Organisation)
+ {
+ // Currently only Organization support in AccessLists
+ return false;
+ }
+
+ resourceAttributes.OrganizationNumber = party.OrgNumber;
+ AccessListAuthorizationRequest accessListAuthorizationRequest = new AccessListAuthorizationRequest
+ {
+ Subject = PartyUrn.OrganizationIdentifier.Create(OrganizationNumber.CreateUnchecked(resourceAttributes.OrganizationNumber)),
+ Resource = ResourceIdUrn.ResourceId.Create(Altinn.Authorization.Models.ResourceRegistry.ResourceIdentifier.CreateUnchecked(resourceId)),
+ Action = ActionUrn.ActionId.Create(ActionIdentifier.CreateUnchecked(_delegationContextHandler.GetActionString(decisionRequest)))
+ };
+ Result result = await _accessListAuthorization.Authorize(accessListAuthorizationRequest, cancellationToken);
+
+ if (result.IsProblem)
+ {
+ return false;
+ }
+
+ return result.Value.Result == AccessListAuthorizationResult.Authorized;
+ }
+
+ return true;
+ }
+
private static string CreateCacheKey(params string[] cacheKeys) =>
string.Join("-", cacheKeys.Where(c => c != null && (c != string.Empty || !c.EndsWith(':'))));
diff --git a/src/Authorization/Errors/Problems.cs b/src/Authorization/Errors/Problems.cs
new file mode 100644
index 00000000..aaa4ecd7
--- /dev/null
+++ b/src/Authorization/Errors/Problems.cs
@@ -0,0 +1,21 @@
+#nullable enable
+
+using System.Net;
+using Altinn.Authorization.ProblemDetails;
+
+namespace Altinn.Authorization.Errors;
+
+///
+/// Problem descriptors for Authorization
+///
+public static class Problems
+{
+ private static readonly ProblemDescriptorFactory _factory
+ = ProblemDescriptorFactory.New("AUTHZ");
+
+ ///
+ /// Gets a for not implemented feature.
+ ///
+ public static ProblemDescriptor NotImplemented { get; }
+ = _factory.Create(0, HttpStatusCode.NotImplemented, "Not implemented.");
+}
diff --git a/src/Authorization/Errors/ValidationErrors.cs b/src/Authorization/Errors/ValidationErrors.cs
new file mode 100644
index 00000000..aa40a17f
--- /dev/null
+++ b/src/Authorization/Errors/ValidationErrors.cs
@@ -0,0 +1,32 @@
+#nullable enable
+
+using Altinn.Authorization.ProblemDetails;
+
+namespace Altinn.Authorization.Errors;
+
+///
+/// Validation errors for Authorization
+///
+public static class ValidationErrors
+{
+ private static readonly ValidationErrorDescriptorFactory _factory
+ = ValidationErrorDescriptorFactory.New("AUTHZ");
+
+ ///
+ /// Gets a validation error descriptor for when a provided resource registry identifier is not found as a valid resource.
+ ///
+ public static ValidationErrorDescriptor ResourceRegistry_ResourceIdentifier_NotFound { get; }
+ = _factory.Create(0, "Unknown resource registry identifier.");
+
+ ///
+ /// Gets a validation error descriptor for when the authencticated organization number is not authorized as the competent authority owner of a resource registry resource.
+ ///
+ public static ValidationErrorDescriptor ResourceRegistry_CompetentAuthority_NotMatchingAuthenticatedOrganization { get; }
+ = _factory.Create(1, "Authorized organization is not the competent authority owner of the requested resource.");
+
+ ///
+ /// Gets a validation error descriptor for when the authencticated organization code is not authorized as the competent authority owner of a resource registry resource.
+ ///
+ public static ValidationErrorDescriptor ResourceRegistry_CompetentAuthority_NotMatchingAuthenticatedOrgCode { get; }
+ = _factory.Create(2, "Authorized organization code is not the competent authority owner of the requested resource.");
+}
diff --git a/src/Authorization/Extensions/HttpClientExtension.cs b/src/Authorization/Extensions/HttpClientExtension.cs
index 2461a3d6..807b39fd 100644
--- a/src/Authorization/Extensions/HttpClientExtension.cs
+++ b/src/Authorization/Extensions/HttpClientExtension.cs
@@ -14,43 +14,54 @@ public static class HttpClientExtension
/// Extension that add authorization header to request
///
/// The HttpClient
- /// the authorization token (jwt)
/// The request Uri
/// The http content
+ /// the authorization token (jwt)
/// The platformAccess tokens
+ /// The
/// A HttpResponseMessage
- public static Task PostAsync(this HttpClient httpClient, string authorizationToken, string requestUri, HttpContent content, string platformAccessToken = null)
+ public static Task PostAsync(this HttpClient httpClient, string requestUri, HttpContent content, string authorizationToken = null, string platformAccessToken = null, CancellationToken cancellationToken = default)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, new Uri(requestUri, UriKind.Relative));
- request.Headers.Add("Authorization", "Bearer " + authorizationToken);
request.Content = content;
+ if (!string.IsNullOrEmpty(authorizationToken))
+ {
+ request.Headers.Add("Authorization", "Bearer " + authorizationToken);
+ }
+
if (!string.IsNullOrEmpty(platformAccessToken))
{
request.Headers.Add("PlatformAccessToken", platformAccessToken);
}
- return httpClient.SendAsync(request, CancellationToken.None);
+ return httpClient.SendAsync(request, cancellationToken);
}
///
/// Extension that add authorization header to request
///
/// The HttpClient
- /// the authorization token (jwt)
/// The request Uri
+ /// the authorization token (jwt)
/// The platformAccess tokens
+ /// The
/// A HttpResponseMessage
- public static Task GetAsync(this HttpClient httpClient, string authorizationToken, string requestUri, string platformAccessToken = null)
+ public static Task GetAsync(this HttpClient httpClient, string requestUri, string authorizationToken = null, string platformAccessToken = null, CancellationToken cancellationToken = default)
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri);
- request.Headers.Add("Authorization", "Bearer " + authorizationToken);
+
+ if (!string.IsNullOrEmpty(authorizationToken))
+ {
+ request.Headers.Add("Authorization", "Bearer " + authorizationToken);
+ }
+
if (!string.IsNullOrEmpty(platformAccessToken))
{
request.Headers.Add("PlatformAccessToken", platformAccessToken);
}
- return httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, CancellationToken.None);
+ return httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken);
}
}
}
diff --git a/src/Authorization/Extensions/PlatformAccessTokenDependencyInjectionExtensions.cs b/src/Authorization/Extensions/PlatformAccessTokenDependencyInjectionExtensions.cs
new file mode 100644
index 00000000..a9b9f3fe
--- /dev/null
+++ b/src/Authorization/Extensions/PlatformAccessTokenDependencyInjectionExtensions.cs
@@ -0,0 +1,36 @@
+using Altinn.Authorization.Services;
+using Altinn.Common.AccessTokenClient.Services;
+using Altinn.Platform.Authorization.Configuration;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Altinn.Platform.Authorization.Extensions;
+
+///
+/// Extension methods for adding services to the dependency injection container in order to support platform service integrations requiring platform access token.
+///
+public static class PlatformAccessTokenDependencyInjectionExtensions
+{
+ ///
+ /// Registers services to the dependency injection container in order to support platform service integrations requiring platform access token.
+ ///
+ /// The .
+ /// The .
+ /// Whether the setup is for local dev environment. Will setup the platform token support using the web based Altinn test token generator.
+ /// for further chaining.
+ public static IServiceCollection AddPlatformAccessTokenSupport(
+ this IServiceCollection services, IConfiguration config, bool isDevelopment)
+ {
+ if (isDevelopment)
+ {
+ services.Configure(config.GetSection("TokenGeneratorSettings"));
+ services.AddSingleton();
+ }
+ else
+ {
+ services.AddSingleton();
+ }
+
+ return services;
+ }
+}
\ No newline at end of file
diff --git a/src/Authorization/Helpers/ServiceResourceHelper.cs b/src/Authorization/Helpers/ServiceResourceHelper.cs
new file mode 100644
index 00000000..a66c34ce
--- /dev/null
+++ b/src/Authorization/Helpers/ServiceResourceHelper.cs
@@ -0,0 +1,16 @@
+using System.Text.RegularExpressions;
+
+namespace Altinn.Authorization.Helpers
+{
+ ///
+ /// ServiceResource helper methods
+ ///
+ public static partial class ServiceResourceHelper
+ {
+ ///
+ /// Resource identifier regex.
+ ///
+ [GeneratedRegex("^[a-z0-9_-]{4,}$")]
+ internal static partial Regex ResourceIdentifierRegex();
+ }
+}
diff --git a/src/Authorization/Models/AccessListAuthorizationRequest.cs b/src/Authorization/Models/AccessListAuthorizationRequest.cs
new file mode 100644
index 00000000..6c975eb9
--- /dev/null
+++ b/src/Authorization/Models/AccessListAuthorizationRequest.cs
@@ -0,0 +1,34 @@
+#nullable enable
+
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+using Altinn.Authorization.Models.Register;
+using Altinn.Authorization.Models.ResourceRegistry;
+using Altinn.Urn.Json;
+
+namespace Altinn.Platform.Authorization.Models;
+
+///
+/// Contains attribute match info about the reportee party and resource that's to be authorized
+///
+public class AccessListAuthorizationRequest
+{
+ ///
+ /// Gets or sets the attributes identifying the party to be authorized
+ ///
+ [Required]
+ [JsonRequired]
+ public UrnJsonTypeValue Subject { get; set; }
+
+ ///
+ /// Gets or sets the attributes identifying the resource to authorize the party for
+ ///
+ [Required]
+ [JsonRequired]
+ public UrnJsonTypeValue Resource { get; set; }
+
+ ///
+ /// Gets or sets an optional action value to authorize
+ ///
+ public UrnJsonTypeValue Action { get; set; }
+}
\ No newline at end of file
diff --git a/src/Authorization/Models/AccessListAuthorizationResponse.cs b/src/Authorization/Models/AccessListAuthorizationResponse.cs
new file mode 100644
index 00000000..120214e6
--- /dev/null
+++ b/src/Authorization/Models/AccessListAuthorizationResponse.cs
@@ -0,0 +1,51 @@
+using System;
+using Altinn.Authorization.Enums;
+using Altinn.Authorization.Models.Register;
+using Altinn.Authorization.Models.ResourceRegistry;
+using Altinn.Urn.Json;
+
+namespace Altinn.Platform.Authorization.Models;
+
+///
+/// Contains attribute match info about the reportee party and resource that's to be authorized
+///
+public class AccessListAuthorizationResponse
+{
+ ///
+ /// Creates a new from an .
+ ///
+ /// The request.
+ /// The mapped .
+ public static AccessListAuthorizationResponse From(AccessListAuthorizationRequest request)
+ {
+ request = request ?? throw new ArgumentNullException(nameof(request));
+
+ return new AccessListAuthorizationResponse
+ {
+ Subject = request.Subject,
+ Resource = request.Resource,
+ Action = request.Action,
+ Result = AccessListAuthorizationResult.NotDetermined
+ };
+ }
+
+ ///
+ /// Gets or sets the attributes identifying the party to be authorized
+ ///
+ public UrnJsonTypeValue Subject { get; set; }
+
+ ///
+ /// Gets or sets the attributes identifying the resource to authorize the party for
+ ///
+ public UrnJsonTypeValue Resource { get; set; }
+
+ ///
+ /// Gets or sets an optional action value to authorize
+ ///
+ public UrnJsonTypeValue Action { get; set; }
+
+ ///
+ /// Gets or sets the result of the access list authorization
+ ///
+ public AccessListAuthorizationResult Result { get; set; }
+}
\ No newline at end of file
diff --git a/src/Authorization/Models/AccessListAuthorizationResult.cs b/src/Authorization/Models/AccessListAuthorizationResult.cs
new file mode 100644
index 00000000..1e52f289
--- /dev/null
+++ b/src/Authorization/Models/AccessListAuthorizationResult.cs
@@ -0,0 +1,29 @@
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace Altinn.Authorization.Enums;
+
+///
+/// Enum defining the different results of an access list authorization
+///
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum AccessListAuthorizationResult
+{
+ ///
+ /// Result is not yet determined
+ ///
+ [EnumMember(Value = "NotDetermined")]
+ NotDetermined,
+
+ ///
+ /// Subject is not authorized to access the resource through any access lists
+ ///
+ [EnumMember(Value = "NotAuthorized")]
+ NotAuthorized,
+
+ ///
+ /// Subject is authorized to access the resource through one or more access lists
+ ///
+ [EnumMember(Value = "Authorized")]
+ Authorized
+}
\ No newline at end of file
diff --git a/src/Authorization/Models/ActionIdentifier.cs b/src/Authorization/Models/ActionIdentifier.cs
new file mode 100644
index 00000000..f823fd08
--- /dev/null
+++ b/src/Authorization/Models/ActionIdentifier.cs
@@ -0,0 +1,121 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Altinn.Swashbuckle.Examples;
+
+namespace Altinn.Authorization.Models;
+
+///
+/// A xacml action string.
+///
+[JsonConverter(typeof(JsonConverter))]
+public class ActionIdentifier
+ : ISpanParsable,
+ ISpanFormattable,
+ IExampleDataProvider
+{
+ private readonly string _value;
+
+ private ActionIdentifier(string value)
+ {
+ _value = value;
+ }
+
+ ///
+ /// Creates a new from the specified value without validation.
+ ///
+ /// The action identifier.
+ /// A .
+ public static ActionIdentifier CreateUnchecked(string value)
+ => new(value);
+
+ ///
+ public static IEnumerable? GetExamples(ExampleDataOptions options)
+ {
+ yield return new ActionIdentifier("read");
+ yield return new ActionIdentifier("write");
+ }
+
+ ///
+ public static ActionIdentifier Parse(string s)
+ => Parse(s, provider: null);
+
+ ///
+ public static ActionIdentifier Parse(string s, IFormatProvider? provider)
+ => TryParse(s, provider, out var result)
+ ? result
+ : throw new FormatException("Invalid action");
+
+ ///
+ public static ActionIdentifier Parse(ReadOnlySpan s)
+ => Parse(s, provider: null);
+
+ ///
+ public static ActionIdentifier Parse(ReadOnlySpan s, IFormatProvider? provider)
+ => TryParse(s, provider, out var result)
+ ? result
+ : throw new FormatException("Invalid action");
+
+ ///
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out ActionIdentifier result)
+ => TryParse(s.AsSpan(), s, out result);
+
+ ///
+ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out ActionIdentifier result)
+ => TryParse(s, original: null, out result);
+
+ private static bool TryParse(ReadOnlySpan s, string? original, [MaybeNullWhen(false)] out ActionIdentifier result)
+ {
+ result = new ActionIdentifier(original ?? new string(s));
+ return true;
+ }
+
+ ///
+ public override string ToString()
+ => _value;
+
+ ///
+ public string ToString(string? format)
+ => _value;
+
+ ///
+ public string ToString(string? format, IFormatProvider? formatProvider)
+ => _value;
+
+ ///
+ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider)
+ {
+ if (destination.Length < _value.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ _value.AsSpan().CopyTo(destination);
+ charsWritten = _value.Length;
+ return true;
+ }
+
+ private sealed class JsonConverter : JsonConverter
+ {
+ public override ActionIdentifier? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var str = reader.GetString();
+ if (!TryParse(str, null, out var result))
+ {
+ throw new JsonException("Invalid action");
+ }
+
+ return result;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ActionIdentifier value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value._value);
+ }
+ }
+}
diff --git a/src/Authorization/Models/ActionUrn.cs b/src/Authorization/Models/ActionUrn.cs
new file mode 100644
index 00000000..d8a10caa
--- /dev/null
+++ b/src/Authorization/Models/ActionUrn.cs
@@ -0,0 +1,20 @@
+#nullable enable
+
+using Altinn.Urn;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// A unique reference to an action in the form of an URN.
+///
+[KeyValueUrn]
+public abstract partial record ActionUrn
+{
+ ///
+ /// Try to get the urn as an action.
+ ///
+ /// The resulting action.
+ /// if this is an action, otherwise .
+ [UrnKey("oasis:names:tc:xacml:1.0:action:action-id")]
+ public partial bool IsActionId(out ActionIdentifier action);
+}
diff --git a/src/Authorization/Models/Register/OrganizationNumber.cs b/src/Authorization/Models/Register/OrganizationNumber.cs
new file mode 100644
index 00000000..4dbe9e4d
--- /dev/null
+++ b/src/Authorization/Models/Register/OrganizationNumber.cs
@@ -0,0 +1,135 @@
+#nullable enable
+
+using System;
+using System.Buffers;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Altinn.Swashbuckle.Examples;
+
+namespace Altinn.Authorization.Models.Register;
+
+///
+/// A organization number (a string of 9 digits).
+///
+[JsonConverter(typeof(JsonConverter))]
+public class OrganizationNumber
+ : ISpanParsable,
+ ISpanFormattable,
+ IExampleDataProvider
+{
+ private static readonly SearchValues NUMBERS = SearchValues.Create(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']);
+ private readonly string _value;
+
+ private OrganizationNumber(string value)
+ {
+ _value = value;
+ }
+
+ ///
+ /// Creates a new from the specified value without validation.
+ ///
+ /// The organization identifier.
+ /// A .
+ public static OrganizationNumber CreateUnchecked(string value)
+ => new(value);
+
+ ///
+ public static IEnumerable? GetExamples(ExampleDataOptions options)
+ {
+ yield return new OrganizationNumber("123456789");
+ yield return new OrganizationNumber("987654321");
+ }
+
+ ///
+ public static OrganizationNumber Parse(string s)
+ => Parse(s, provider: null);
+
+ ///
+ public static OrganizationNumber Parse(string s, IFormatProvider? provider)
+ => TryParse(s, provider, out var result)
+ ? result
+ : throw new FormatException("Invalid organization number");
+
+ ///
+ public static OrganizationNumber Parse(ReadOnlySpan s)
+ => Parse(s, provider: null);
+
+ ///
+ public static OrganizationNumber Parse(ReadOnlySpan s, IFormatProvider? provider)
+ => TryParse(s, provider, out var result)
+ ? result
+ : throw new FormatException("Invalid organization number");
+
+ ///
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out OrganizationNumber result)
+ => TryParse(s.AsSpan(), s, out result);
+
+ ///
+ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out OrganizationNumber result)
+ => TryParse(s, original: null, out result);
+
+ private static bool TryParse(ReadOnlySpan s, string? original, [MaybeNullWhen(false)] out OrganizationNumber result)
+ {
+ if (s.Length != 9)
+ {
+ result = null;
+ return false;
+ }
+
+ if (s.ContainsAnyExcept(NUMBERS))
+ {
+ result = null;
+ return false;
+ }
+
+ result = new OrganizationNumber(original ?? new string(s));
+ return true;
+ }
+
+ ///
+ public override string ToString()
+ => _value;
+
+ ///
+ public string ToString(string? format)
+ => _value;
+
+ ///
+ public string ToString(string? format, IFormatProvider? formatProvider)
+ => _value;
+
+ ///
+ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider)
+ {
+ if (destination.Length < _value.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ _value.AsSpan().CopyTo(destination);
+ charsWritten = _value.Length;
+ return true;
+ }
+
+ private sealed class JsonConverter : JsonConverter
+ {
+ public override OrganizationNumber? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var str = reader.GetString();
+ if (!TryParse(str, null, out var result))
+ {
+ throw new JsonException("Invalid organization number");
+ }
+
+ return result;
+ }
+
+ public override void Write(Utf8JsonWriter writer, OrganizationNumber value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value._value);
+ }
+ }
+}
diff --git a/src/Authorization/Models/Register/PartyIdentifiers.cs b/src/Authorization/Models/Register/PartyIdentifiers.cs
new file mode 100644
index 00000000..4078c7c8
--- /dev/null
+++ b/src/Authorization/Models/Register/PartyIdentifiers.cs
@@ -0,0 +1,31 @@
+#nullable enable
+
+using System;
+
+namespace Altinn.Authorization.Models.Register;
+
+///
+/// A set of identifiers for a party.
+///
+public record PartyIdentifiers
+{
+ ///
+ /// The party id.
+ ///
+ public required int PartyId { get; init; }
+
+ ///
+ /// The party uuid.
+ ///
+ public required Guid PartyUuid { get; init; }
+
+ ///
+ /// The organization number of the party (if applicable).
+ ///
+ public required string? OrgNumber { get; init; }
+
+ ///
+ /// The social security number of the party (if applicable and included).
+ ///
+ public string? SSN { get; init; }
+}
diff --git a/src/Authorization/Models/Register/PartyUrn.cs b/src/Authorization/Models/Register/PartyUrn.cs
new file mode 100644
index 00000000..3eab2243
--- /dev/null
+++ b/src/Authorization/Models/Register/PartyUrn.cs
@@ -0,0 +1,34 @@
+#nullable enable
+
+using System;
+using System.Globalization;
+using Altinn.Urn;
+
+namespace Altinn.Authorization.Models.Register;
+
+///
+/// A unique reference to a party in the form of an URN.
+///
+[KeyValueUrn]
+public abstract partial record PartyUrn
+{
+ ///
+ /// Try to get the urn as a party uuid.
+ ///
+ /// The resulting party uuid.
+ /// if this party reference is a party uuid, otherwise .
+ [UrnKey("altinn:party:uuid")]
+ public partial bool IsPartyUuid(out Guid partyUuid);
+
+ ///
+ /// Try to get the urn as an organization number.
+ ///
+ /// The resulting organization number.
+ /// if this party reference is an organization number, otherwise .
+ [UrnKey("altinn:organization:identifier-no")]
+ public partial bool IsOrganizationIdentifier(out OrganizationNumber organizationNumber);
+
+ // Manually overridden to disallow negative party ids
+ private static bool TryParsePartyId(ReadOnlySpan segment, IFormatProvider? provider, out int value)
+ => int.TryParse(segment, NumberStyles.None, provider, out value);
+}
diff --git a/src/Authorization/Models/ResourceRegistry/AccessListResourceMembershipWithActionFilterDto.cs b/src/Authorization/Models/ResourceRegistry/AccessListResourceMembershipWithActionFilterDto.cs
new file mode 100644
index 00000000..2c357ca4
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/AccessListResourceMembershipWithActionFilterDto.cs
@@ -0,0 +1,29 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Altinn.Authorization.Models.Register;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Represents a party's membership of a access list connected to a specific resource with an optional set of action filters.
+///
+/// The party UUID.
+/// The resource id.
+/// Since when this party has been a member of the list connected to the party.
+/// Optional set of action filters.
+public record AccessListResourceMembershipWithActionFilterDto(
+ PartyUrn.PartyUuid Party,
+ ResourceUrn.ResourceId Resource,
+ DateTimeOffset Since,
+ IReadOnlyCollection? ActionFilters)
+{
+ ///
+ /// Gets the allowed actions or if all actions are allowed.
+ ///
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ public IReadOnlyCollection? ActionFilters { get; }
+ = ActionFilters is null or { Count: 0 } ? null : ActionFilters;
+}
diff --git a/src/Authorization/Models/ResourceRegistry/AuthorizationReferenceAttribute.cs b/src/Authorization/Models/ResourceRegistry/AuthorizationReferenceAttribute.cs
new file mode 100644
index 00000000..a1f0a745
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/AuthorizationReferenceAttribute.cs
@@ -0,0 +1,17 @@
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// The reference
+///
+public class AuthorizationReferenceAttribute
+{
+ ///
+ /// The key for authorization reference. Used for authorization api related to resource
+ ///
+ public string Id { get; set; }
+
+ ///
+ /// The value for authorization reference. Used for authorization api related to resource
+ ///
+ public string Value { get; set; }
+}
diff --git a/src/Authorization/Models/ResourceRegistry/CompetentAuthority.cs b/src/Authorization/Models/ResourceRegistry/CompetentAuthority.cs
new file mode 100644
index 00000000..657e3611
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/CompetentAuthority.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Model representation of Competent Authority part of the ServiceResource model
+///
+public class CompetentAuthority
+{
+ ///
+ /// The organization number
+ ///
+ public string Organization { get; set; }
+
+ ///
+ /// The organization code
+ ///
+ public string Orgcode { get; set; }
+
+ ///
+ /// The organization name. If not set it will be retrived from register based on Organization number
+ ///
+ public Dictionary Name { get; set; }
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ContactPoint.cs b/src/Authorization/Models/ResourceRegistry/ContactPoint.cs
new file mode 100644
index 00000000..0ee732b8
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ContactPoint.cs
@@ -0,0 +1,27 @@
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Defines a contact point
+///
+public class ContactPoint
+{
+ ///
+ /// The type of contact point, phone, email ++
+ ///
+ public string Category { get; set; }
+
+ ///
+ /// The contact details. The actual phone number, email adress
+ ///
+ public string Email { get; set; }
+
+ ///
+ /// Phone details
+ ///
+ public string Telephone { get; set; }
+
+ ///
+ /// Contact page
+ ///
+ public string ContactPage { get; set; }
+}
diff --git a/src/Authorization/Models/ResourceRegistry/Keyword.cs b/src/Authorization/Models/ResourceRegistry/Keyword.cs
new file mode 100644
index 00000000..8dab955e
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/Keyword.cs
@@ -0,0 +1,17 @@
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Model for defining keywords
+///
+public class Keyword
+{
+ ///
+ /// The key word
+ ///
+ public string Word { get; set; }
+
+ ///
+ /// Language of the key word
+ ///
+ public string Language { get; set; }
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ListObject.cs b/src/Authorization/Models/ResourceRegistry/ListObject.cs
new file mode 100644
index 00000000..e9e5174d
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ListObject.cs
@@ -0,0 +1,53 @@
+#nullable enable
+
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.Annotations;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// A list object is a wrapper around a list of items to allow for the API to be
+/// extended in the future without breaking backwards compatibility.
+///
+[SwaggerSchemaFilter(typeof(SchemaFilter))]
+public abstract record ListObject
+{
+ ///
+ /// Creates a new from a list of items.
+ ///
+ /// The list type.
+ /// The list of items.
+ /// A .
+ public static ListObject Create(IEnumerable items)
+ => new(items);
+
+ ///
+ /// Default schema filter for .
+ ///
+ protected class SchemaFilter : ISchemaFilter
+ {
+ ///
+ public void Apply(OpenApiSchema schema, SchemaFilterContext context)
+ {
+ foreach (var prop in schema.Properties)
+ {
+ schema.Required.Add(prop.Key);
+ }
+
+ schema.Properties["data"].Nullable = false;
+ }
+ }
+}
+
+///
+/// A concrete list object.
+///
+/// The item type.
+/// The items.
+public record ListObject(
+ [property: JsonPropertyName("data")]
+ IEnumerable Items)
+ : ListObject;
diff --git a/src/Authorization/Models/ResourceRegistry/ReferenceSource.cs b/src/Authorization/Models/ResourceRegistry/ReferenceSource.cs
new file mode 100644
index 00000000..ef756c3a
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ReferenceSource.cs
@@ -0,0 +1,24 @@
+using System.Runtime.Serialization;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Enum for the different reference sources for resources in the resource registry
+///
+public enum ReferenceSource
+{
+ [EnumMember(Value = "Default")]
+ Default = 0,
+
+ [EnumMember(Value = "Altinn1")]
+ Altinn1 = 1,
+
+ [EnumMember(Value = "Altinn2")]
+ Altinn2 = 2,
+
+ [EnumMember(Value = "Altinn3")]
+ Altinn3 = 3,
+
+ [EnumMember(Value = "ExternalPlatform")]
+ ExternalPlatform = 4
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ReferenceType.cs b/src/Authorization/Models/ResourceRegistry/ReferenceType.cs
new file mode 100644
index 00000000..93f36cb1
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ReferenceType.cs
@@ -0,0 +1,30 @@
+using System.Runtime.Serialization;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Enum for reference types of resources in the resource registry
+///
+public enum ReferenceType
+{
+ [EnumMember(Value = "Default")]
+ Default = 0,
+
+ [EnumMember(Value = "Uri")]
+ Uri = 1,
+
+ [EnumMember(Value = "DelegationSchemeId")]
+ DelegationSchemeId = 2,
+
+ [EnumMember(Value = "MaskinportenScope")]
+ MaskinportenScope = 3,
+
+ [EnumMember(Value = "ServiceCode")]
+ ServiceCode = 4,
+
+ [EnumMember(Value = "ServiceEditionCode")]
+ ServiceEditionCode = 5,
+
+ [EnumMember(Value = "ApplicationId")]
+ ApplicationId = 6,
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ResourceAccessListMode.cs b/src/Authorization/Models/ResourceRegistry/ResourceAccessListMode.cs
new file mode 100644
index 00000000..fb4a1d7d
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ResourceAccessListMode.cs
@@ -0,0 +1,11 @@
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Enum representation of the different types of ResourceAccessListModes supported by the resource registry
+///
+public enum ResourceAccessListMode
+{
+ Disabled = 0,
+
+ Enabled = 1
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ResourceIdUrn.cs b/src/Authorization/Models/ResourceRegistry/ResourceIdUrn.cs
new file mode 100644
index 00000000..95daa97e
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ResourceIdUrn.cs
@@ -0,0 +1,20 @@
+#nullable enable
+
+using Altinn.Urn;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// A unique reference to a resource in the form of an URN.
+///
+[KeyValueUrn]
+public abstract partial record ResourceIdUrn
+{
+ ///
+ /// Try to get the urn as a resource id.
+ ///
+ /// The resulting resource id.
+ /// if this resource reference is a resource id, otherwise .
+ [UrnKey("altinn:resource")]
+ public partial bool IsResourceId(out ResourceIdentifier resourceId);
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ResourceIdentifier.cs b/src/Authorization/Models/ResourceRegistry/ResourceIdentifier.cs
new file mode 100644
index 00000000..ef41e8fa
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ResourceIdentifier.cs
@@ -0,0 +1,130 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Altinn.Authorization.Helpers;
+using Altinn.Swashbuckle.Examples;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// A valid resource identifier.
+///
+[JsonConverter(typeof(JsonConverter))]
+[DebuggerDisplay("{_value}")]
+public sealed record ResourceIdentifier
+ : ISpanParsable,
+ ISpanFormattable,
+ IExampleDataProvider
+{
+ private readonly string _value;
+
+ private ResourceIdentifier(string value)
+ {
+ _value = value;
+ }
+
+ ///
+ public static IEnumerable? GetExamples(ExampleDataOptions options)
+ {
+ yield return new ResourceIdentifier("example-resourceid");
+ yield return new ResourceIdentifier("app_skd_flyttemelding");
+ }
+
+ ///
+ /// Creates a new from the specified value without validation.
+ ///
+ /// The resource identifier.
+ /// A .
+ public static ResourceIdentifier CreateUnchecked(string value)
+ => new(value);
+
+ ///
+ public static ResourceIdentifier Parse(string s)
+ => Parse(s, provider: null);
+
+ ///
+ public static ResourceIdentifier Parse(string s, IFormatProvider? provider)
+ => TryParse(s, provider, out var result)
+ ? result
+ : throw new FormatException("Invalid resource identifier");
+
+ ///
+ public static ResourceIdentifier Parse(ReadOnlySpan s)
+ => Parse(s, provider: null);
+
+ ///
+ public static ResourceIdentifier Parse(ReadOnlySpan s, IFormatProvider? provider)
+ => TryParse(s, provider, out var result)
+ ? result
+ : throw new FormatException("Invalid resource identifier");
+
+ ///
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out ResourceIdentifier result)
+ => TryParse(s.AsSpan(), s, out result);
+
+ ///
+ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out ResourceIdentifier result)
+ => TryParse(s, original: null, out result);
+
+ private static bool TryParse(ReadOnlySpan s, string? original, [MaybeNullWhen(false)] out ResourceIdentifier result)
+ {
+ if (!ServiceResourceHelper.ResourceIdentifierRegex().IsMatch(s))
+ {
+ result = null;
+ return false;
+ }
+
+ result = new ResourceIdentifier(original ?? new string(s));
+ return true;
+ }
+
+ ///
+ public override string ToString()
+ => _value;
+
+ ///
+ public string ToString(string? format)
+ => _value;
+
+ ///
+ public string ToString(string? format, IFormatProvider? formatProvider)
+ => _value;
+
+ ///
+ public bool TryFormat(Span destination, out int charsWritten, ReadOnlySpan format, IFormatProvider? provider)
+ {
+ if (destination.Length < _value.Length)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ _value.AsSpan().CopyTo(destination);
+ charsWritten = _value.Length;
+ return true;
+ }
+
+ private sealed class JsonConverter : JsonConverter
+ {
+ public override ResourceIdentifier? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ var str = reader.GetString();
+ if (!TryParse(str, null, out var result))
+ {
+ throw new JsonException("Invalid resource identifier");
+ }
+
+ return result;
+ }
+
+ public override void Write(Utf8JsonWriter writer, ResourceIdentifier value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue(value._value);
+ }
+ }
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ResourcePartyType.cs b/src/Authorization/Models/ResourceRegistry/ResourcePartyType.cs
new file mode 100644
index 00000000..2e83b277
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ResourcePartyType.cs
@@ -0,0 +1,26 @@
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Defines the type of party that a resource is targeting
+///
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum ResourcePartyType
+{
+ [EnumMember(Value = "PrivatePerson")]
+ PrivatePerson = 0,
+
+ [EnumMember(Value = "LegalEntityEnterprise")]
+ LegalEntityEnterprise = 1,
+
+ [EnumMember(Value = "Company")]
+ Company = 2,
+
+ [EnumMember(Value = "BankruptcyEstate")]
+ BankruptcyEstate = 3,
+
+ [EnumMember(Value = "SelfRegisteredUser")]
+ SelfRegisteredUser = 4
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ResourceReference.cs b/src/Authorization/Models/ResourceRegistry/ResourceReference.cs
new file mode 100644
index 00000000..5a73bb94
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ResourceReference.cs
@@ -0,0 +1,27 @@
+#nullable enable
+using System.Text.Json.Serialization;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Model representation of the resource reference part of the ServiceResource model
+///
+public class ResourceReference
+{
+ ///
+ /// The source the reference identifier points to
+ ///
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public ReferenceSource? ReferenceSource { get; set; }
+
+ ///
+ /// The reference identifier
+ ///
+ public string? Reference { get; set; }
+
+ ///
+ /// The reference type
+ ///
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public ReferenceType? ReferenceType { get; set; }
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ResourceType.cs b/src/Authorization/Models/ResourceRegistry/ResourceType.cs
new file mode 100644
index 00000000..6dd8b1a0
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ResourceType.cs
@@ -0,0 +1,33 @@
+using NpgsqlTypes;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Enum representation of the different types of resources supported by the resource registry
+///
+public enum ResourceType
+{
+ [PgName("default")]
+ Default = 0,
+
+ [PgName("systemresource")]
+ Systemresource = 1 << 0,
+
+ [PgName("maskinportenschema")]
+ MaskinportenSchema = 1 << 1,
+
+ [PgName("altinn2service")]
+ Altinn2Service = 1 << 2,
+
+ [PgName("altinnapp")]
+ AltinnApp = 1 << 3,
+
+ [PgName("genericaccessresource")]
+ GenericAccessResource = 1 << 4,
+
+ [PgName("brokerservice")]
+ BrokerService = 1 << 5,
+
+ [PgName("correspondenceservice")]
+ CorrespondenceService = 1 << 6,
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ResourceUrn.cs b/src/Authorization/Models/ResourceRegistry/ResourceUrn.cs
new file mode 100644
index 00000000..9b191438
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ResourceUrn.cs
@@ -0,0 +1,20 @@
+#nullable enable
+
+using Altinn.Urn;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// A unique reference to a resource in the form of an URN.
+///
+[KeyValueUrn]
+public abstract partial record ResourceUrn
+{
+ ///
+ /// Try to get the urn as a resource id.
+ ///
+ /// The resulting resource id.
+ /// if this resource reference is a resource id, otherwise .
+ [UrnKey("altinn:resource")]
+ public partial bool IsResourceId(out ResourceIdentifier resourceId);
+}
diff --git a/src/Authorization/Models/ResourceRegistry/ServiceResource.cs b/src/Authorization/Models/ResourceRegistry/ServiceResource.cs
new file mode 100644
index 00000000..29a5c802
--- /dev/null
+++ b/src/Authorization/Models/ResourceRegistry/ServiceResource.cs
@@ -0,0 +1,144 @@
+#nullable enable
+using System.Collections.Generic;
+using System.ComponentModel.DataAnnotations;
+using System.Text.Json.Serialization;
+
+namespace Altinn.Authorization.Models.ResourceRegistry;
+
+///
+/// Model describing a complete resource from the resrouce registry
+///
+public class ServiceResource
+{
+ ///
+ /// The identifier of the resource
+ ///
+ [Required]
+ public string? Identifier { get; set; }
+
+ ///
+ /// The version of the resource
+ ///
+ public string? Version { get; set; }
+
+ ///
+ /// The title of service
+ ///
+ [Required]
+ public Dictionary? Title { get; set; }
+
+ ///
+ /// Description
+ ///
+ [Required]
+ public Dictionary? Description { get; set; }
+
+ ///
+ /// Description explaining the rights a recipient will receive if given access to the resource
+ ///
+ public Dictionary? RightDescription { get; set; }
+
+ ///
+ /// The homepage
+ ///
+ public string? Homepage { get; set; }
+
+ ///
+ /// The status
+ ///
+ public string? Status { get; set; }
+
+ ///
+ /// spatial coverage
+ /// This property represents that area(s) a Public Service is likely to be available only within, typically the area(s) covered by a particular public authority.
+ ///
+ public List? Spatial { get; set; }
+
+ ///
+ /// List of possible contact points
+ ///
+ [Required]
+ public List? ContactPoints { get; set; }
+
+ ///
+ /// Linkes to the outcome of a public service
+ ///
+ public List? Produces { get; set; }
+
+ ///
+ /// IsPartOf
+ ///
+ public string? IsPartOf { get; set; }
+
+ ///
+ /// ThematicAreas
+ ///
+ public List? ThematicAreas { get; set; }
+
+ ///
+ /// ResourceReference
+ ///
+ public List? ResourceReferences { get; set; }
+
+ ///
+ /// Is this resource possible to delegate to others or not
+ ///
+ public bool Delegable { get; set; } = true;
+
+ ///
+ /// The visibility of the resource
+ ///
+ public bool Visible { get; set; } = true;
+
+ ///
+ /// HasCompetentAuthority
+ ///
+ [Required]
+ public CompetentAuthority? HasCompetentAuthority { get; set; }
+
+ ///
+ /// Keywords
+ ///
+ public List? Keywords { get; set; }
+
+ ///
+ /// Sets the access list mode for the resource
+ ///
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public ResourceAccessListMode AccessListMode { get; set; }
+
+ ///
+ /// The user acting on behalf of party can be a selfidentifed users
+ ///
+ public bool SelfIdentifiedUserEnabled { get; set; }
+
+ ///
+ /// The user acting on behalf of party can be an enterprise users
+ ///
+ public bool EnterpriseUserEnabled { get; set; }
+
+ ///
+ /// ResourceType
+ ///
+ [JsonConverter(typeof(JsonStringEnumConverter))]
+ public ResourceType ResourceType { get; set; }
+
+ ///
+ /// Available for type defines which type of entity / person that resource targets
+ ///
+ public List? AvailableForType { get; set; }
+
+ ///
+ /// List of autorizationReference attributes to reference this resource in authorization API
+ ///
+ public List? AuthorizationReference { get; set; }
+
+ ///
+ /// Writes key information when this object is written to Log.
+ ///
+ ///
+ public override string ToString()
+ {
+ return $"Identifier: {Identifier}, ResourceType: {ResourceType}";
+ }
+}
diff --git a/src/Authorization/Program.cs b/src/Authorization/Program.cs
index ce1b798e..b83f03a1 100644
--- a/src/Authorization/Program.cs
+++ b/src/Authorization/Program.cs
@@ -1,15 +1,20 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading.Tasks;
using Altinn.ApiClients.Maskinporten.Extensions;
using Altinn.ApiClients.Maskinporten.Services;
+using Altinn.Common.AccessToken;
+using Altinn.Common.AccessToken.Configuration;
+using Altinn.Common.AccessToken.Services;
using Altinn.Common.AccessTokenClient.Services;
using Altinn.Common.PEP.Authorization;
using Altinn.Platform.Authorization.Clients;
using Altinn.Platform.Authorization.Clients.Interfaces;
using Altinn.Platform.Authorization.Configuration;
using Altinn.Platform.Authorization.Constants;
+using Altinn.Platform.Authorization.Extensions;
using Altinn.Platform.Authorization.Filters;
using Altinn.Platform.Authorization.Health;
using Altinn.Platform.Authorization.ModelBinding;
@@ -20,8 +25,6 @@
using Altinn.Platform.Authorization.Services.Interface;
using Altinn.Platform.Authorization.Services.Interfaces;
using Altinn.Platform.Telemetry;
-
-using AltinnCore.Authentication.Constants;
using AltinnCore.Authentication.JwtCookie;
using Azure.Identity;
@@ -46,7 +49,7 @@
using Microsoft.IdentityModel.Tokens;
using Microsoft.OpenApi.Models;
using Npgsql;
-
+using Swashbuckle.AspNetCore.Filters;
using Yuniql.AspNetCore;
using Yuniql.PostgreSql;
@@ -213,12 +216,15 @@ void ConfigureServices(IServiceCollection services, IConfiguration config)
services.AddSingleton();
services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
services.Configure(config.GetSection("GeneralSettings"));
services.Configure(config.GetSection("AzureStorageConfiguration"));
services.Configure(config.GetSection("AzureCosmosSettings"));
services.Configure(config.GetSection("PostgreSQLSettings"));
services.Configure(config.GetSection("PlatformSettings"));
+ services.Configure(config.GetSection("kvSetting"));
OedAuthzMaskinportenClientSettings oedAuthzMaskinportenClientSettings = config.GetSection("OedAuthzMaskinportenClientSettings").Get();
services.Configure(config.GetSection("OedAuthzMaskinportenClientSettings"));
services.AddMaskinportenHttpClient(oedAuthzMaskinportenClientSettings);
@@ -232,7 +238,6 @@ void ConfigureServices(IServiceCollection services, IConfiguration config)
services.AddHttpClient();
services.AddHttpClient();
services.TryAddSingleton();
- services.AddSingleton();
services.AddTransient();
services.AddSingleton();
services.AddSingleton();
@@ -263,12 +268,16 @@ void ConfigureServices(IServiceCollection services, IConfiguration config)
{
options.AddPolicy(AuthzConstants.POLICY_STUDIO_DESIGNER, policy => policy.Requirements.Add(new ClaimAccessRequirement("urn:altinn:app", "studio.designer")));
options.AddPolicy(AuthzConstants.ALTINNII_AUTHORIZATION, policy => policy.Requirements.Add(new ClaimAccessRequirement("urn:altinn:app", "sbl.authorization")));
+ options.AddPolicy(AuthzConstants.POLICY_PLATFORMISSUER_ACCESSTOKEN, policy => policy.Requirements.Add(new AccessTokenRequirement(AuthzConstants.PLATFORM_ACCESSTOKEN_ISSUER)));
options.AddPolicy(AuthzConstants.DELEGATIONEVENT_FUNCTION_AUTHORIZATION, policy => policy.Requirements.Add(new ClaimAccessRequirement("urn:altinn:app", "platform.authorization")));
- options.AddPolicy(AuthzConstants.AUTHORIZESCOPEACCESS, policy => policy.Requirements.Add(new ScopeAccessRequirement(new string[] { AuthzConstants.PDP_SCOPE, AuthzConstants.AUTHORIZE_SCOPE, AuthzConstants.AUTHORIZE_ADMIN_SCOPE })));
+ options.AddPolicy(AuthzConstants.AUTHORIZESCOPEACCESS, policy => policy.Requirements.Add(new ScopeAccessRequirement([AuthzConstants.AUTHORIZE_SCOPE, AuthzConstants.AUTHORIZE_ADMIN_SCOPE])));
});
services.AddTransient();
services.AddTransient();
+ services.AddSingleton();
+
+ services.AddPlatformAccessTokenSupport(config, builder.Environment.IsDevelopment());
services.Configure(options =>
{
@@ -298,21 +307,61 @@ void ConfigureServices(IServiceCollection services, IConfiguration config)
});
// Add Swagger support (Swashbuckle)
- services.AddSwaggerGen(c =>
+ services.AddSwaggerGen(options =>
{
- c.SwaggerDoc("v1", new OpenApiInfo { Title = "Altinn Platform Authorization", Version = "v1" });
+ options.SwaggerDoc("v1", new OpenApiInfo { Title = "Altinn Platform Authorization", Version = "v1" });
try
{
string filePath = GetXmlCommentsPathForControllers();
- c.IncludeXmlComments(filePath);
+ options.IncludeXmlComments(filePath);
}
catch
{
// catch swashbuckle exception if it doesn't find the generated xml documentation file
}
+
+ options.AddSecurityDefinition("AuthorizeAPI", new OpenApiSecurityScheme
+ {
+ Name = "AuthorizeAPI",
+ Description = $"Requires one of the following Scopes: [{AuthzConstants.AUTHORIZE_SCOPE}, {AuthzConstants.AUTHORIZE_ADMIN_SCOPE}]",
+ Type = SecuritySchemeType.Http,
+ In = ParameterLocation.Header,
+ Scheme = "bearer",
+ BearerFormat = "JWT"
+ });
+ options.AddSecurityDefinition("SubscriptionKey", new OpenApiSecurityScheme
+ {
+ Name = "SubscriptionKey",
+ Description = $"Requires a valid product subscription key as header value: \"Ocp-Apim-Subscription-Key\"",
+ Type = SecuritySchemeType.ApiKey,
+ In = ParameterLocation.Header
+ });
+ options.OperationFilter();
+
+ var originalIdSelector = options.SchemaGeneratorOptions.SchemaIdSelector;
+ options.SchemaGeneratorOptions.SchemaIdSelector = (Type t) =>
+ {
+ if (!t.IsNested)
+ {
+ return originalIdSelector(t);
+ }
+
+ var chain = new List();
+ do
+ {
+ chain.Add(originalIdSelector(t));
+ t = t.DeclaringType;
+ }
+ while (t != null);
+
+ chain.Reverse();
+ return string.Join(".", chain);
+ };
});
+ services.AddUrnSwaggerSupport();
+
services.AddFeatureManagement();
}
diff --git a/src/Authorization/Services/Implementation/AccessListAuthorization.cs b/src/Authorization/Services/Implementation/AccessListAuthorization.cs
new file mode 100644
index 00000000..8983baf2
--- /dev/null
+++ b/src/Authorization/Services/Implementation/AccessListAuthorization.cs
@@ -0,0 +1,49 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Altinn.Authorization.Enums;
+using Altinn.Authorization.Models.ResourceRegistry;
+using Altinn.Authorization.ProblemDetails;
+using Altinn.Platform.Authorization.Models;
+using Altinn.Platform.Authorization.Services.Interface;
+
+namespace Altinn.Platform.Authorization.Services.Implementation;
+
+///
+/// The service used to map internal delegation change to delegation change events and push them to the event queue.
+///
+public class AccessListAuthorization : IAccessListAuthorization
+{
+ private readonly IResourceRegistry _client;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public AccessListAuthorization(IResourceRegistry client)
+ {
+ _client = client;
+ }
+
+ ///
+ public async Task> Authorize(AccessListAuthorizationRequest request, CancellationToken cancellationToken = default)
+ {
+ AccessListAuthorizationResponse response = AccessListAuthorizationResponse.From(request);
+ IEnumerable memberships = await _client.GetMembershipsForResourceForParty(request.Subject.Value, request.Resource.Value, cancellationToken);
+
+ if (memberships == null || !memberships.Any())
+ {
+ response.Result = AccessListAuthorizationResult.NotAuthorized;
+ }
+ else if (memberships.Any(m => m.ActionFilters == null || m.ActionFilters.Any(actionFilter => actionFilter == request.Action.Value.ValueSpan.ToString())))
+ {
+ response.Result = AccessListAuthorizationResult.Authorized;
+ }
+ else
+ {
+ response.Result = AccessListAuthorizationResult.NotAuthorized;
+ }
+
+ return new(response);
+ }
+}
diff --git a/src/Authorization/Services/Implementation/ContextHandler.cs b/src/Authorization/Services/Implementation/ContextHandler.cs
index 6fa7a1bc..d0fd578d 100644
--- a/src/Authorization/Services/Implementation/ContextHandler.cs
+++ b/src/Authorization/Services/Implementation/ContextHandler.cs
@@ -183,11 +183,11 @@ protected async Task EnrichResourceParty(XacmlContextAttributes requestResourceA
{
if (string.IsNullOrEmpty(resourceAttributes.ResourcePartyValue) && !string.IsNullOrEmpty(resourceAttributes.OrganizationNumber))
{
- int partyId = await _registerService.PartyLookup(resourceAttributes.OrganizationNumber, null);
- if (partyId != 0)
+ Party party = await _registerService.PartyLookup(resourceAttributes.OrganizationNumber, null);
+ if (party != null)
{
- resourceAttributes.ResourcePartyValue = partyId.ToString();
- requestResourceAttributes.Attributes.Add(GetPartyIdsAttribute(new List { partyId }));
+ resourceAttributes.ResourcePartyValue = party.PartyId.ToString();
+ requestResourceAttributes.Attributes.Add(GetPartyIdsAttribute(new List { party.PartyId }));
}
}
else if (string.IsNullOrEmpty(resourceAttributes.ResourcePartyValue) && !string.IsNullOrEmpty(resourceAttributes.PersonId))
@@ -197,11 +197,11 @@ protected async Task EnrichResourceParty(XacmlContextAttributes requestResourceA
throw new ArgumentException("Not allowed to use ssn for internal API");
}
- int partyId = await _registerService.PartyLookup(null, resourceAttributes.PersonId);
- if (partyId != 0)
+ Party party = await _registerService.PartyLookup(null, resourceAttributes.PersonId);
+ if (party != null)
{
- resourceAttributes.ResourcePartyValue = partyId.ToString();
- requestResourceAttributes.Attributes.Add(GetPartyIdsAttribute(new List { partyId }));
+ resourceAttributes.ResourcePartyValue = party.PartyId.ToString();
+ requestResourceAttributes.Attributes.Add(GetPartyIdsAttribute(new List { party.PartyId }));
}
}
}
@@ -397,8 +397,8 @@ protected async Task EnrichSubjectAttributes(XacmlContextRequest request, string
if (isExternalRequest && !string.IsNullOrEmpty(subjectOrgnNo))
{
- int partyId = await _registerService.PartyLookup(subjectOrgnNo, null);
- subjectContextAttributes.Attributes.Add(GetPartyIdsAttribute(new List { partyId }));
+ Party party = await _registerService.PartyLookup(subjectOrgnNo, null);
+ subjectContextAttributes.Attributes.Add(GetPartyIdsAttribute(new List { party.PartyId }));
}
// No need for further enrichment of roles of no user subject exists
diff --git a/src/Authorization/Services/Implementation/DelegationContextHandler.cs b/src/Authorization/Services/Implementation/DelegationContextHandler.cs
index 747d6777..7f23f6b4 100644
--- a/src/Authorization/Services/Implementation/DelegationContextHandler.cs
+++ b/src/Authorization/Services/Implementation/DelegationContextHandler.cs
@@ -1,9 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
-using Altinn.Authorization.ABAC.Interface;
+using Altinn.Authorization.ABAC.Constants;
using Altinn.Authorization.ABAC.Xacml;
using Altinn.Platform.Authorization.Configuration;
using Altinn.Platform.Authorization.Constants;
@@ -112,6 +113,13 @@ public XacmlResourceAttributes GetResourceAttributes(XacmlContextRequest request
return GetResourceAttributeValues(resourceContextAttributes);
}
+ ///
+ public string GetActionString(XacmlContextRequest request)
+ {
+ XacmlContextAttributes actionAttributes = request.Attributes.FirstOrDefault(a => a.Category.OriginalString.Equals(XacmlConstants.MatchAttributeCategory.Action));
+ return actionAttributes?.Attributes.FirstOrDefault(a => a.AttributeId.OriginalString.Equals(XacmlConstants.MatchAttributeIdentifiers.ActionId))?.AttributeValues.FirstOrDefault()?.Value;
+ }
+
///
/// Gets the list of mainunits for a subunit
///
diff --git a/src/Authorization/Services/Implementation/DevAccessTokenGenerator.cs b/src/Authorization/Services/Implementation/DevAccessTokenGenerator.cs
new file mode 100644
index 00000000..299618f0
--- /dev/null
+++ b/src/Authorization/Services/Implementation/DevAccessTokenGenerator.cs
@@ -0,0 +1,47 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Net.Http;
+using System.Security.Cryptography.X509Certificates;
+using Altinn.Common.AccessTokenClient.Services;
+using Altinn.Platform.Authorization.Configuration;
+using Microsoft.Extensions.Options;
+
+namespace Altinn.Authorization.Services;
+
+///
+/// Sets up an access token generator for development environment using the web based Altinn test token generator.
+///
+[ExcludeFromCodeCoverage]
+public class DevAccessTokenGenerator : IAccessTokenGenerator
+{
+ private readonly TokenGeneratorSettings _settings;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public DevAccessTokenGenerator(IOptions settings)
+ {
+ _settings = settings.Value;
+ }
+
+ ///
+ public string GenerateAccessToken(string issuer, string app)
+ {
+ return GetToken(app);
+ }
+
+ ///
+ public string GenerateAccessToken(string issuer, string app, X509Certificate2 certificate)
+ {
+ return GetToken(app);
+ }
+
+ private string GetToken(string app)
+ {
+ var request = new HttpRequestMessage(HttpMethod.Get, $"{_settings.Url}?env={_settings.Env}&app={app}");
+ request.Headers.Authorization = new BasicAuthenticationHeaderValue(_settings.User, _settings.Password);
+
+ using HttpClient client = new HttpClient();
+ var response = client.SendAsync(request).Result.EnsureSuccessStatusCode();
+ return response.Content.ReadAsStringAsync().Result;
+ }
+}
diff --git a/src/Authorization/Services/Implementation/RegisterService.cs b/src/Authorization/Services/Implementation/RegisterService.cs
index 2700b90c..4e92e817 100644
--- a/src/Authorization/Services/Implementation/RegisterService.cs
+++ b/src/Authorization/Services/Implementation/RegisterService.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
@@ -12,6 +13,7 @@
using Altinn.Platform.Register.Models;
using AltinnCore.Authentication.Utils;
using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
@@ -20,6 +22,7 @@ namespace Altinn.Platform.Authorization.Services
///
/// Handles register service
///
+ [ExcludeFromCodeCoverage]
public class RegisterService : IRegisterService
{
private readonly HttpClient _client;
@@ -27,6 +30,7 @@ public class RegisterService : IRegisterService
private readonly GeneralSettings _generalSettings;
private readonly IAccessTokenGenerator _accessTokenGenerator;
private readonly ILogger _logger;
+ private readonly IMemoryCache _memoryCache;
private readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
///
@@ -38,7 +42,8 @@ public RegisterService(
IAccessTokenGenerator accessTokenGenerator,
IOptions generalSettings,
IOptions platformSettings,
- ILogger logger)
+ ILogger logger,
+ IMemoryCache memoryCache)
{
httpClient.BaseAddress = new Uri(platformSettings.Value.ApiRegisterEndpoint);
httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
@@ -47,58 +52,94 @@ public RegisterService(
_generalSettings = generalSettings.Value;
_accessTokenGenerator = accessTokenGenerator;
_logger = logger;
+ _memoryCache = memoryCache;
}
///
public async Task GetParty(int partyId)
{
- Party party = null;
+ string cacheKey = $"p:{partyId}";
+ if (!_memoryCache.TryGetValue(cacheKey, out Party party))
+ {
+ string endpointUrl = $"parties/{partyId}";
+ string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _generalSettings.RuntimeCookieName);
+ string accessToken = _accessTokenGenerator.GenerateAccessToken("platform", "authorization");
- string endpointUrl = $"parties/{partyId}";
- string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _generalSettings.RuntimeCookieName);
- string accessToken = _accessTokenGenerator.GenerateAccessToken("platform", "authorization");
+ HttpResponseMessage response = await _client.GetAsync(endpointUrl, token, accessToken);
- HttpResponseMessage response = await _client.GetAsync(token, endpointUrl, accessToken);
- if (response.StatusCode == HttpStatusCode.OK)
- {
- string responseContent = await response.Content.ReadAsStringAsync();
- party = JsonSerializer.Deserialize(responseContent, _serializerOptions);
- }
- else
- {
- _logger.LogError("// Getting party with partyID {partyId} failed with statuscode {response.StatusCode}", partyId, response.StatusCode);
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+ party = JsonSerializer.Deserialize(responseContent, _serializerOptions);
+ PutInCache(cacheKey, 10, party);
+ }
+ else
+ {
+ _logger.LogError("// Getting party with partyID {partyId} failed with statuscode {response.StatusCode}", partyId, response.StatusCode);
+ }
}
return party;
}
///
- public async Task PartyLookup(string orgNo, string person)
+ public async Task PartyLookup(string orgNo, string person)
{
- string endpointUrl = "parties/lookup";
-
- PartyLookup partyLookup = new PartyLookup() { Ssn = person, OrgNo = orgNo };
+ string cacheKey;
+ PartyLookup partyLookup;
- string bearerToken = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _generalSettings.RuntimeCookieName);
- string accessToken = _accessTokenGenerator.GenerateAccessToken("platform", "authorization");
-
- StringContent content = new StringContent(JsonSerializer.Serialize(partyLookup));
- content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
-
- HttpResponseMessage response = await _client.PostAsync(bearerToken, endpointUrl, content, accessToken);
- if (response.StatusCode == HttpStatusCode.OK)
+ if (!string.IsNullOrWhiteSpace(orgNo))
+ {
+ cacheKey = $"org:{orgNo}";
+ partyLookup = new PartyLookup { OrgNo = orgNo };
+ }
+ else if (!string.IsNullOrWhiteSpace(person))
{
- string responseContent = await response.Content.ReadAsStringAsync();
- Party party = JsonSerializer.Deserialize(responseContent, _serializerOptions);
- return party.PartyId;
+ cacheKey = $"fnr:{person}";
+ partyLookup = new PartyLookup { Ssn = person };
}
else
{
- string reason = await response.Content.ReadAsStringAsync();
- _logger.LogError("// RegisterService // PartyLookup // Failed to lookup party in platform register. Response {response}. \n Reason {reason}.", response, reason);
+ return null;
+ }
- throw await PlatformHttpException.CreateAsync(response);
+ if (!_memoryCache.TryGetValue(cacheKey, out Party party))
+ {
+ string endpointUrl = "parties/lookup";
+
+ string bearerToken = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _generalSettings.RuntimeCookieName);
+ string accessToken = _accessTokenGenerator.GenerateAccessToken("platform", "authorization");
+
+ StringContent content = new StringContent(JsonSerializer.Serialize(partyLookup));
+ content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
+
+ HttpResponseMessage response = await _client.PostAsync(endpointUrl, content, bearerToken, accessToken);
+
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ string responseContent = await response.Content.ReadAsStringAsync();
+ party = JsonSerializer.Deserialize(responseContent, _serializerOptions);
+ PutInCache(cacheKey, 10, party);
+ }
+ else
+ {
+ string reason = await response.Content.ReadAsStringAsync();
+ _logger.LogError("// RegisterService // PartyLookup // Failed to lookup party in platform register. Response {response}. \n Reason {reason}.", response, reason);
+
+ throw await PlatformHttpException.CreateAsync(response);
+ }
}
+
+ return party;
+ }
+
+ private void PutInCache(string cachekey, int cacheTimeout, object cacheObject)
+ {
+ var cacheEntryOptions = new MemoryCacheEntryOptions()
+ .SetPriority(CacheItemPriority.High)
+ .SetAbsoluteExpiration(new TimeSpan(0, cacheTimeout, 0));
+
+ _memoryCache.Set(cachekey, cacheObject, cacheEntryOptions);
}
}
}
diff --git a/src/Authorization/Services/Implementation/ResourceRegistryWrapper.cs b/src/Authorization/Services/Implementation/ResourceRegistryWrapper.cs
index 1f41ae01..adb451a5 100644
--- a/src/Authorization/Services/Implementation/ResourceRegistryWrapper.cs
+++ b/src/Authorization/Services/Implementation/ResourceRegistryWrapper.cs
@@ -1,12 +1,21 @@
using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Net;
using System.Net.Http;
+using System.Net.Http.Json;
+using System.Text.Json;
+using System.Threading;
using System.Threading.Tasks;
using Altinn.Authorization.ABAC.Xacml;
+using Altinn.Authorization.Models.Register;
+using Altinn.Authorization.Models.ResourceRegistry;
+using Altinn.Common.AccessTokenClient.Services;
using Altinn.Platform.Authorization.Clients;
using Altinn.Platform.Authorization.Configuration;
+using Altinn.Platform.Authorization.Extensions;
using Altinn.Platform.Authorization.Helpers;
-using Altinn.Platform.Authorization.Models;
using Altinn.Platform.Authorization.Services.Interface;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.Options;
@@ -16,53 +25,102 @@ namespace Altinn.Platform.Authorization.Services.Implementation
///
/// Wrapper for resource registry
///
+ [ExcludeFromCodeCoverage]
public class ResourceRegistryWrapper : IResourceRegistry
{
- private readonly ResourceRegistryClient _client;
+ private readonly ResourceRegistryClient _resourceRegistry;
+ private readonly IAccessTokenGenerator _accessTokenGenerator;
private readonly IMemoryCache _memoryCache;
private readonly GeneralSettings _generalSettings;
+ private readonly JsonSerializerOptions _jsonOptions;
///
/// Initializes a new instance of the class.
///
- public ResourceRegistryWrapper(ResourceRegistryClient resourceRegistryClient, IMemoryCache memoryCache, IOptions settings)
+ public ResourceRegistryWrapper(ResourceRegistryClient resourceRegistryClient, IAccessTokenGenerator accessTokenGenerator, IMemoryCache memoryCache, IOptions settings)
{
- _client = resourceRegistryClient;
+ _resourceRegistry = resourceRegistryClient;
_generalSettings = settings.Value;
_memoryCache = memoryCache;
+ _accessTokenGenerator = accessTokenGenerator;
+ _jsonOptions = new JsonSerializerOptions
+ {
+ PropertyNameCaseInsensitive = true,
+ PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
+ };
+ }
+
+ ///
+ public async Task GetResourceAsync(string resourceId, CancellationToken cancellationToken = default)
+ {
+ string cacheKey = "r:" + resourceId;
+ if (!_memoryCache.TryGetValue(cacheKey, out ServiceResource resource))
+ {
+ string apiurl = $"resource/{resourceId}";
+ HttpResponseMessage response = await _resourceRegistry.Client.GetAsync(apiurl, cancellationToken);
+
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ resource = await response.Content.ReadFromJsonAsync(_jsonOptions, cancellationToken);
+ PutInCache(cacheKey, _generalSettings.PolicyCacheTimeout, resource);
+ }
+ }
+
+ return resource;
}
///
- public async Task GetResourcePolicyAsync(string resourceId)
+ public async Task GetResourcePolicyAsync(string resourceId, CancellationToken cancellationToken = default)
{
string cacheKey = "resourcepolicy:" + resourceId;
if (!_memoryCache.TryGetValue(cacheKey, out XacmlPolicy policy))
{
string apiurl = $"resource/{resourceId}/policy";
- HttpResponseMessage response = await _client.Client.GetAsync(apiurl);
+ HttpResponseMessage response = await _resourceRegistry.Client.GetAsync(apiurl, cancellationToken);
- if (response.StatusCode == System.Net.HttpStatusCode.OK)
+ if (response.StatusCode == HttpStatusCode.OK)
{
- Stream policyBlob = await response.Content.ReadAsStreamAsync();
+ Stream policyBlob = await response.Content.ReadAsStreamAsync(cancellationToken);
using (policyBlob)
{
policy = (policyBlob.Length > 0) ? PolicyHelper.ParsePolicy(policyBlob) : null;
}
- PutXacmlPolicyInCache(cacheKey, policy);
+ PutInCache(cacheKey, _generalSettings.PolicyCacheTimeout, policy);
}
}
return policy;
}
- private void PutXacmlPolicyInCache(string cachekey, XacmlPolicy policy)
+ ///
+ public async Task> GetMembershipsForResourceForParty(PartyUrn partyUrn, ResourceIdUrn resourceIdUrn, CancellationToken cancellationToken = default)
+ {
+ string cacheKey = $"AccListMemb|{partyUrn}|{resourceIdUrn}";
+ if (!_memoryCache.TryGetValue(cacheKey, out IEnumerable memberships))
+ {
+ string apiurl = $"access-lists/memberships?party={partyUrn}&resource={resourceIdUrn}";
+ string accessToken = _accessTokenGenerator.GenerateAccessToken("platform", "authorization");
+ HttpResponseMessage response = await _resourceRegistry.Client.GetAsync(apiurl, platformAccessToken: accessToken, cancellationToken: cancellationToken);
+
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ ListObject result = await response.Content.ReadFromJsonAsync>(_jsonOptions, cancellationToken);
+ memberships = result.Items;
+ PutInCache(cacheKey, _generalSettings.PolicyCacheTimeout, memberships);
+ }
+ }
+
+ return memberships;
+ }
+
+ private void PutInCache(string cachekey, int cacheTimeout, object cacheObject)
{
var cacheEntryOptions = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
- .SetAbsoluteExpiration(new TimeSpan(0, _generalSettings.PolicyCacheTimeout, 0));
+ .SetAbsoluteExpiration(new TimeSpan(0, cacheTimeout, 0));
- _memoryCache.Set(cachekey, policy, cacheEntryOptions);
+ _memoryCache.Set(cachekey, cacheObject, cacheEntryOptions);
}
}
}
diff --git a/src/Authorization/Services/Interface/IAccessListAuthorization.cs b/src/Authorization/Services/Interface/IAccessListAuthorization.cs
new file mode 100644
index 00000000..2860c0e0
--- /dev/null
+++ b/src/Authorization/Services/Interface/IAccessListAuthorization.cs
@@ -0,0 +1,20 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Altinn.Authorization.ProblemDetails;
+using Altinn.Platform.Authorization.Models;
+
+namespace Altinn.Platform.Authorization.Services.Interface;
+
+///
+/// The service used to map internal delegation change to delegation change events and push them to the event queue.
+///
+public interface IAccessListAuthorization
+{
+ ///
+ /// Authorization of a given party for a resource, through RRR access lists
+ ///
+ /// Accesslist authorization request model
+ /// The
+ /// Boolean whether the access list authorization check passes or not
+ public Task> Authorize(AccessListAuthorizationRequest request, CancellationToken cancellationToken = default);
+}
diff --git a/src/Authorization/Services/Interface/IDelegationContextHandler.cs b/src/Authorization/Services/Interface/IDelegationContextHandler.cs
index 31cb63c3..8672b8c5 100644
--- a/src/Authorization/Services/Interface/IDelegationContextHandler.cs
+++ b/src/Authorization/Services/Interface/IDelegationContextHandler.cs
@@ -47,6 +47,13 @@ public interface IDelegationContextHandler : IContextHandler
/// XacmlResourceAttributes model
public XacmlResourceAttributes GetResourceAttributes(XacmlContextRequest request);
+ ///
+ /// Gets a the Action string from the XacmlContextRequest
+ ///
+ /// The Xacml Context Request
+ /// Action attribute string value
+ public string GetActionString(XacmlContextRequest request);
+
///
/// Gets the list of mainunits for a subunit
///
diff --git a/src/Authorization/Services/Interface/IRegisterService.cs b/src/Authorization/Services/Interface/IRegisterService.cs
index 3fc09d65..6381ea31 100644
--- a/src/Authorization/Services/Interface/IRegisterService.cs
+++ b/src/Authorization/Services/Interface/IRegisterService.cs
@@ -21,6 +21,6 @@ public interface IRegisterService
/// organisation number
/// f or d number
///
- Task PartyLookup(string orgNo, string person);
+ Task PartyLookup(string orgNo, string person);
}
}
diff --git a/src/Authorization/Services/Interface/IResourceRegistry.cs b/src/Authorization/Services/Interface/IResourceRegistry.cs
index 568ef1b5..b6388d85 100644
--- a/src/Authorization/Services/Interface/IResourceRegistry.cs
+++ b/src/Authorization/Services/Interface/IResourceRegistry.cs
@@ -1,18 +1,39 @@
-using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
using Altinn.Authorization.ABAC.Xacml;
+using Altinn.Authorization.Models.Register;
+using Altinn.Authorization.Models.ResourceRegistry;
-namespace Altinn.Platform.Authorization.Services.Interface
+namespace Altinn.Platform.Authorization.Services.Interface;
+
+///
+/// Interface for resource registry
+///
+public interface IResourceRegistry
{
///
- /// Interface for resource registry
+ /// Returns the service resource based on the resourceId, if it exists.
+ ///
+ /// the policyid
+ /// The
+ /// ServiceResource
+ Task GetResourceAsync(string resourceId, CancellationToken cancellationToken = default);
+
+ ///
+ /// Returns a policy based on the resourceId
+ ///
+ /// the policyid
+ /// The
+ /// XacmlPolicy
+ Task GetResourcePolicyAsync(string resourceId, CancellationToken cancellationToken = default);
+
+ ///
+ /// Returns all memberships a given party has access to through access lists, for a given resource.
///
- public interface IResourceRegistry
- {
- ///
- /// Returns a policy based on the resourceId
- ///
- /// the policyid
- /// XacmlPolicy
- Task GetResourcePolicyAsync(string resourceId);
- }
+ /// Urn identifying the party
+ /// Urn identifying the resource
+ /// The
+ /// List of memberships
+ Task> GetMembershipsForResourceForParty(PartyUrn partyUrn, ResourceIdUrn resourceIdUrn, CancellationToken cancellationToken = default);
}
diff --git a/src/Authorization/appsettings.Development.json b/src/Authorization/appsettings.Development.json
index e203e940..0bbe6dcb 100644
--- a/src/Authorization/appsettings.Development.json
+++ b/src/Authorization/appsettings.Development.json
@@ -5,5 +5,11 @@
"System": "Information",
"Microsoft": "Information"
}
+ },
+ "TokenGeneratorSettings": {
+ "Url": "https://altinn-testtools-token-generator.azurewebsites.net/api/GetPlatformAccessToken",
+ "User": "",
+ "Password": "",
+ "Env": "at22"
}
}
diff --git a/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC1_Delegation_Permit.bru b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC1_Delegation_Permit.bru
new file mode 100644
index 00000000..4cb1db40
--- /dev/null
+++ b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC1_Delegation_Permit.bru
@@ -0,0 +1,116 @@
+meta {
+ name: AccessList_AC1_Delegation_Permit
+ type: http
+ seq: 2
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/authorize
+ body: json
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{apimSubscriptionKey}}
+}
+
+body:json {
+ /*
+ See Docs tab for test case description
+ */
+ {
+ "Request": {
+ "ReturnPolicyIdList": false,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:person:identifier-no",
+ "Value": "08827798585"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "read",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "devtest_gar_bruno_accesslist"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "313776735",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const sharedtestdata = require(`./Testdata/Authorization/sharedtestdata.json`);
+
+ var getTokenParameters = {
+ auth_tokenType: sharedtestdata.authTokenType.enterprise,
+ auth_scopes: sharedtestdata.auth_scopes.authorize,
+ auth_org: "digdir",
+ auth_orgNo: "991825827"
+ }
+
+ const token = await testTokenGenerator.getToken(getTokenParameters);
+
+ bru.setVar("bearerToken", token);
+}
+
+script:post-response {
+ //console.log("request url (after): " + req.getUrl());
+}
+
+tests {
+
+ test("POST Authorize AccessList_AC1_Delegation_Permit result", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Permit");
+ });
+}
+
+docs {
+ Issue:
+ https://github.com/Altinn/altinn-access-management/issues/748
+
+ Acceptance Criteria:
+ AC1 - Avgiver med Tilgangssliste tilgang uten action filter - Permit
+
+ GITT en bruker med tilgang til ressurs for avgiver gjennom rolle eller enkeltdelegering
+ NÅR ressursen krever tilgangsliste autorisasjon
+ OG avgiver er medlem av minst en tilgangsliste som er knytt til ressursen
+ OG tilgangslisten for ressursen ikke har noe action filter begrensning for gitte actions
+ SÅ skal bruker få Permit
+
+ Scenario/Testdata setup:
+ The user has received delegation of read action for the resource from the party.
+ The party have access list membership for the resource without action filter.
+}
diff --git a/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC1_Permit.bru b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC1_Permit.bru
new file mode 100644
index 00000000..e26fb0d4
--- /dev/null
+++ b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC1_Permit.bru
@@ -0,0 +1,115 @@
+meta {
+ name: AccessList_AC1_Permit
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/authorize
+ body: json
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{apimSubscriptionKey}}
+}
+
+body:json {
+ /*
+ See Docs tab for test case description
+ */
+ {
+ "Request": {
+ "ReturnPolicyIdList": false,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:person:identifier-no",
+ "Value": "12819498464"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "write",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "devtest_gar_bruno_accesslist"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "313776735",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const sharedtestdata = require(`./Testdata/Authorization/sharedtestdata.json`);
+
+ var getTokenParameters = {
+ auth_tokenType: sharedtestdata.authTokenType.enterprise,
+ auth_scopes: sharedtestdata.auth_scopes.authorize,
+ auth_org: "digdir",
+ auth_orgNo: "991825827"
+ }
+
+ const token = await testTokenGenerator.getToken(getTokenParameters);
+
+ bru.setVar("bearerToken", token);
+}
+
+script:post-response {
+ //console.log("request url (after): " + req.getUrl());
+}
+
+tests {
+
+ test("POST Authorize AccessList_AC1_Permit result", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Permit");
+ });
+}
+
+docs {
+ Issue:
+ https://github.com/Altinn/altinn-access-management/issues/748
+
+ Acceptance Criteria:
+ AC1 - Avgiver med Tilgangssliste tilgang uten action filter - Permit
+
+ GITT en bruker med tilgang til ressurs for avgiver gjennom rolle eller enkeltdelegering
+ NÅR ressursen krever tilgangsliste autorisasjon
+ OG avgiver er medlem av minst en tilgangsliste som er knytt til ressursen
+ OG tilgangslisten for ressursen ikke har noe action filter begrensning for gitte actions
+ SÅ skal bruker få Permit
+
+ Scenario/Testdata setup:
+ The user is DAGL for the party. The party have access list membership for the resource without action filter.
+}
diff --git a/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC2_Delegation_Permit.bru b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC2_Delegation_Permit.bru
new file mode 100644
index 00000000..21706781
--- /dev/null
+++ b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC2_Delegation_Permit.bru
@@ -0,0 +1,116 @@
+meta {
+ name: AccessList_AC2_Delegation_Permit
+ type: http
+ seq: 4
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/authorize
+ body: json
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{apimSubscriptionKey}}
+}
+
+body:json {
+ /*
+ See Docs tab for test case description
+ */
+ {
+ "Request": {
+ "ReturnPolicyIdList": false,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:person:identifier-no",
+ "Value": "08827798585"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "read",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "devtest_gar_bruno_accesslist_actionfilter"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "313776735",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const sharedtestdata = require(`./Testdata/Authorization/sharedtestdata.json`);
+
+ var getTokenParameters = {
+ auth_tokenType: sharedtestdata.authTokenType.enterprise,
+ auth_scopes: sharedtestdata.auth_scopes.authorize,
+ auth_org: "digdir",
+ auth_orgNo: "991825827"
+ }
+
+ const token = await testTokenGenerator.getToken(getTokenParameters);
+
+ bru.setVar("bearerToken", token);
+}
+
+script:post-response {
+ //console.log("request url (after): " + req.getUrl());
+}
+
+tests {
+
+ test("POST Authorize AccessList_AC2_Delegation_Permit result", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Permit");
+ });
+}
+
+docs {
+ Issue:
+ https://github.com/Altinn/altinn-access-management/issues/748
+
+ Acceptance Criteria:
+ AC2 - Avgiver med Tilgangssliste tilgang med action filter - Permit
+
+ GITT en bruker med tilgang til ressurs for avgiver gjennom rolle eller enkeltdelegering
+ NÅR ressursen krever tilgangsliste autorisasjon
+ OG avgiver er medlem av minst en tilgangsliste som er knytt til ressursen
+ OG tilgangslisten for ressursen har action filter begrensning som matcher action brukeren skal autoriseres for
+ SÅ skal bruker få Permit
+
+ Scenario/Testdata setup:
+ The user has received delegation of read action for the resource from the party.
+ The party has access list membership for the resource with action filter for the action: read.
+}
diff --git a/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC2_Permit.bru b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC2_Permit.bru
new file mode 100644
index 00000000..1d454e5e
--- /dev/null
+++ b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC2_Permit.bru
@@ -0,0 +1,115 @@
+meta {
+ name: AccessList_AC2_Permit
+ type: http
+ seq: 3
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/authorize
+ body: json
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{apimSubscriptionKey}}
+}
+
+body:json {
+ /*
+ See Docs tab for test case description
+ */
+ {
+ "Request": {
+ "ReturnPolicyIdList": false,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:person:identifier-no",
+ "Value": "12819498464"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "read",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "devtest_gar_bruno_accesslist_actionfilter"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "313776735",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const sharedtestdata = require(`./Testdata/Authorization/sharedtestdata.json`);
+
+ var getTokenParameters = {
+ auth_tokenType: sharedtestdata.authTokenType.enterprise,
+ auth_scopes: sharedtestdata.auth_scopes.authorize,
+ auth_org: "digdir",
+ auth_orgNo: "991825827"
+ }
+
+ const token = await testTokenGenerator.getToken(getTokenParameters);
+
+ bru.setVar("bearerToken", token);
+}
+
+script:post-response {
+ //console.log("request url (after): " + req.getUrl());
+}
+
+tests {
+
+ test("POST Authorize AccessList_AC2_Permit result", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Permit");
+ });
+}
+
+docs {
+ Issue:
+ https://github.com/Altinn/altinn-access-management/issues/748
+
+ Acceptance Criteria:
+ AC2 - Avgiver med Tilgangssliste tilgang med action filter - Permit
+
+ GITT en bruker med tilgang til ressurs for avgiver gjennom rolle eller enkeltdelegering
+ NÅR ressursen krever tilgangsliste autorisasjon
+ OG avgiver er medlem av minst en tilgangsliste som er knytt til ressursen
+ OG tilgangslisten for ressursen har action filter begrensning som matcher action brukeren skal autoriseres for
+ SÅ skal bruker få Permit
+
+ Scenario/Testdata setup:
+ The user is DAGL for the party. The party have access list membership for the resource with action filter for the action: read.
+}
diff --git a/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC3_Delegation_Deny.bru b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC3_Delegation_Deny.bru
new file mode 100644
index 00000000..603f9f13
--- /dev/null
+++ b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC3_Delegation_Deny.bru
@@ -0,0 +1,115 @@
+meta {
+ name: AccessList_AC3_Delegation_Deny
+ type: http
+ seq: 6
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/authorize
+ body: json
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{apimSubscriptionKey}}
+}
+
+body:json {
+ /*
+ See Docs tab for test case description
+ */
+ {
+ "Request": {
+ "ReturnPolicyIdList": false,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:person:identifier-no",
+ "Value": "12819498464"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "read",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "devtest_gar_bruno_accesslist"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "310631302",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const sharedtestdata = require(`./Testdata/Authorization/sharedtestdata.json`);
+
+ var getTokenParameters = {
+ auth_tokenType: sharedtestdata.authTokenType.enterprise,
+ auth_scopes: sharedtestdata.auth_scopes.authorize,
+ auth_org: "digdir",
+ auth_orgNo: "991825827"
+ }
+
+ const token = await testTokenGenerator.getToken(getTokenParameters);
+
+ bru.setVar("bearerToken", token);
+}
+
+script:post-response {
+ //console.log("request url (after): " + req.getUrl());
+}
+
+tests {
+
+ test("POST Authorize AccessList_AC3_Delegation_Deny result", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Deny");
+ });
+}
+
+docs {
+ Issue:
+ https://github.com/Altinn/altinn-access-management/issues/748
+
+ Acceptance Criteria:
+ AC3 - Avgiver uten Tilgangssliste tilgang - Deny
+
+ GITT en bruker med tilgang til ressurs for avgiver gjennom rolle eller enkeltdelegering
+ NÅR ressursen krever tilgangsliste autorisasjon
+ OG avgiver IKKE er medlem av noen tilgangsliste som er knytt til ressursen
+ SÅ skal bruker få Deny
+
+ Scenario/Testdata setup:
+ The user has received delegation of read action for the resource from the party.
+ The party does not have access list membership for the resource.
+}
diff --git a/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC3_Deny.bru b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC3_Deny.bru
new file mode 100644
index 00000000..dec141f3
--- /dev/null
+++ b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC3_Deny.bru
@@ -0,0 +1,114 @@
+meta {
+ name: AccessList_AC3_Deny
+ type: http
+ seq: 5
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/authorize
+ body: json
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{apimSubscriptionKey}}
+}
+
+body:json {
+ /*
+ See Docs tab for test case description
+ */
+ {
+ "Request": {
+ "ReturnPolicyIdList": false,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:person:identifier-no",
+ "Value": "08827798585"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "read",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "devtest_gar_bruno_accesslist"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "310631302",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const sharedtestdata = require(`./Testdata/Authorization/sharedtestdata.json`);
+
+ var getTokenParameters = {
+ auth_tokenType: sharedtestdata.authTokenType.enterprise,
+ auth_scopes: sharedtestdata.auth_scopes.authorize,
+ auth_org: "digdir",
+ auth_orgNo: "991825827"
+ }
+
+ const token = await testTokenGenerator.getToken(getTokenParameters);
+
+ bru.setVar("bearerToken", token);
+}
+
+script:post-response {
+ //console.log("request url (after): " + req.getUrl());
+}
+
+tests {
+
+ test("POST Authorize AccessList_AC3_Deny result", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Deny");
+ });
+}
+
+docs {
+ Issue:
+ https://github.com/Altinn/altinn-access-management/issues/748
+
+ Acceptance Criteria:
+ AC3 - Avgiver uten Tilgangssliste tilgang - Deny
+
+ GITT en bruker med tilgang til ressurs for avgiver gjennom rolle eller enkeltdelegering
+ NÅR ressursen krever tilgangsliste autorisasjon
+ OG avgiver IKKE er medlem av noen tilgangsliste som er knytt til ressursen
+ SÅ skal bruker få Deny
+
+ Scenario/Testdata setup:
+ The user is DAGL for the party. The party does not have access list membership for the resource.
+}
diff --git a/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC4_Delegation_Deny.bru b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC4_Delegation_Deny.bru
new file mode 100644
index 00000000..8cc0234b
--- /dev/null
+++ b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC4_Delegation_Deny.bru
@@ -0,0 +1,116 @@
+meta {
+ name: AccessList_AC4_Delegation_Deny
+ type: http
+ seq: 8
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/authorize
+ body: json
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{apimSubscriptionKey}}
+}
+
+body:json {
+ /*
+ See Docs tab for test case description
+ */
+ {
+ "Request": {
+ "ReturnPolicyIdList": false,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:person:identifier-no",
+ "Value": "08827798585"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "write",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "devtest_gar_bruno_accesslist_actionfilter"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "313776735",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const sharedtestdata = require(`./Testdata/Authorization/sharedtestdata.json`);
+
+ var getTokenParameters = {
+ auth_tokenType: sharedtestdata.authTokenType.enterprise,
+ auth_scopes: sharedtestdata.auth_scopes.authorize,
+ auth_org: "digdir",
+ auth_orgNo: "991825827"
+ }
+
+ const token = await testTokenGenerator.getToken(getTokenParameters);
+
+ bru.setVar("bearerToken", token);
+}
+
+script:post-response {
+ //console.log("request url (after): " + req.getUrl());
+}
+
+tests {
+
+ test("POST Authorize AccessList_AC4_Deny result", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Deny");
+ });
+}
+
+docs {
+ Issue:
+ https://github.com/Altinn/altinn-access-management/issues/748
+
+ Acceptance Criteria:
+ AC4 - Avgiver med Tilgangssliste tilgang med action filter - Deny
+
+ GITT en bruker med tilgang til ressurs for avgiver gjennom rolle eller enkeltdelegering
+ NÅR ressursen krever tilgangsliste autorisasjon
+ OG avgiver er medlem av minst en tilgangsliste som er knytt til ressursen
+ OG tilgangslisten for ressursen har action filter begrensning som IKKE matcher action brukeren skal autoriseres for
+ SÅ skal bruker få Deny
+
+ Scenario/Testdata setup:
+ The user has received delegation of both read and write action for the resource from the party.
+ The party have access list membership for the resource with action filter for the action: read.
+}
diff --git a/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC4_Deny.bru b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC4_Deny.bru
new file mode 100644
index 00000000..70aaabc4
--- /dev/null
+++ b/test/Bruno/Altinn.Authorization/Automatic Test Collection/Authorize/AccessList_AC4_Deny.bru
@@ -0,0 +1,115 @@
+meta {
+ name: AccessList_AC4_Deny
+ type: http
+ seq: 7
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/authorize
+ body: json
+ auth: inherit
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{apimSubscriptionKey}}
+}
+
+body:json {
+ /*
+ See Docs tab for test case description
+ */
+ {
+ "Request": {
+ "ReturnPolicyIdList": false,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:person:identifier-no",
+ "Value": "12819498464"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "write",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "devtest_gar_bruno_accesslist_actionfilter"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "313776735",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ]
+ }
+ }
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const sharedtestdata = require(`./Testdata/Authorization/sharedtestdata.json`);
+
+ var getTokenParameters = {
+ auth_tokenType: sharedtestdata.authTokenType.enterprise,
+ auth_scopes: sharedtestdata.auth_scopes.authorize,
+ auth_org: "digdir",
+ auth_orgNo: "991825827"
+ }
+
+ const token = await testTokenGenerator.getToken(getTokenParameters);
+
+ bru.setVar("bearerToken", token);
+}
+
+script:post-response {
+ //console.log("request url (after): " + req.getUrl());
+}
+
+tests {
+
+ test("POST Authorize AccessList_AC4_Deny result", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Deny");
+ });
+}
+
+docs {
+ Issue:
+ https://github.com/Altinn/altinn-access-management/issues/748
+
+ Acceptance Criteria:
+ AC4 - Avgiver med Tilgangssliste tilgang med action filter - Deny
+
+ GITT en bruker med tilgang til ressurs for avgiver gjennom rolle eller enkeltdelegering
+ NÅR ressursen krever tilgangsliste autorisasjon
+ OG avgiver er medlem av minst en tilgangsliste som er knytt til ressursen
+ OG tilgangslisten for ressursen har action filter begrensning som IKKE matcher action brukeren skal autoriseres for
+ SÅ skal bruker få Deny
+
+ Scenario/Testdata setup:
+ The user is DAGL for the party. The party have access list membership for the resource with action filter for the action: read.
+}
diff --git "a/test/Bruno/Altinn.Authorization/Manual Test Collection/AccessList Authorization/\303\230rsta for devtest_gar_rrr_accesslist.bru" "b/test/Bruno/Altinn.Authorization/Manual Test Collection/AccessList Authorization/\303\230rsta for devtest_gar_rrr_accesslist.bru"
new file mode 100644
index 00000000..7e9a209d
--- /dev/null
+++ "b/test/Bruno/Altinn.Authorization/Manual Test Collection/AccessList Authorization/\303\230rsta for devtest_gar_rrr_accesslist.bru"
@@ -0,0 +1,73 @@
+meta {
+ name: Ørsta for devtest_gar_rrr_accesslist
+ type: http
+ seq: 1
+}
+
+post {
+ url: {{baseUrl}}/authorization/api/v1/accesslist/authorize
+ body: json
+ auth: bearer
+}
+
+headers {
+ Content-Type: application/json
+ Ocp-Apim-Subscription-Key: {{appsAccessKey}}
+}
+
+auth:bearer {
+ token: eyJhbGciOiJSUzI1NiIsImtpZCI6IjM4OTJENDgyRTYyMDI2NzI1MTJBRTBDMkQ5REJBQzBERTRBNEVDMzciLCJ0eXAiOiJKV1QiLCJ4NWMiOiIzODkyRDQ4MkU2MjAyNjcyNTEyQUUwQzJEOURCQUMwREU0QTRFQzM3In0.eyJzY29wZSI6ImFsdGlubjphdXRob3JpemF0aW9uL2F1dGhvcml6ZS5hZG1pbiIsInRva2VuX3R5cGUiOiJCZWFyZXIiLCJleHAiOjE3Mjg4ODEzNzQsImlhdCI6MTcyNTI4MTM3NCwiY2xpZW50X2lkIjoiOTAwMjhjODctNmIxOC00MzllLTg1YWYtOGE0NmFmZGI5MzljIiwianRpIjoiZHNoSDR1R1ZDQ0VZb3RvODBDMTdqd2JtbVZhMnZyVWFPcDg2LXZ6QkRBWCIsImNvbnN1bWVyIjp7ImF1dGhvcml0eSI6ImlzbzY1MjMtYWN0b3JpZC11cGlzIiwiSUQiOiIwMTkyOjk5MTgyNTgyNyJ9LCJ1cm46YWx0aW5uOm9yZ051bWJlciI6Ijk5MTgyNTgyNyIsInVybjphbHRpbm46YXV0aGVudGljYXRlbWV0aG9kIjoibWFza2lucG9ydGVuIiwidXJuOmFsdGlubjphdXRobGV2ZWwiOjMsImlzcyI6Imh0dHBzOi8vcGxhdGZvcm0uYXQyMi5hbHRpbm4uY2xvdWQvYXV0aGVudGljYXRpb24vYXBpL3YxL29wZW5pZC8iLCJhY3R1YWxfaXNzIjoiYWx0aW5uLXRlc3QtdG9vbHMiLCJuYmYiOjE3MjUyODEzNzQsInVybjphbHRpbm46b3JnIjoiZGlnZGlyIn0.N09_m_fwptp6ChwTrMLKVxIEtCcDN-hdhqpyLVpWrxYoTcIcFcjpiHkUV0MyOKu-3TzQCcRDXaiA_Mhhp957EL7cZI5METV7wuFnnzxbwuwv3y6LplIYMvtb-8BWQ6pvxJB_vuGhp9AtCPpVZRS1M1JgwHXC6D16OMlu5rLwN3q_ck8VquD3uob8Toh7IMqrQk_9u0LwwFiPn8zmOKuZblYIL9tfBAnDt_yQPaLOEXBGHCqcZNeJM3P2T-bAoponpFYs6pC4Gbeu6QDpr1ULFXcl2e1Hy14SF5OiHXVi0nDjeQtYXJEabX6sPQbfOuILCwv03EN8GOBZuwCXxlB26w
+}
+
+body:json {
+ {
+ "subject": {
+ "type": "urn:altinn:organization:identifier-no",
+ "value": "910459880"
+ },
+ "resource": {
+ "type": "urn:altinn:resource",
+ "value": "devtest_gar_rrr_accesslist"
+ },
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "whatever"
+ }
+ }
+}
+
+vars:pre-request {
+ auth_tokenType: Enterprise
+ auth_scopes: altinn:authorization/authorize
+ auth_org: ttd
+ auth_orgNo: 991825827
+}
+
+assert {
+ ~res.status: eq 200
+ ~res.body: contains created
+}
+
+script:pre-request {
+ //await testTokenGenerator.getToken();
+}
+
+tests {
+
+ test("3 POST Decision result on read is permit", function() {
+ const testdata = require(`./Testdata/Authorization/${bru.getEnvVar("tokenEnv")}testdata.json`);
+ const data = res.getBody();
+ expect(res.status).to.equal(200);
+ expect(data.response[0]).to.have.property('decision', "Permit");
+ });
+}
+
+docs {
+ Get a decision from PDP with appOwner details and validate response to have Permit.
+
+ AccessSubject: ['urn:altinn:org']
+
+ Action: ['read']
+
+ Resource: ['urn:altinn:app', 'urn:altinn:org']
+}
diff --git a/test/Bruno/Altinn.Authorization/environments/PROD.bru b/test/Bruno/Altinn.Authorization/environments/PROD.bru
index b8d10fb6..6ffdb653 100644
--- a/test/Bruno/Altinn.Authorization/environments/PROD.bru
+++ b/test/Bruno/Altinn.Authorization/environments/PROD.bru
@@ -1,3 +1,4 @@
vars {
- baseUrl: https://platform.altinn.cloud
+ baseUrl: https://platform.altinn.no
+ apimSubscriptionKey: {{process.env.PROD_APIM_SUBSCRIPTION_KEY}}
}
diff --git a/test/IntegrationTests/AccessListAuthorizationControllerTest.cs b/test/IntegrationTests/AccessListAuthorizationControllerTest.cs
new file mode 100644
index 00000000..10a3b5b1
--- /dev/null
+++ b/test/IntegrationTests/AccessListAuthorizationControllerTest.cs
@@ -0,0 +1,112 @@
+using System;
+using System.IO;
+using System.Net;
+using System.Net.Http;
+using System.Text;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Altinn.Common.AccessToken.Services;
+using Altinn.Common.Authentication.Configuration;
+using Altinn.Platform.Authorization.Controllers;
+using Altinn.Platform.Authorization.IntegrationTests.MockServices;
+using Altinn.Platform.Authorization.IntegrationTests.Util;
+using Altinn.Platform.Authorization.IntegrationTests.Webfactory;
+using Altinn.Platform.Authorization.Models;
+using Altinn.Platform.Authorization.Services.Interface;
+using Altinn.Platform.Authorization.Services.Interfaces;
+using Altinn.Platform.Events.Tests.Mocks;
+using Altinn.ResourceRegistry.Tests.Mocks;
+using AltinnCore.Authentication.JwtCookie;
+using Microsoft.AspNetCore.Mvc.Testing;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Xunit;
+
+namespace Altinn.Platform.Authorization.IntegrationTests;
+
+public class AccessListAuthorizationControllerTest : IClassFixture>
+{
+ private static readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
+ private readonly CustomWebApplicationFactory _factory;
+
+ public AccessListAuthorizationControllerTest(CustomWebApplicationFactory fixture)
+ {
+ _factory = fixture;
+ }
+
+ ///
+ /// Tests the scenario where the request does not have a valid platform access token.
+ ///
+ [Fact]
+ public async Task AccessList_Authorization_Unauthorized_MissingPlatformAccessToken()
+ {
+ // Act
+ HttpResponseMessage response = await GetTestClient().SendAsync(GetPostRequestMessage("Permit_WithoutActionFilter"));
+
+ // Assert
+ Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode);
+ }
+
+ ///
+ /// Tests the scenario where the subject organization has access to the resource 'ttd-accesslist-resource' through access list membership without any action filter.
+ ///
+ [Fact]
+ public async Task AccessList_Authorization_Permit_WithoutActionFilter()
+ {
+ string testCase = "Permit_WithoutActionFilter";
+ AccessListAuthorizationResponse expected = GetExpectedResponse("Permit_WithoutActionFilter");
+
+ // Act
+ HttpResponseMessage response = await GetTestClient().SendAsync(GetPostRequestMessage(testCase, PrincipalUtil.GetAccessToken("access-management", "platform")));
+ string responseContent = await response.Content.ReadAsStringAsync();
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ AccessListAuthorizationResponse actual = JsonSerializer.Deserialize(responseContent, _serializerOptions);
+ AssertionUtil.AssertEqual(expected, actual);
+ }
+
+ private HttpClient GetTestClient()
+ {
+ HttpClient client = _factory.WithWebHostBuilder(builder =>
+ {
+ builder.ConfigureTestServices(services =>
+ {
+ services.AddSingleton();
+ services.AddSingleton, JwtCookiePostConfigureOptionsStub>();
+ services.AddSingleton, OidcProviderPostConfigureSettingsStub>();
+ services.AddSingleton();
+ services.AddSingleton();
+ services.AddSingleton();
+ });
+ }).CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
+
+ return client;
+ }
+
+ private static HttpRequestMessage GetPostRequestMessage(string testCase, string platformAccessToken = null)
+ {
+ string requestPath = Path.Combine(Path.GetDirectoryName(new Uri(typeof(AccessListAuthorizationControllerTest).Assembly.Location).LocalPath), "Data", "Json", "AccessListAuthorization");
+ string requestText = File.ReadAllText(Path.Combine(requestPath, testCase + "_Request.json"));
+
+ HttpRequestMessage message = new HttpRequestMessage(HttpMethod.Post, "authorization/api/v1/accesslist/accessmanagement/authorization")
+ {
+ Content = new StringContent(requestText, Encoding.UTF8, "application/json")
+ };
+
+ if (!string.IsNullOrEmpty(platformAccessToken))
+ {
+ message.Headers.Add("PlatformAccessToken", platformAccessToken);
+ }
+
+ return message;
+ }
+
+ private static AccessListAuthorizationResponse GetExpectedResponse(string testCase)
+ {
+ string requestPath = Path.Combine(Path.GetDirectoryName(new Uri(typeof(AccessListAuthorizationControllerTest).Assembly.Location).LocalPath), "Data", "Json", "AccessListAuthorization");
+ return (AccessListAuthorizationResponse)JsonSerializer.Deserialize(File.ReadAllText(Path.Combine(requestPath, testCase + "_Response.json")), typeof(AccessListAuthorizationResponse), _serializerOptions);
+ }
+}
diff --git a/test/IntegrationTests/Altinn.Platform.Authorization.IntegrationTests.csproj b/test/IntegrationTests/Altinn.Platform.Authorization.IntegrationTests.csproj
index 9f10cc23..8f3e6d04 100644
--- a/test/IntegrationTests/Altinn.Platform.Authorization.IntegrationTests.csproj
+++ b/test/IntegrationTests/Altinn.Platform.Authorization.IntegrationTests.csproj
@@ -48,6 +48,12 @@
Always
+
+ Always
+
+
+ Always
+
Always
@@ -63,213 +69,7 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
- Always
-
-
-
-
-
-
-
diff --git a/test/IntegrationTests/Data/Json/AccessListAuthorization/Permit_WithoutActionFilter_Request.json b/test/IntegrationTests/Data/Json/AccessListAuthorization/Permit_WithoutActionFilter_Request.json
new file mode 100644
index 00000000..366311d1
--- /dev/null
+++ b/test/IntegrationTests/Data/Json/AccessListAuthorization/Permit_WithoutActionFilter_Request.json
@@ -0,0 +1,14 @@
+{
+ "subject": {
+ "type": "urn:altinn:organization:identifier-no",
+ "value": "910459880"
+ },
+ "resource": {
+ "type": "urn:altinn:resource",
+ "value": "ttd-accesslist-resource"
+ },
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "whatever"
+ }
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Json/AccessListAuthorization/Permit_WithoutActionFilter_Response.json b/test/IntegrationTests/Data/Json/AccessListAuthorization/Permit_WithoutActionFilter_Response.json
new file mode 100644
index 00000000..9b340de3
--- /dev/null
+++ b/test/IntegrationTests/Data/Json/AccessListAuthorization/Permit_WithoutActionFilter_Response.json
@@ -0,0 +1,15 @@
+{
+ "subject": {
+ "type": "urn:altinn:organization:identifier-no",
+ "value": "910459880"
+ },
+ "resource": {
+ "type": "urn:altinn:resource",
+ "value": "ttd-accesslist-resource"
+ },
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "whatever"
+ },
+ "result": "Authorized"
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Json/ResourceList/ResourceList.json b/test/IntegrationTests/Data/Json/ResourceList/ResourceList.json
new file mode 100644
index 00000000..dd61d295
--- /dev/null
+++ b/test/IntegrationTests/Data/Json/ResourceList/ResourceList.json
@@ -0,0 +1,137 @@
+[
+ {
+ "identifier": "ttd-externalpdp-resource1",
+ "title": {
+ "en": "External PDP Test Resource1",
+ "nb": "External PDP Test Resource1",
+ "nn": "External PDP Test Resource1"
+ },
+ "description": {
+ "en": "Very nice test resource",
+ "nb": "Veldig fin test ressurs",
+ "nn": "Steikje fine test ressurs"
+ },
+ "rightDescription": {
+ "en": "You'll give access to this nice test resource",
+ "nb": "Du gir tilgang til denne fine test ressursen",
+ "nn": "Du gir tilgong til dinne steikje fine test ressursen"
+ },
+ "homepage": "",
+ "status": "Active",
+ "contactPoints": [],
+ "isPartOf": "",
+ "resourceReferences": [],
+ "delegable": true,
+ "visible": true,
+ "hasCompetentAuthority": {
+ "name": {
+ "en": "Test Ministry",
+ "nb": "Testdepartementet",
+ "nn": "Testdepartementet"
+ },
+ "organization": "991825827",
+ "orgcode": "ttd"
+ },
+ "keywords": [],
+ "AccessListMode": "Disabled",
+ "selfIdentifiedUserEnabled": false,
+ "enterpriseUserEnabled": false,
+ "resourceType": "GenericAccessResource",
+ "authorizationReference": [
+ {
+ "id": "urn:altinn:resource",
+ "value": "ttd-externalpdp-resource1"
+ }
+ ]
+ },
+ {
+ "identifier": "ttd-accesslist-resource",
+ "title": {
+ "en": "PDP Test Resource Requiring AccessList Authorization",
+ "nb": "PDP Test Resource Requiring AccessList Authorization",
+ "nn": "PDP Test Resource Requiring AccessList Authorization"
+ },
+ "description": {
+ "en": "Very nice test resource",
+ "nb": "Veldig fin test ressurs",
+ "nn": "Steikje fine test ressurs"
+ },
+ "rightDescription": {
+ "en": "You'll give access to this nice test resource",
+ "nb": "Du gir tilgang til denne fine test ressursen",
+ "nn": "Du gir tilgong til dinne steikje fine test ressursen"
+ },
+ "homepage": "",
+ "status": "Active",
+ "contactPoints": [],
+ "isPartOf": "",
+ "resourceReferences": [],
+ "delegable": true,
+ "visible": true,
+ "hasCompetentAuthority": {
+ "name": {
+ "en": "Test Ministry",
+ "nb": "Testdepartementet",
+ "nn": "Testdepartementet"
+ },
+ "organization": "991825827",
+ "orgcode": "ttd"
+ },
+ "keywords": [],
+ "AccessListMode": "Enabled",
+ "selfIdentifiedUserEnabled": false,
+ "enterpriseUserEnabled": false,
+ "resourceType": "GenericAccessResource",
+ "authorizationReference": [
+ {
+ "id": "urn:altinn:resource",
+ "value": "ttd-accesslist-resource"
+ }
+ ]
+ },
+ {
+ "identifier": "ttd-accesslist-resource-with-actionfilter",
+ "title": {
+ "en": "PDP Test Resource Requiring AccessList Authorization, AccessLists include action filter",
+ "nb": "PDP Test Resource Requiring AccessList Authorization, AccessLists include action filter",
+ "nn": "PDP Test Resource Requiring AccessList Authorization, AccessLists include action filter"
+ },
+ "description": {
+ "en": "Very nice test resource",
+ "nb": "Veldig fin test ressurs",
+ "nn": "Steikje fine test ressurs"
+ },
+ "rightDescription": {
+ "en": "You'll give access to this nice test resource",
+ "nb": "Du gir tilgang til denne fine test ressursen",
+ "nn": "Du gir tilgong til dinne steikje fine test ressursen"
+ },
+ "homepage": "",
+ "status": "Active",
+ "contactPoints": [],
+ "isPartOf": "",
+ "resourceReferences": [],
+ "delegable": true,
+ "visible": true,
+ "hasCompetentAuthority": {
+ "name": {
+ "en": "Test Ministry",
+ "nb": "Testdepartementet",
+ "nn": "Testdepartementet"
+ },
+ "organization": "991825827",
+ "orgcode": "ttd"
+ },
+ "keywords": [],
+ "AccessListMode": "Enabled",
+ "selfIdentifiedUserEnabled": false,
+ "enterpriseUserEnabled": false,
+ "resourceType": "GenericAccessResource",
+ "authorizationReference": [
+ {
+ "id": "urn:altinn:resource",
+ "value": "ttd-accesslist-resource-with-actionfilter"
+ }
+ ]
+ }
+]
diff --git a/test/IntegrationTests/Data/Register/50005545.json b/test/IntegrationTests/Data/Register/50005545.json
new file mode 100644
index 00000000..53ce3b14
--- /dev/null
+++ b/test/IntegrationTests/Data/Register/50005545.json
@@ -0,0 +1,29 @@
+{
+ "PartyTypeName": 2,
+ "SSN": "",
+ "OrgNumber": "910459880",
+ "Person": null,
+ "Organization": {
+ "OrgNumber": "910459880",
+ "Name": "ØRSTA OG HEGGEDAL REGNSKAP",
+ "UnitType": "AS",
+ "TelephoneNumber": null,
+ "MobileNumber": null,
+ "FaxNumber": null,
+ "EMailAddress": "test@test.test",
+ "InternetAddress": null,
+ "MailingAddress": null,
+ "MailingPostalCode": null,
+ "MailingPostalCity": null,
+ "BusinessAddress": null,
+ "BusinessPostalCode": null,
+ "BusinessPostalCity": null
+ },
+ "PartyId": 50005545,
+ "PartyUuid": "00000000-0000-0000-0005-000000005545",
+ "UnitType": "AS",
+ "Name": "ØRSTA OG HEGGEDAL REGNSKAP",
+ "IsDeleted": false,
+ "OnlyHierarchyElementWithNoAccess": false,
+ "ChildParties": null
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Register/Org/910459880.json b/test/IntegrationTests/Data/Register/Org/910459880.json
new file mode 100644
index 00000000..53ce3b14
--- /dev/null
+++ b/test/IntegrationTests/Data/Register/Org/910459880.json
@@ -0,0 +1,29 @@
+{
+ "PartyTypeName": 2,
+ "SSN": "",
+ "OrgNumber": "910459880",
+ "Person": null,
+ "Organization": {
+ "OrgNumber": "910459880",
+ "Name": "ØRSTA OG HEGGEDAL REGNSKAP",
+ "UnitType": "AS",
+ "TelephoneNumber": null,
+ "MobileNumber": null,
+ "FaxNumber": null,
+ "EMailAddress": "test@test.test",
+ "InternetAddress": null,
+ "MailingAddress": null,
+ "MailingPostalCode": null,
+ "MailingPostalCity": null,
+ "BusinessAddress": null,
+ "BusinessPostalCode": null,
+ "BusinessPostalCity": null
+ },
+ "PartyId": 50005545,
+ "PartyUuid": "00000000-0000-0000-0005-000000005545",
+ "UnitType": "AS",
+ "Name": "ØRSTA OG HEGGEDAL REGNSKAP",
+ "IsDeleted": false,
+ "OnlyHierarchyElementWithNoAccess": false,
+ "ChildParties": null
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Roles/user_20000490/party_50005545/roles.json b/test/IntegrationTests/Data/Roles/user_20000490/party_50005545/roles.json
new file mode 100644
index 00000000..6986aad0
--- /dev/null
+++ b/test/IntegrationTests/Data/Roles/user_20000490/party_50005545/roles.json
@@ -0,0 +1,10 @@
+[
+ {
+ "Type": "altinn",
+ "value": "dagl"
+ },
+ {
+ "Type": "altinn",
+ "value": "apiadm"
+ }
+]
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyAccessListDontSupportPersonRequest.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyAccessListDontSupportPersonRequest.json
new file mode 100644
index 00000000..ae68a794
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyAccessListDontSupportPersonRequest.json
@@ -0,0 +1,40 @@
+{
+ "Request": {
+ "ReturnPolicyIdList": true,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:userid",
+ "Value": "1337"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "write",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "ttd-accesslist-resource-with-actionfilter"
+ },
+ {
+ "AttributeId": "urn:altinn:partyid",
+ "Value": "501337"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyAccessListDontSupportPersonResponse.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyAccessListDontSupportPersonResponse.json
new file mode 100644
index 00000000..daf3682e
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyAccessListDontSupportPersonResponse.json
@@ -0,0 +1,19 @@
+{
+ "response": [
+ {
+ "decision": "Deny",
+ "status": {
+ "statusMessage": null,
+ "statusDetails": null,
+ "statusCode": {
+ "value": "urn:oasis:names:tc:xacml:1.0:status:ok",
+ "statusCode": null
+ }
+ },
+ "obligations": null,
+ "associateAdvice": null,
+ "category": null,
+ "policyIdentifierList": null
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyActionFilterNotMatchingRequest.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyActionFilterNotMatchingRequest.json
new file mode 100644
index 00000000..7de33af7
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyActionFilterNotMatchingRequest.json
@@ -0,0 +1,40 @@
+{
+ "Request": {
+ "ReturnPolicyIdList": true,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:userid",
+ "Value": "20000490"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "write",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "ttd-accesslist-resource-with-actionfilter"
+ },
+ {
+ "AttributeId": "urn:altinn:organization:identifier-no",
+ "Value": "910459880"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyActionFilterNotMatchingResponse.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyActionFilterNotMatchingResponse.json
new file mode 100644
index 00000000..daf3682e
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyActionFilterNotMatchingResponse.json
@@ -0,0 +1,19 @@
+{
+ "response": [
+ {
+ "decision": "Deny",
+ "status": {
+ "statusMessage": null,
+ "statusDetails": null,
+ "statusCode": {
+ "value": "urn:oasis:names:tc:xacml:1.0:status:ok",
+ "statusCode": null
+ }
+ },
+ "obligations": null,
+ "associateAdvice": null,
+ "category": null,
+ "policyIdentifierList": null
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyRequest.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyRequest.json
new file mode 100644
index 00000000..de2d33d1
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyRequest.json
@@ -0,0 +1,40 @@
+{
+ "Request": {
+ "ReturnPolicyIdList": true,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:userid",
+ "Value": "1337"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "write",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "ttd-accesslist-resource"
+ },
+ {
+ "AttributeId": "urn:altinn:partyid",
+ "Value": "500000"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyResponse.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyResponse.json
new file mode 100644
index 00000000..daf3682e
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_DenyResponse.json
@@ -0,0 +1,19 @@
+{
+ "response": [
+ {
+ "decision": "Deny",
+ "status": {
+ "statusMessage": null,
+ "statusDetails": null,
+ "statusCode": {
+ "value": "urn:oasis:names:tc:xacml:1.0:status:ok",
+ "statusCode": null
+ }
+ },
+ "obligations": null,
+ "associateAdvice": null,
+ "category": null,
+ "policyIdentifierList": null
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitRequest.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitRequest.json
new file mode 100644
index 00000000..ac478238
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitRequest.json
@@ -0,0 +1,40 @@
+{
+ "Request": {
+ "ReturnPolicyIdList": true,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:userid",
+ "Value": "20000490"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "read",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "ttd-accesslist-resource"
+ },
+ {
+ "AttributeId": "urn:altinn:partyid",
+ "Value": "50005545"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitResponse.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitResponse.json
new file mode 100644
index 00000000..f6efcc08
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitResponse.json
@@ -0,0 +1,32 @@
+{
+ "response": [
+ {
+ "decision": "Permit",
+ "status": {
+ "statusMessage": null,
+ "statusDetails": null,
+ "statusCode": {
+ "value": "urn:oasis:names:tc:xacml:1.0:status:ok",
+ "statusCode": null
+ }
+ },
+ "obligations": [
+ {
+ "id": "urn:altinn:obligation:1",
+ "attributeAssignment": [
+ {
+ "attributeId": "urn:altinn:obligation-assignment:1",
+ "value": "3",
+ "category": "urn:altinn:minimum-authenticationlevel",
+ "dataType": "http://www.w3.org/2001/XMLSchema#integer",
+ "issuer": null
+ }
+ ]
+ }
+ ],
+ "associateAdvice": null,
+ "category": null,
+ "policyIdentifierList": null
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitWithActionFilterMatchRequest.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitWithActionFilterMatchRequest.json
new file mode 100644
index 00000000..9d683a72
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitWithActionFilterMatchRequest.json
@@ -0,0 +1,40 @@
+{
+ "Request": {
+ "ReturnPolicyIdList": true,
+ "AccessSubject": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:userid",
+ "Value": "20000490"
+ }
+ ]
+ }
+ ],
+ "Action": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "Value": "read",
+ "DataType": "http://www.w3.org/2001/XMLSchema#string"
+ }
+ ]
+ }
+ ],
+ "Resource": [
+ {
+ "Attribute": [
+ {
+ "AttributeId": "urn:altinn:resource",
+ "Value": "ttd-accesslist-resource-with-actionfilter"
+ },
+ {
+ "AttributeId": "urn:altinn:partyid",
+ "Value": "50005545"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitWithActionFilterMatchResponse.json b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitWithActionFilterMatchResponse.json
new file mode 100644
index 00000000..f6efcc08
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ResourceRegistry_AccessListAuthorization_Json_PermitWithActionFilterMatchResponse.json
@@ -0,0 +1,32 @@
+{
+ "response": [
+ {
+ "decision": "Permit",
+ "status": {
+ "statusMessage": null,
+ "statusDetails": null,
+ "statusCode": {
+ "value": "urn:oasis:names:tc:xacml:1.0:status:ok",
+ "statusCode": null
+ }
+ },
+ "obligations": [
+ {
+ "id": "urn:altinn:obligation:1",
+ "attributeAssignment": [
+ {
+ "attributeId": "urn:altinn:obligation-assignment:1",
+ "value": "3",
+ "category": "urn:altinn:minimum-authenticationlevel",
+ "dataType": "http://www.w3.org/2001/XMLSchema#integer",
+ "issuer": null
+ }
+ ]
+ }
+ ],
+ "associateAdvice": null,
+ "category": null,
+ "policyIdentifierList": null
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ttd-accesslist-resource-with-actionfilter/policy.xml b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ttd-accesslist-resource-with-actionfilter/policy.xml
new file mode 100644
index 00000000..ede33bac
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ttd-accesslist-resource-with-actionfilter/policy.xml
@@ -0,0 +1,58 @@
+
+
+
+
+ A rule giving user with role PRIV or DAGL and the ttd the right to read and write to this AccessList Authorized Resource
+
+
+
+
+ PRIV
+
+
+
+
+
+ DAGL
+
+
+
+
+
+ ttd
+
+
+
+
+
+
+
+ ttd-accesslist-resource-with-actionfilter
+
+
+
+
+
+
+
+ read
+
+
+
+
+
+ write
+
+
+
+
+
+
+
+
+
+ 3
+
+
+
+
\ No newline at end of file
diff --git a/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ttd-accesslist-resource/policy.xml b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ttd-accesslist-resource/policy.xml
new file mode 100644
index 00000000..f159c11c
--- /dev/null
+++ b/test/IntegrationTests/Data/Xacml/3.0/ResourceRegistry/ttd-accesslist-resource/policy.xml
@@ -0,0 +1,58 @@
+
+
+
+
+ A rule giving user with role PRIV or DAGL and the ttd the right to read and write to this AccessList Authorized Resource
+
+
+
+
+ PRIV
+
+
+
+
+
+ DAGL
+
+
+
+
+
+ ttd
+
+
+
+
+
+
+
+ ttd-accesslist-resource
+
+
+
+
+
+
+
+ read
+
+
+
+
+
+ write
+
+
+
+
+
+
+
+
+
+ 3
+
+
+
+
\ No newline at end of file
diff --git a/test/IntegrationTests/DelegationsControllerTest.cs b/test/IntegrationTests/DelegationsControllerTest.cs
index da8609ec..79e8f217 100644
--- a/test/IntegrationTests/DelegationsControllerTest.cs
+++ b/test/IntegrationTests/DelegationsControllerTest.cs
@@ -6,6 +6,7 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
+using Altinn.Common.AccessToken.Services;
using Altinn.Platform.Authorization.Constants;
using Altinn.Platform.Authorization.Controllers;
using Altinn.Platform.Authorization.IntegrationTests.Data;
@@ -1287,6 +1288,7 @@ private HttpClient GetTestClient(DelegationChangeEventQueueMock queueMock = null
services.AddSingleton();
services.AddSingleton(queueMock);
services.AddSingleton, JwtCookiePostConfigureOptionsStub>();
+ services.AddSingleton();
});
}).CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
diff --git a/test/IntegrationTests/ExternalDecisionTest.cs b/test/IntegrationTests/ExternalDecisionTest.cs
index df5dadbc..b85e5984 100644
--- a/test/IntegrationTests/ExternalDecisionTest.cs
+++ b/test/IntegrationTests/ExternalDecisionTest.cs
@@ -4,6 +4,7 @@
using Altinn.Authorization.ABAC.Interface;
using Altinn.Authorization.ABAC.Xacml;
using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.AccessToken.Services;
using Altinn.Common.Authentication.Configuration;
using Altinn.Platform.Authorization.Controllers;
using Altinn.Platform.Authorization.IntegrationTests.MockServices;
@@ -36,7 +37,7 @@ public ExternalDecisionTest(CustomWebApplicationFactory fixt
[Fact]
public async Task PDPExternal_Decision_AltinnApps0008()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnApps0008";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -53,7 +54,7 @@ public async Task PDPExternal_Decision_AltinnApps0008()
[Fact]
public async Task PDPExternal_Decision_AltinnApps0010()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnApps0010";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -70,7 +71,7 @@ public async Task PDPExternal_Decision_AltinnApps0010()
[Fact]
public async Task PDPExternal_Decision_AltinnResourceRegistry0005()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnResourceRegistry0005";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -87,7 +88,7 @@ public async Task PDPExternal_Decision_AltinnResourceRegistry0005()
[Fact]
public async Task PDPExternal_Decision_AltinnResourceRegistry0006()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnResourceRegistry0006";
HttpClient client = GetTestClient();
@@ -106,7 +107,7 @@ public async Task PDPExternal_Decision_AltinnResourceRegistry0006()
[Fact]
public async Task PDPExternal_Decision_AltinnResourceRegistry0007()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnResourceRegistry0007";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -123,7 +124,7 @@ public async Task PDPExternal_Decision_AltinnResourceRegistry0007()
[Fact]
public async Task PDPExternal_Decision_AltinnResourceRegistry0008()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnResourceRegistry0008";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -143,7 +144,7 @@ public async Task PDPExternal_Decision_AltinnResourceRegistry0008()
[Fact]
public async Task PDPExternal_Decision_AltinnResourceRegistry0009()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnResourceRegistry0009";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -163,7 +164,7 @@ public async Task PDPExternal_Decision_AltinnResourceRegistry0009()
[Fact]
public async Task PDPExternal_Decision_AltinnResourceRegistry0010()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnResourceRegistry0010";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -183,7 +184,7 @@ public async Task PDPExternal_Decision_AltinnResourceRegistry0010()
[Fact]
public async Task PDPExternal_Decision_AltinnResourceRegistry0011()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnResourceRegistry0011";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -203,7 +204,7 @@ public async Task PDPExternal_Decision_AltinnResourceRegistry0011()
[Fact]
public async Task PDPExternal_Decision_SystemUserWithResourceDelegation_Permit()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "ResourceRegistry_SystemUserWithDelegation_Permit";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -223,7 +224,7 @@ public async Task PDPExternal_Decision_SystemUserWithResourceDelegation_Permit()
[Fact]
public async Task PDPExternal_Decision_SystemUserWithAppDelegation_Permit()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "AltinnApps_SystemUserWithDelegation_Permit";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -243,7 +244,7 @@ public async Task PDPExternal_Decision_SystemUserWithAppDelegation_Permit()
[Fact]
public async Task PDPExternal_Decision_SystemUserWithDelegation_TooManyRequestSubjects_Indeterminate()
{
- string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization:pdp");
+ string token = PrincipalUtil.GetOrgToken("skd", "974761076", "altinn:authorization/authorize");
string testCase = "ResourceRegistry_SystemUserWithDelegation_TooManyRequestSubjects_Indeterminate";
HttpClient client = GetTestClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
@@ -275,7 +276,9 @@ private HttpClient GetTestClient()
services.AddSingleton, JwtCookiePostConfigureOptionsStub>();
services.AddSingleton, OidcProviderPostConfigureSettingsStub>();
services.AddSingleton();
+ services.AddSingleton();
services.AddSingleton();
+ services.AddSingleton();
});
}).CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
diff --git a/test/IntegrationTests/MockServices/PublicSigningKeyProviderMock.cs b/test/IntegrationTests/MockServices/PublicSigningKeyProviderMock.cs
new file mode 100644
index 00000000..3e9cee93
--- /dev/null
+++ b/test/IntegrationTests/MockServices/PublicSigningKeyProviderMock.cs
@@ -0,0 +1,25 @@
+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.Platform.Authorization.IntegrationTests.MockServices;
+
+public class PublicSigningKeyProviderMock : IPublicSigningKeyProvider
+{
+ public Task> GetSigningKeys(string issuer)
+ {
+ List signingKeys = new List();
+
+ X509Certificate2 cert = new X509Certificate2($"{issuer}-org.pem");
+ SecurityKey key = new X509SecurityKey(cert);
+
+ signingKeys.Add(key);
+
+ return Task.FromResult(signingKeys.AsEnumerable());
+ }
+}
diff --git a/test/IntegrationTests/MockServices/RegisterServiceMock.cs b/test/IntegrationTests/MockServices/RegisterServiceMock.cs
index 5bc32f16..033aedcb 100644
--- a/test/IntegrationTests/MockServices/RegisterServiceMock.cs
+++ b/test/IntegrationTests/MockServices/RegisterServiceMock.cs
@@ -7,7 +7,7 @@
using Altinn.Platform.Authorization.Exceptions;
using Altinn.Platform.Authorization.Services.Interfaces;
using Altinn.Platform.Register.Models;
-
+using Microsoft.Extensions.Caching.Memory;
using Newtonsoft.Json;
namespace Altinn.Platform.Events.Tests.Mocks
@@ -15,6 +15,7 @@ namespace Altinn.Platform.Events.Tests.Mocks
public class RegisterServiceMock : IRegisterService
{
private readonly int _partiesCollection;
+ private IMemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions());
public RegisterServiceMock(int partiesCollection = 1)
{
@@ -23,39 +24,72 @@ public RegisterServiceMock(int partiesCollection = 1)
public async Task GetParty(int partyId)
{
- Party party = null;
- string partyPath = GetPartyPath(partyId);
- if (File.Exists(partyPath))
+ string cacheKey = $"p:{partyId}";
+ if (!_memoryCache.TryGetValue(cacheKey, out Party party))
{
- string content = File.ReadAllText(partyPath);
- party = JsonConvert.DeserializeObject(content);
+ string partyPath = GetPartyPath(partyId);
+ if (File.Exists(partyPath))
+ {
+ string content = File.ReadAllText(partyPath);
+ party = JsonConvert.DeserializeObject(content);
+ }
+
+ if (party != null)
+ {
+ PutInCache(cacheKey, 10, party);
+ }
}
return await Task.FromResult(party);
}
- public async Task PartyLookup(string orgNo, string person)
+ public async Task PartyLookup(string orgNo, string person)
{
- string eventsPath = Path.Combine(GetPartiesPath(), $@"{_partiesCollection}.json");
- int partyId = 0;
+ string cacheKey;
+ PartyLookup partyLookup;
- if (File.Exists(eventsPath))
+ if (!string.IsNullOrWhiteSpace(orgNo))
+ {
+ cacheKey = $"org:{orgNo}";
+ partyLookup = new PartyLookup { OrgNo = orgNo };
+ }
+ else if (!string.IsNullOrWhiteSpace(person))
+ {
+ cacheKey = $"fnr:{person}";
+ partyLookup = new PartyLookup { Ssn = person };
+ }
+ else
{
- string content = File.ReadAllText(eventsPath);
- List parties = JsonConvert.DeserializeObject>(content);
+ return null;
+ }
+
+ if (!_memoryCache.TryGetValue(cacheKey, out Party party))
+ {
+ string eventsPath = Path.Combine(GetPartiesPath(), $@"{_partiesCollection}.json");
- if (!string.IsNullOrEmpty(orgNo))
+ if (File.Exists(eventsPath))
{
- partyId = parties.Where(p => p.OrgNumber != null && p.OrgNumber.Equals(orgNo)).Select(p => p.PartyId).FirstOrDefault();
+ string content = File.ReadAllText(eventsPath);
+ List parties = JsonConvert.DeserializeObject>(content);
+
+ if (!string.IsNullOrEmpty(orgNo))
+ {
+ party = parties.Where(p => p.OrgNumber != null && p.OrgNumber.Equals(orgNo)).FirstOrDefault();
+ }
+ else
+ {
+ party = parties.Where(p => p.SSN != null && p.SSN.Equals(person)).FirstOrDefault();
+ }
}
- else
+
+ if (party != null)
{
- partyId = parties.Where(p => p.SSN != null && p.SSN.Equals(person)).Select(p => p.PartyId).FirstOrDefault();
+ PutInCache(cacheKey, 10, party);
}
}
- return partyId > 0
- ? partyId
+ return party != null
+ ? await Task.FromResult(party)
: throw await PlatformHttpException.CreateAsync(new HttpResponseMessage
{ Content = new StringContent(string.Empty), StatusCode = System.Net.HttpStatusCode.NotFound });
}
@@ -71,5 +105,14 @@ private static string GetPartyPath(int partyId)
string unitTestFolder = Path.GetDirectoryName(new Uri(typeof(RegisterServiceMock).Assembly.Location).LocalPath);
return Path.Combine(unitTestFolder, "..", "..", "..", "Data", "Register", partyId.ToString() + ".json");
}
+
+ private void PutInCache(string cachekey, int cacheTimeout, object cacheObject)
+ {
+ var cacheEntryOptions = new MemoryCacheEntryOptions()
+ .SetPriority(CacheItemPriority.High)
+ .SetAbsoluteExpiration(new TimeSpan(0, cacheTimeout, 0));
+
+ _memoryCache.Set(cachekey, cacheObject, cacheEntryOptions);
+ }
}
}
diff --git a/test/IntegrationTests/MockServices/ResourceRegistryMock.cs b/test/IntegrationTests/MockServices/ResourceRegistryMock.cs
index c56d3772..f85c1106 100644
--- a/test/IntegrationTests/MockServices/ResourceRegistryMock.cs
+++ b/test/IntegrationTests/MockServices/ResourceRegistryMock.cs
@@ -1,45 +1,149 @@
using System;
+using System.Collections.Generic;
using System.IO;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using Altinn.Authorization.ABAC.Utils;
using Altinn.Authorization.ABAC.Xacml;
+using Altinn.Authorization.Models.Register;
+using Altinn.Authorization.Models.ResourceRegistry;
using Altinn.Platform.Authorization.Services.Interface;
-using Microsoft.AspNetCore.Http;
-using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Caching.Memory;
-namespace Altinn.Platform.Authorization.IntegrationTests.MockServices
+namespace Altinn.Platform.Authorization.IntegrationTests.MockServices;
+
+public class ResourceRegistryMock : IResourceRegistry
{
- public class ResourceRegistryMock : IResourceRegistry
+ private readonly JsonSerializerOptions options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
+ private IMemoryCache _memoryCache = new MemoryCache(new MemoryCacheOptions());
+
+ public Task GetResourceAsync(string resourceId, CancellationToken cancellationToken = default)
{
- public async Task GetResourcePolicyAsync(string resourceId)
+ string cacheKey = "r:" + resourceId;
+ if (!_memoryCache.TryGetValue(cacheKey, out ServiceResource resource))
{
- if (File.Exists(Path.Combine(GetResourceRegistryPolicyPath(resourceId), "policy.xml")))
+ string unitTestFolder = Path.GetDirectoryName(new Uri(typeof(AltinnApps_DecisionTests).Assembly.Location).LocalPath);
+ string resourceListPath = Path.Combine(unitTestFolder, "Data", "Json", "ResourceList", "ResourceList.json");
+ if (File.Exists(resourceListPath))
+ {
+ string content = File.ReadAllText(resourceListPath);
+ List resourceList = JsonSerializer.Deserialize>(content, options);
+ return Task.FromResult(resourceList.Find(r => r.Identifier.Equals(resourceId)));
+ }
+
+ if (resource != null)
{
- return await Task.FromResult(ParsePolicy("policy.xml", GetResourceRegistryPolicyPath(resourceId)));
+ PutInCache(cacheKey, 5, resource);
}
-
- return null;
}
- private static string GetResourceRegistryPolicyPath(string resourceId)
+ return Task.FromResult(resource);
+ }
+
+ public async Task GetResourcePolicyAsync(string resourceId, CancellationToken cancellationToken = default)
+ {
+ string cacheKey = "resourcepolicy:" + resourceId;
+ if (!_memoryCache.TryGetValue(cacheKey, out XacmlPolicy policy))
{
- string unitTestFolder = Path.GetDirectoryName(new Uri(typeof(AltinnApps_DecisionTests).Assembly.Location).LocalPath);
- return Path.Combine(unitTestFolder, "..", "..", "..", "Data", "Xacml", "3.0", "ResourceRegistry", resourceId);
+ if (File.Exists(Path.Combine(GetResourceRegistryPolicyPath(resourceId), "policy.xml")))
+ {
+ policy = await Task.FromResult(ParsePolicy("policy.xml", GetResourceRegistryPolicyPath(resourceId)));
+ }
+
+ if (policy != null)
+ {
+ if (File.Exists(Path.Combine(GetResourceRegistryPolicyPath(resourceId), "policy.xml")))
+ {
+ return await Task.FromResult(ParsePolicy("policy.xml", GetResourceRegistryPolicyPath(resourceId)));
+ }
+
+ PutInCache(cacheKey, 5, policy);
+ }
}
-
- public static XacmlPolicy ParsePolicy(string policyDocumentTitle, string policyPath)
+
+ return policy;
+ }
+
+ public Task> GetMembershipsForResourceForParty(PartyUrn partyUrn, ResourceIdUrn resourceIdUrn, CancellationToken cancellationToken = default)
+ {
+ partyUrn.IsPartyUuid(out Guid partyUuid);
+ partyUrn.IsOrganizationIdentifier(out OrganizationNumber partyOrgNum);
+ resourceIdUrn.IsResourceId(out ResourceIdentifier resourceId);
+
+ string cacheKey = $"AccListMemb|{partyUrn}|{resourceIdUrn}";
+ if (!_memoryCache.TryGetValue(cacheKey, out IEnumerable memberships))
{
- XmlDocument policyDocument = new XmlDocument();
+ if (partyOrgNum.ToString() == "910459880" && resourceId.ToString() == "ttd-accesslist-resource")
+ {
+ memberships = JsonSerializer.Deserialize>(
+ """
+ [
+ {
+ "party": "urn:altinn:party:uuid:00000000-0000-0000-0005-000000005545",
+ "resource": "urn:altinn:resource:ttd-accesslist-resource",
+ "since": "2024-08-27T15:15:55.446051+00:00"
+ }
+ ]
+ """,
+ options);
+ }
+ else if (partyOrgNum.ToString() == "910459880" && resourceId.ToString() == "ttd-accesslist-resource-with-actionfilter")
+ {
+ memberships = JsonSerializer.Deserialize>(
+ """
+ [
+ {
+ "party": "urn:altinn:party:uuid:00000000-0000-0000-0005-000000005545",
+ "resource": "urn:altinn:resource:ttd-accesslist-resource",
+ "since": "2024-08-27T15:15:55.446051+00:00",
+ "actionFilters": [
+ "read"
+ ]
+ }
+ ]
+ """,
+ options);
+ }
- policyDocument.Load(Path.Combine(policyPath, policyDocumentTitle));
- XacmlPolicy policy;
- using (XmlReader reader = XmlReader.Create(new StringReader(policyDocument.OuterXml)))
+ if (memberships != null)
{
- policy = XacmlParser.ParseXacmlPolicy(reader);
+ PutInCache(cacheKey, 5, memberships);
+ return Task.FromResult(memberships);
}
+ }
+
+ return Task.FromResult(Enumerable.Empty());
+ }
- return policy;
+ private static string GetResourceRegistryPolicyPath(string resourceId)
+ {
+ string unitTestFolder = Path.GetDirectoryName(new Uri(typeof(AltinnApps_DecisionTests).Assembly.Location).LocalPath);
+ return Path.Combine(unitTestFolder, "..", "..", "..", "Data", "Xacml", "3.0", "ResourceRegistry", resourceId);
+ }
+
+ public static XacmlPolicy ParsePolicy(string policyDocumentTitle, string policyPath)
+ {
+ XmlDocument policyDocument = new XmlDocument();
+
+ policyDocument.Load(Path.Combine(policyPath, policyDocumentTitle));
+ XacmlPolicy policy;
+ using (XmlReader reader = XmlReader.Create(new StringReader(policyDocument.OuterXml)))
+ {
+ policy = XacmlParser.ParseXacmlPolicy(reader);
}
+
+ return policy;
+ }
+
+ private void PutInCache(string cachekey, int cacheTimeout, object cacheObject)
+ {
+ var cacheEntryOptions = new MemoryCacheEntryOptions()
+ .SetPriority(CacheItemPriority.High)
+ .SetAbsoluteExpiration(new TimeSpan(0, cacheTimeout, 0));
+
+ _memoryCache.Set(cachekey, cacheObject, cacheEntryOptions);
}
}
diff --git a/test/IntegrationTests/PartiesControllerTest.cs b/test/IntegrationTests/PartiesControllerTest.cs
index 93691325..cac8eebc 100644
--- a/test/IntegrationTests/PartiesControllerTest.cs
+++ b/test/IntegrationTests/PartiesControllerTest.cs
@@ -2,6 +2,7 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
+using Altinn.Common.AccessToken.Services;
using Altinn.Platform.Authorization.Controllers;
using Altinn.Platform.Authorization.IntegrationTests.MockServices;
using Altinn.Platform.Authorization.IntegrationTests.Util;
@@ -61,6 +62,7 @@ private HttpClient GetTestClient()
services.AddSingleton();
services.AddSingleton();
services.AddSingleton, JwtCookiePostConfigureOptionsStub>();
+ services.AddSingleton();
});
}).CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
diff --git a/test/IntegrationTests/PolicyControllerTest.cs b/test/IntegrationTests/PolicyControllerTest.cs
index 1c8bcaf6..663d6df3 100644
--- a/test/IntegrationTests/PolicyControllerTest.cs
+++ b/test/IntegrationTests/PolicyControllerTest.cs
@@ -6,6 +6,7 @@
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Altinn.Authorization.ABAC.Constants;
+using Altinn.Common.AccessToken.Services;
using Altinn.Platform.Authorization.Constants;
using Altinn.Platform.Authorization.Controllers;
using Altinn.Platform.Authorization.IntegrationTests.Data;
@@ -492,6 +493,7 @@ private HttpClient GetTestClient()
services.AddSingleton();
services.AddSingleton();
services.AddSingleton, JwtCookiePostConfigureOptionsStub>();
+ services.AddSingleton();
});
}).CreateClient(new WebApplicationFactoryClientOptions { AllowAutoRedirect = false });
diff --git a/test/IntegrationTests/ResourceRegistry_DecisionTests.cs b/test/IntegrationTests/ResourceRegistry_DecisionTests.cs
index fe3757d7..f5a0c9ce 100644
--- a/test/IntegrationTests/ResourceRegistry_DecisionTests.cs
+++ b/test/IntegrationTests/ResourceRegistry_DecisionTests.cs
@@ -99,6 +99,96 @@ public async Task PDP_Decision_ResourceRegistry_OedFormuesfullmakt_Json_Indeterm
AssertionUtil.AssertEqual(expected, contextResponse);
}
+ ///
+ /// Tests the scenario where the reportee organization has access to 'ttd-accesslist-resource' through access list membership without any action filter.
+ ///
+ [Fact]
+ public async Task PDP_Decision_ResourceRegistry_AccessListAuthorization_Json_Permit()
+ {
+ string testCase = "ResourceRegistry_AccessListAuthorization_Json_Permit";
+ HttpClient client = GetTestClient();
+ HttpRequestMessage httpRequestMessage = TestSetupUtil.CreateJsonProfileXacmlRequest(testCase);
+ XacmlJsonResponse expected = TestSetupUtil.ReadExpectedJsonProfileResponse(testCase);
+
+ // Act
+ XacmlJsonResponse contextResponse = await TestSetupUtil.GetXacmlJsonProfileContextResponseAsync(client, httpRequestMessage);
+
+ // Assert
+ AssertionUtil.AssertEqual(expected, contextResponse);
+ }
+
+ ///
+ /// Tests the scenario where the reportee organization does NOT have access to 'ttd-accesslist-resource' through any access list membership.
+ ///
+ [Fact]
+ public async Task PDP_Decision_ResourceRegistry_AccessListAuthorization_Json_Deny()
+ {
+ string testCase = "ResourceRegistry_AccessListAuthorization_Json_Deny";
+ HttpClient client = GetTestClient();
+ HttpRequestMessage httpRequestMessage = TestSetupUtil.CreateJsonProfileXacmlRequest(testCase);
+ XacmlJsonResponse expected = TestSetupUtil.ReadExpectedJsonProfileResponse(testCase);
+
+ // Act
+ XacmlJsonResponse contextResponse = await TestSetupUtil.GetXacmlJsonProfileContextResponseAsync(client, httpRequestMessage);
+
+ // Assert
+ AssertionUtil.AssertEqual(expected, contextResponse);
+ }
+
+ ///
+ /// Tests the scenario where the reportee organization has access to 'ttd-accesslist-resource' through access list membership with matching action filter.
+ ///
+ [Fact]
+ public async Task PDP_Decision_ResourceRegistry_AccessListAuthorization_Json_PermitWithActionFilterMatch()
+ {
+ string testCase = "ResourceRegistry_AccessListAuthorization_Json_PermitWithActionFilterMatch";
+ HttpClient client = GetTestClient();
+ HttpRequestMessage httpRequestMessage = TestSetupUtil.CreateJsonProfileXacmlRequest(testCase);
+ XacmlJsonResponse expected = TestSetupUtil.ReadExpectedJsonProfileResponse(testCase);
+
+ // Act
+ XacmlJsonResponse contextResponse = await TestSetupUtil.GetXacmlJsonProfileContextResponseAsync(client, httpRequestMessage);
+
+ // Assert
+ AssertionUtil.AssertEqual(expected, contextResponse);
+ }
+
+ ///
+ /// Tests the scenario where the reportee organization has access to 'ttd-accesslist-resource' through access list membership but with action filter not matching the request action.
+ ///
+ [Fact]
+ public async Task PDP_Decision_ResourceRegistry_AccessListAuthorization_Json_DenyActionFilterNotMatching()
+ {
+ string testCase = "ResourceRegistry_AccessListAuthorization_Json_DenyActionFilterNotMatching";
+ HttpClient client = GetTestClient();
+ HttpRequestMessage httpRequestMessage = TestSetupUtil.CreateJsonProfileXacmlRequest(testCase);
+ XacmlJsonResponse expected = TestSetupUtil.ReadExpectedJsonProfileResponse(testCase);
+
+ // Act
+ XacmlJsonResponse contextResponse = await TestSetupUtil.GetXacmlJsonProfileContextResponseAsync(client, httpRequestMessage);
+
+ // Assert
+ AssertionUtil.AssertEqual(expected, contextResponse);
+ }
+
+ ///
+ /// Tests the scenario where the reportee is a person. Currently the access list authorization service only supports organizations.
+ ///
+ [Fact]
+ public async Task PDP_Decision_ResourceRegistry_AccessListAuthorization_Json_DenyAccessListDontSupportPerson()
+ {
+ string testCase = "ResourceRegistry_AccessListAuthorization_Json_DenyAccessListDontSupportPerson";
+ HttpClient client = GetTestClient();
+ HttpRequestMessage httpRequestMessage = TestSetupUtil.CreateJsonProfileXacmlRequest(testCase);
+ XacmlJsonResponse expected = TestSetupUtil.ReadExpectedJsonProfileResponse(testCase);
+
+ // Act
+ XacmlJsonResponse contextResponse = await TestSetupUtil.GetXacmlJsonProfileContextResponseAsync(client, httpRequestMessage);
+
+ // Assert
+ AssertionUtil.AssertEqual(expected, contextResponse);
+ }
+
[Fact]
public async Task PDP_Decision_ResourceRegistry0001()
{
diff --git a/test/IntegrationTests/Util/AssertionUtil.cs b/test/IntegrationTests/Util/AssertionUtil.cs
index 993d6fcb..1f7803cf 100644
--- a/test/IntegrationTests/Util/AssertionUtil.cs
+++ b/test/IntegrationTests/Util/AssertionUtil.cs
@@ -286,6 +286,22 @@ public static void AssertAuthorizationEvent(Mock eventQueue,
numberOfTimes);
}
+ ///
+ /// Assert that two have the same property values.
+ ///
+ /// An instance with the expected values.
+ /// The instance to verify.
+ public static void AssertEqual(AccessListAuthorizationResponse expected, AccessListAuthorizationResponse actual)
+ {
+ Assert.NotNull(actual);
+ Assert.NotNull(expected);
+
+ Assert.Equal(expected.Result, actual.Result);
+ Assert.Equal(expected.Subject.ToString(), actual.Subject.ToString());
+ Assert.Equal(expected.Resource.ToString(), actual.Resource.ToString());
+ Assert.Equal(expected.Action.ToString(), actual.Action.ToString());
+ }
+
private static void AssertEqual(List expected, List actual)
{
if (expected == null)
diff --git a/test/IntegrationTests/Util/JwtTokenMock.cs b/test/IntegrationTests/Util/JwtTokenMock.cs
index 3652b2c0..8edb1e8a 100644
--- a/test/IntegrationTests/Util/JwtTokenMock.cs
+++ b/test/IntegrationTests/Util/JwtTokenMock.cs
@@ -1,5 +1,6 @@
using System;
using System.IdentityModel.Tokens.Jwt;
+using System.IO;
using System.Security.Claims;
using System.Security.Cryptography.X509Certificates;
@@ -26,7 +27,7 @@ public static string GenerateToken(ClaimsPrincipal principal, TimeSpan tokenExpi
{
Subject = new ClaimsIdentity(principal.Identity),
Expires = DateTime.UtcNow.AddSeconds(tokenExpiry.TotalSeconds),
- SigningCredentials = GetSigningCredentials(),
+ SigningCredentials = GetSigningCredentials(issuer),
Audience = "altinn.no",
Issuer = issuer
};
@@ -37,8 +38,17 @@ public static string GenerateToken(ClaimsPrincipal principal, TimeSpan tokenExpi
return serializedToken;
}
- private static SigningCredentials GetSigningCredentials()
+ private static SigningCredentials GetSigningCredentials(string issuer)
{
+ string certPath = "selfSignedTestCertificate.pfx";
+ if (!issuer.Equals("UnitTest") && File.Exists($"{issuer}-org.pfx"))
+ {
+ certPath = $"{issuer}-org.pfx";
+
+ X509Certificate2 certIssuer = new X509Certificate2(certPath);
+ return new X509SigningCredentials(certIssuer, SecurityAlgorithms.RsaSha256);
+ }
+
X509Certificate2 cert = new X509Certificate2("selfSignedTestCertificate.pfx", "qwer1234");
return new X509SigningCredentials(cert, SecurityAlgorithms.RsaSha256);
}
diff --git a/test/IntegrationTests/Util/PrincipalUtil.cs b/test/IntegrationTests/Util/PrincipalUtil.cs
index c76f4dd1..f07321b4 100644
--- a/test/IntegrationTests/Util/PrincipalUtil.cs
+++ b/test/IntegrationTests/Util/PrincipalUtil.cs
@@ -90,10 +90,9 @@ public static ClaimsPrincipal GetClaimsPrincipal(string org, string orgNumber, s
return new ClaimsPrincipal(identity);
}
- public static string GetAccessToken(string appId)
+ public static string GetAccessToken(string appId, string issuer = "www.altinn.no")
{
List claims = new List();
- string issuer = "www.altinn.no";
if (!string.IsNullOrEmpty(appId))
{
claims.Add(new Claim("urn:altinn:app", appId, ClaimValueTypes.String, issuer));
diff --git a/test/IntegrationTests/platform-org.pem b/test/IntegrationTests/platform-org.pem
new file mode 100644
index 00000000..f2a42d40
--- /dev/null
+++ b/test/IntegrationTests/platform-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/IntegrationTests/platform-org.pfx b/test/IntegrationTests/platform-org.pfx
new file mode 100644
index 00000000..6da835fb
Binary files /dev/null and b/test/IntegrationTests/platform-org.pfx differ