diff --git a/Altinn.Authorization.sln b/Altinn.Authorization.sln
index 35d5a780..e6592a5d 100644
--- a/Altinn.Authorization.sln
+++ b/Altinn.Authorization.sln
@@ -65,6 +65,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{35FE03F3
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.Authorization.Hosting.Tests", "src\libs\Altinn.Authorization.Hosting\tests\Altinn.Authorization.Hosting.Tests\Altinn.Authorization.Hosting.Tests.csproj", "{95DC14A3-43E1-4DE8-8C40-3DAF2719B864}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "pkgs", "pkgs", "{CA323293-CA35-413A-8EE2-F33902239D11}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Altinn.Authorization.PEP", "Altinn.Authorization.PEP", "{B1E3ACAE-89C4-4693-95D0-A71DDFA728C7}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A6AC78FE-D74C-4759-9467-087DFB70D5B6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.Authorization.PEP", "src\pkgs\Altinn.Authorization.PEP\src\Altinn.Authorization.PEP\Altinn.Authorization.PEP.csproj", "{874B5EF3-BA5F-41F3-B97D-3EC6DF383BA2}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{355D903B-A1F0-4640-A528-2DB546AA76AE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.Authorization.PEP.Tests", "src\pkgs\Altinn.Authorization.PEP\tests\Altinn.Authorization.PEP.Tests\Altinn.Authorization.PEP.Tests.csproj", "{6CD7B4EE-5AE6-4940-9EC9-3000E5F3E9D0}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -130,6 +142,14 @@ Global
{95DC14A3-43E1-4DE8-8C40-3DAF2719B864}.Debug|Any CPU.Build.0 = Debug|Any CPU
{95DC14A3-43E1-4DE8-8C40-3DAF2719B864}.Release|Any CPU.ActiveCfg = Release|Any CPU
{95DC14A3-43E1-4DE8-8C40-3DAF2719B864}.Release|Any CPU.Build.0 = Release|Any CPU
+ {874B5EF3-BA5F-41F3-B97D-3EC6DF383BA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {874B5EF3-BA5F-41F3-B97D-3EC6DF383BA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {874B5EF3-BA5F-41F3-B97D-3EC6DF383BA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {874B5EF3-BA5F-41F3-B97D-3EC6DF383BA2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6CD7B4EE-5AE6-4940-9EC9-3000E5F3E9D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6CD7B4EE-5AE6-4940-9EC9-3000E5F3E9D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6CD7B4EE-5AE6-4940-9EC9-3000E5F3E9D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6CD7B4EE-5AE6-4940-9EC9-3000E5F3E9D0}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{E9D041A5-2AB6-45FD-8D24-EF552025539E} = {2891B160-9E46-42E5-95FF-08523A3192EF}
@@ -162,5 +182,11 @@ Global
{57E22F21-9A30-4753-851F-AB061A0EA683} = {5AF3188D-BF17-404A-A949-6A88527B61CD}
{35FE03F3-263C-4C98-ABB1-B64BE2FB70CF} = {3C5636A1-2425-49FD-96F9-7884BA3F3797}
{95DC14A3-43E1-4DE8-8C40-3DAF2719B864} = {35FE03F3-263C-4C98-ABB1-B64BE2FB70CF}
+ {CA323293-CA35-413A-8EE2-F33902239D11} = {2891B160-9E46-42E5-95FF-08523A3192EF}
+ {B1E3ACAE-89C4-4693-95D0-A71DDFA728C7} = {CA323293-CA35-413A-8EE2-F33902239D11}
+ {A6AC78FE-D74C-4759-9467-087DFB70D5B6} = {B1E3ACAE-89C4-4693-95D0-A71DDFA728C7}
+ {874B5EF3-BA5F-41F3-B97D-3EC6DF383BA2} = {A6AC78FE-D74C-4759-9467-087DFB70D5B6}
+ {355D903B-A1F0-4640-A528-2DB546AA76AE} = {B1E3ACAE-89C4-4693-95D0-A71DDFA728C7}
+ {6CD7B4EE-5AE6-4940-9EC9-3000E5F3E9D0} = {355D903B-A1F0-4640-A528-2DB546AA76AE}
EndGlobalSection
EndGlobal
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 00000000..df49be66
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,6 @@
+{
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
+ "extends": [
+ "local>Altinn/renovate-config"
+ ]
+}
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 7c7bc05e..a1f783d8 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -10,7 +10,6 @@
MIT
true
Altinn Authorization
- Altinn-Authorization
-
+
\ No newline at end of file
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index 0488e24b..e75d8946 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -1,12 +1,14 @@
+
+ true
+
+
-
+
-
+
@@ -17,10 +19,8 @@
-
-
+
+
@@ -33,42 +33,44 @@
-
+
+
+
+
-
-
+
+
+
+
+
-
+
+
+
+
-
+
-
+
-
+
-
+
@@ -83,8 +85,6 @@
-
-
@@ -98,8 +98,9 @@
+
+
+
-
- true
-
+
\ No newline at end of file
diff --git a/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement/Altinn.AccessManagement.csproj b/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement/Altinn.AccessManagement.csproj
index 0a4c5ca5..912b2604 100644
--- a/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement/Altinn.AccessManagement.csproj
+++ b/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement/Altinn.AccessManagement.csproj
@@ -27,7 +27,6 @@
-
@@ -41,4 +40,4 @@
Include="..\Altinn.AccessManagement.Persistence\Altinn.AccessManagement.Persistence.csproj" />
-
+
\ No newline at end of file
diff --git a/src/apps/Altinn.Authorization/src/Altinn.Authorization/Altinn.Authorization.csproj b/src/apps/Altinn.Authorization/src/Altinn.Authorization/Altinn.Authorization.csproj
index deaade4c..d658ee89 100644
--- a/src/apps/Altinn.Authorization/src/Altinn.Authorization/Altinn.Authorization.csproj
+++ b/src/apps/Altinn.Authorization/src/Altinn.Authorization/Altinn.Authorization.csproj
@@ -38,7 +38,6 @@
-
diff --git a/src/apps/Directory.Build.props b/src/apps/Directory.Build.props
index 9af0ff8c..20e06d22 100644
--- a/src/apps/Directory.Build.props
+++ b/src/apps/Directory.Build.props
@@ -1,4 +1,5 @@
+
-
+
\ No newline at end of file
diff --git a/src/pkgs/Altinn.Authorization.PEP/Altinn.Authorization.PEP.sln b/src/pkgs/Altinn.Authorization.PEP/Altinn.Authorization.PEP.sln
new file mode 100644
index 00000000..2254f8a1
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/Altinn.Authorization.PEP.sln
@@ -0,0 +1,36 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DA970EDE-848F-4A7A-8F67-13835BD4DA89}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.Authorization.PEP", "src\Altinn.Authorization.PEP\Altinn.Authorization.PEP.csproj", "{C99D2680-4A77-40E4-99E0-C2FBBB4C709C}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DDA3BB50-F068-4BDB-8684-CBF68ECDE9FC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Altinn.Authorization.PEP.Tests", "tests\Altinn.Authorization.PEP.Tests\Altinn.Authorization.PEP.Tests.csproj", "{9005F003-ECD8-4EA7-9149-F47F4FEC5697}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {C99D2680-4A77-40E4-99E0-C2FBBB4C709C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C99D2680-4A77-40E4-99E0-C2FBBB4C709C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C99D2680-4A77-40E4-99E0-C2FBBB4C709C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C99D2680-4A77-40E4-99E0-C2FBBB4C709C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9005F003-ECD8-4EA7-9149-F47F4FEC5697}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9005F003-ECD8-4EA7-9149-F47F4FEC5697}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9005F003-ECD8-4EA7-9149-F47F4FEC5697}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9005F003-ECD8-4EA7-9149-F47F4FEC5697}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {C99D2680-4A77-40E4-99E0-C2FBBB4C709C} = {DA970EDE-848F-4A7A-8F67-13835BD4DA89}
+ {9005F003-ECD8-4EA7-9149-F47F4FEC5697} = {DDA3BB50-F068-4BDB-8684-CBF68ECDE9FC}
+ EndGlobalSection
+EndGlobal
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Altinn.Authorization.PEP.csproj b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Altinn.Authorization.PEP.csproj
new file mode 100644
index 00000000..8df6b10f
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Altinn.Authorization.PEP.csproj
@@ -0,0 +1,25 @@
+
+
+
+
+ true
+ Altinn.Authorization.PEP
+ Altinn;Studio;Authorization;Policy;Enforcement;Point
+
+ Policy Enforcement Point for Attribute-based authorization using
+ Altinn.Authorization.ABAC in ASP.Net apps.
+ See our repository for the full documentation.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/AppAccessHandler.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/AppAccessHandler.cs
new file mode 100644
index 00000000..00c8cb84
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/AppAccessHandler.cs
@@ -0,0 +1,71 @@
+using System;
+using System.Threading.Tasks;
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.PEP.Helpers;
+using Altinn.Common.PEP.Interfaces;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.Logging;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ ///
+ /// AuthorizationHandler that is created for handling access to app.
+ /// Authorizes based om AppAccessRequirement and app id from route
+ /// for details about authorization
+ /// in asp.net core
+ ///
+ public class AppAccessHandler : AuthorizationHandler
+ {
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly IPDP _pdp;
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The http context accessor
+ /// The pdp
+ /// The logger.
+ public AppAccessHandler(
+ IHttpContextAccessor httpContextAccessor,
+ IPDP pdp,
+ ILogger logger)
+ {
+ _httpContextAccessor = httpContextAccessor;
+ _pdp = pdp;
+ _logger = logger;
+ }
+
+ ///
+ /// This method authorize access bases on context and requirement
+ /// Is triggered by annotation on MVC action and setup in startup.
+ ///
+ /// The context
+ /// The requirement
+ /// A Task
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, AppAccessRequirement requirement)
+ {
+ HttpContext httpContext = _httpContextAccessor.HttpContext;
+
+ XacmlJsonRequestRoot request = DecisionHelper.CreateDecisionRequest(context, requirement, _httpContextAccessor.HttpContext.GetRouteData());
+
+ XacmlJsonResponse response = await _pdp.GetDecisionForRequest(request);
+
+ if (response?.Response == null)
+ {
+ throw new ArgumentNullException("response");
+ }
+
+ if (!DecisionHelper.ValidatePdpDecision(response.Response, context.User))
+ {
+ context.Fail();
+ }
+
+ context.Succeed(requirement);
+ await Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/AppAccessRequirement.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/AppAccessRequirement.cs
new file mode 100644
index 00000000..e25e988e
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/AppAccessRequirement.cs
@@ -0,0 +1,26 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ ///
+ /// Requirement for authorization policies used for accessing apps.
+ /// for details about authorization
+ /// in asp.net core.
+ ///
+ public class AppAccessRequirement : IAuthorizationRequirement
+ {
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// The Action type for this requirement
+ public AppAccessRequirement(string actionType)
+ {
+ this.ActionType = actionType;
+ }
+
+ ///
+ /// Gets or sets The Action type defined for the policy using this requirement
+ ///
+ public string ActionType { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ClaimAccessHandler.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ClaimAccessHandler.cs
new file mode 100644
index 00000000..af04706f
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ClaimAccessHandler.cs
@@ -0,0 +1,56 @@
+using System.Security.Claims;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.Logging;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ ///
+ /// Authorization handler that verifies that the user has a given claimtype with a given value
+ /// from a given issuer
+ ///
+ public class ClaimAccessHandler : AuthorizationHandler
+ {
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public ClaimAccessHandler()
+ {
+ }
+
+ ///
+ /// This method authorize access bases on context and requirement
+ /// Is triggered by annotation on MVC action and setup in startup.
+ ///
+ /// The context
+ /// The requirement
+ /// No object or value is returned by this method when it completes.
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimAccessRequirement requirement)
+ {
+ bool isAuthorized = false;
+ if (context.User != null && context.User.Claims != null)
+ {
+ foreach (Claim claim in context.User.Claims)
+ {
+ if (claim.Type.Equals(requirement.ClaimType)
+ && claim.Value.Equals(requirement.ClaimValue))
+ {
+ isAuthorized = true;
+ break;
+ }
+ }
+ }
+
+ if (isAuthorized)
+ {
+ context.Succeed(requirement);
+ }
+ else
+ {
+ context.Fail();
+ }
+
+ await Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ClaimAccessRequirement.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ClaimAccessRequirement.cs
new file mode 100644
index 00000000..b88657a0
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ClaimAccessRequirement.cs
@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ ///
+ /// Requirement for authorization policies used for accessing apps.
+ /// for details about authorization
+ /// in asp.net core.
+ ///
+ public class ClaimAccessRequirement : IAuthorizationRequirement
+ {
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// The claim type.
+ /// The claim value
+ public ClaimAccessRequirement(string claimType, string claimValue)
+ {
+ this.ClaimType = claimType;
+ this.ClaimValue = claimValue;
+ }
+
+ ///
+ /// Gets or sets the claim type for the required claim
+ ///
+ public string ClaimType { get; set; }
+
+ ///
+ /// Gets or sets the claim value
+ ///
+ public string ClaimValue { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/IScopeAccessRequirement.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/IScopeAccessRequirement.cs
new file mode 100644
index 00000000..ecd63f36
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/IScopeAccessRequirement.cs
@@ -0,0 +1,16 @@
+#nullable enable
+
+using Microsoft.AspNetCore.Authorization;
+
+namespace Altinn.Common.PEP.Authorization;
+
+///
+/// This interface describes the implementation of a scope access requirement in policy based authorization
+///
+public interface IScopeAccessRequirement : IAuthorizationRequirement
+{
+ ///
+ /// Gets or sets the scope defined for the policy using this requirement
+ ///
+ string[] Scope { get; set; }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ResourceAccessHandler.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ResourceAccessHandler.cs
new file mode 100644
index 00000000..b4a0dfad
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ResourceAccessHandler.cs
@@ -0,0 +1,70 @@
+using System;
+using System.Threading.Tasks;
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.PEP.Helpers;
+using Altinn.Common.PEP.Interfaces;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+using Microsoft.Extensions.Logging;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ ///
+ /// AuthorizationHandler that is created for handling access to app.
+ /// Authorizes based om AppAccessRequirement and app id from route
+ /// for details about authorization
+ /// in asp.net core
+ ///
+ public class ResourceAccessHandler : AuthorizationHandler
+ {
+ private readonly IHttpContextAccessor _httpContextAccessor;
+ private readonly IPDP _pdp;
+ private readonly ILogger _logger;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The http context accessor
+ /// The pdp
+ /// The logger.
+ public ResourceAccessHandler(
+ IHttpContextAccessor httpContextAccessor,
+ IPDP pdp,
+ ILogger logger)
+ {
+ _httpContextAccessor = httpContextAccessor;
+ _pdp = pdp;
+ _logger = logger;
+ }
+
+ ///
+ /// This method authorize access bases on context and requirement
+ /// Is triggered by annotation on MVC action and setup in startup.
+ ///
+ /// The context
+ /// The requirement
+ /// A Task
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ResourceAccessRequirement requirement)
+ {
+ HttpContext httpContext = _httpContextAccessor.HttpContext;
+
+ XacmlJsonRequestRoot request = DecisionHelper.CreateDecisionRequest(context, requirement, _httpContextAccessor.HttpContext.GetRouteData(), _httpContextAccessor.HttpContext.Request.Headers);
+
+ XacmlJsonResponse response = await _pdp.GetDecisionForRequest(request);
+
+ if (response?.Response == null)
+ {
+ throw new ArgumentNullException("response");
+ }
+
+ if (!DecisionHelper.ValidatePdpDecision(response.Response, context.User))
+ {
+ context.Fail();
+ }
+
+ context.Succeed(requirement);
+ await Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ResourceAccessRequirement.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ResourceAccessRequirement.cs
new file mode 100644
index 00000000..e6455ef2
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ResourceAccessRequirement.cs
@@ -0,0 +1,33 @@
+using Microsoft.AspNetCore.Authorization;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ ///
+ /// Requirement for authorization policies used for accessing apps.
+ /// for details about authorization
+ /// in asp.net core.
+ ///
+ public class ResourceAccessRequirement : IAuthorizationRequirement
+ {
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// The Action type for this requirement
+ /// The resource id for the resource authorization is verified for
+ public ResourceAccessRequirement(string actionType, string resourceId)
+ {
+ this.ActionType = actionType;
+ this.ResourceId = resourceId;
+ }
+
+ ///
+ /// Gets or sets The Action type defined for the policy using this requirement
+ ///
+ public string ActionType { get; set; }
+
+ ///
+ /// Gets or sets the resourcId for the resource that authorization should verified for
+ ///
+ public string ResourceId { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ScopeAccessHandler.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ScopeAccessHandler.cs
new file mode 100644
index 00000000..ba95fade
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ScopeAccessHandler.cs
@@ -0,0 +1,56 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+
+using Microsoft.AspNetCore.Authorization;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ ///
+ /// Represents an authorization handler that can perform authorization based on scope
+ ///
+ public class ScopeAccessHandler : AuthorizationHandler
+ {
+ ///
+ /// Performs necessary logic to evaluate the scope requirement.
+ ///
+ /// The current
+ /// The scope requirement to evaluate.
+ /// Returns a Task for async await
+ protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, IScopeAccessRequirement requirement)
+ {
+ // get scope parameter from user claims
+ string contextScope = context.User?.Identities
+ ?.FirstOrDefault(i => i.AuthenticationType != null && i.AuthenticationType.Equals("AuthenticationTypes.Federation"))?.Claims
+ .Where(c => c.Type.Equals("urn:altinn:scope"))?
+ .Select(c => c.Value).FirstOrDefault();
+
+ contextScope ??= context.User?.Claims.Where(c => c.Type.Equals("scope")).Select(c => c.Value).FirstOrDefault();
+
+ bool validScope = false;
+
+ // compare scope claim value to
+ if (!string.IsNullOrWhiteSpace(contextScope))
+ {
+ string[] requiredScopes = requirement.Scope;
+ List clientScopes = contextScope.Split(' ').ToList();
+
+ foreach (string requiredScope in requiredScopes)
+ {
+ if (clientScopes.Contains(requiredScope))
+ {
+ validScope = true;
+ break;
+ }
+ }
+ }
+
+ if (validScope)
+ {
+ context.Succeed(requirement);
+ }
+
+ await Task.CompletedTask;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ScopeAccessRequirement.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ScopeAccessRequirement.cs
new file mode 100644
index 00000000..532fd4f3
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Authorization/ScopeAccessRequirement.cs
@@ -0,0 +1,38 @@
+#nullable enable
+
+using Microsoft.AspNetCore.Authorization;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ ///
+ /// Requirement for authorization policies used for validating a client scope.
+ /// for details about authorization
+ /// in asp.net core.
+ ///
+ public class ScopeAccessRequirement : IScopeAccessRequirement
+ {
+ ///
+ /// Initializes a new instance of the class and
+ /// pupulates the Scope property with the given scope.
+ ///
+ /// The scope for this requirement
+ public ScopeAccessRequirement(string scope)
+ {
+ Scope = new string[] { scope };
+ }
+
+ ///
+ /// Initializes a new instance of the class with the given scopes.
+ ///
+ /// The scope for this requirement
+ public ScopeAccessRequirement(string[] scopes)
+ {
+ Scope = scopes;
+ }
+
+ ///
+ /// Gets or sets the scope defined for the policy using this requirement
+ ///
+ public string[] Scope { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Clients/AuthorizationApiClient.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Clients/AuthorizationApiClient.cs
new file mode 100644
index 00000000..a9f31462
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Clients/AuthorizationApiClient.cs
@@ -0,0 +1,86 @@
+using System.Diagnostics;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Text;
+using System.Text.Json;
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.PEP.Configuration;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+
+namespace Altinn.Common.PEP.Clients
+{
+ ///
+ /// Represents a form of types HttpClient for communication with the Authorization platform service.
+ ///
+ public class AuthorizationApiClient
+ {
+ private const string SubscriptionKeyHeaderName = "Ocp-Apim-Subscription-Key";
+ private const string ForwardedForHeaderName = "x-forwarded-for";
+ private readonly HttpClient _httpClient;
+ private readonly ILogger _logger;
+ private readonly IHttpContextAccessor _httpContextAccessor;
+
+ ///
+ /// Initialize a new instance of the class.
+ ///
+ /// the heep context accessor
+ /// A HttpClient provided by the built in HttpClientFactory.
+ /// The current platform settings
+ /// A logger provided by the built in LoggerFactory.
+ public AuthorizationApiClient(
+ IHttpContextAccessor httpContextAccessor,
+ HttpClient client,
+ IOptions platformSettings,
+ ILogger logger)
+ {
+ _httpContextAccessor = httpContextAccessor;
+ _httpClient = client;
+ _logger = logger;
+
+ if (!_httpClient.DefaultRequestHeaders.Contains(ForwardedForHeaderName))
+ {
+ string clientIpAddress = _httpContextAccessor?.HttpContext?.Request?.Headers?[ForwardedForHeaderName];
+ _httpClient.DefaultRequestHeaders.Add(ForwardedForHeaderName, clientIpAddress);
+ }
+
+ client.BaseAddress = new Uri($"{platformSettings.Value.ApiAuthorizationEndpoint}");
+ client.DefaultRequestHeaders.Add(SubscriptionKeyHeaderName, platformSettings.Value.SubscriptionKey);
+ client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
+ }
+
+ ///
+ /// Method for performing authorization.
+ ///
+ /// An authorization request.
+ /// The result of the authorization request.
+ public async Task AuthorizeRequest(XacmlJsonRequestRoot xacmlJsonRequest)
+ {
+ XacmlJsonResponse xacmlJsonResponse = null;
+ string apiUrl = $"decision";
+ string requestJson = JsonSerializer.Serialize(xacmlJsonRequest);
+ StringContent httpContent = new StringContent(requestJson, Encoding.UTF8, "application/json");
+
+ Stopwatch stopWatch = new Stopwatch();
+ stopWatch.Start();
+ HttpResponseMessage response = await _httpClient.PostAsync(apiUrl, httpContent);
+ stopWatch.Stop();
+ TimeSpan ts = stopWatch.Elapsed;
+ _logger.LogInformation("Authorization PDP time elapsed: " + ts.TotalMilliseconds);
+
+ if (response.StatusCode == HttpStatusCode.OK)
+ {
+ string responseData = await response.Content.ReadAsStringAsync();
+ xacmlJsonResponse = JsonSerializer.Deserialize(responseData);
+ }
+ else
+ {
+ _logger.LogInformation($"// PDPAppSI // GetDecisionForRequest // Non-zero status code: {response.StatusCode}");
+ _logger.LogInformation($"// PDPAppSI // GetDecisionForRequest // Response: {await response.Content.ReadAsStringAsync()}");
+ }
+
+ return xacmlJsonResponse;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Configuration/PepSettings.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Configuration/PepSettings.cs
new file mode 100644
index 00000000..49946439
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Configuration/PepSettings.cs
@@ -0,0 +1,13 @@
+namespace Altinn.Common.PEP.Configuration
+{
+ ///
+ /// General configuration settings
+ ///
+ public class PepSettings
+ {
+ ///
+ /// The timout on pdp decions
+ ///
+ public int PdpDecisionCachingTimeout { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Configuration/PlatformSettings.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Configuration/PlatformSettings.cs
new file mode 100644
index 00000000..353e6a05
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Configuration/PlatformSettings.cs
@@ -0,0 +1,18 @@
+namespace Altinn.Common.PEP.Configuration
+{
+ ///
+ /// Configuratin for platform settings
+ ///
+ public class PlatformSettings
+ {
+ ///
+ /// Gets or sets the url for the API Authorization endpoint
+ ///
+ public string ApiAuthorizationEndpoint { get; set; }
+
+ ///
+ /// Gets or sets the subscription key value to use in requests against the platform.
+ ///
+ public string SubscriptionKey { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Constants/AltinnObligations.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Constants/AltinnObligations.cs
new file mode 100644
index 00000000..cf81e589
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Constants/AltinnObligations.cs
@@ -0,0 +1,13 @@
+namespace Altinn.Common.PEP.Constants
+{
+ ///
+ /// Represents a set of Obligation values
+ ///
+ public static class AltinnObligations
+ {
+ ///
+ /// Get the name of the obligation authentication level.
+ ///
+ public const string RequiredAuthenticationLevel = "RequiredAuthenticationLevel";
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Constants/AltinnXacmlUrns.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Constants/AltinnXacmlUrns.cs
new file mode 100644
index 00000000..d682a18a
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Constants/AltinnXacmlUrns.cs
@@ -0,0 +1,108 @@
+namespace Altinn.Common.PEP.Constants
+{
+ ///
+ /// Represents a collection of URN values for different Altinn specific XACML attributes.
+ ///
+ public static class AltinnXacmlUrns
+ {
+ ///
+ /// Get the URN value for party id.
+ ///
+ public const string PartyId = "urn:altinn:partyid";
+
+ ///
+ /// Get the URN value for
+ ///
+ public const string Ssn = "urn:altinn:ssn";
+
+ ///
+ /// Get the URN value for organization number. This is the legacy version
+ ///
+ public const string OrganizationNumber = "urn:altinn:organizationnumber";
+
+ ///
+ /// xacml string that represents organization number
+ ///
+ public const string OrganizationNumberAttribute = "urn:altinn:organization:identifier-no";
+
+ ///
+ /// Get the URN value for instance id
+ ///
+ public const string InstanceId = "urn:altinn:instance-id";
+
+ ///
+ /// Get the URN value for org (application owner)
+ ///
+ public const string OrgId = "urn:altinn:org";
+
+ ///
+ /// Get the URN value for app id
+ ///
+ public const string AppId = "urn:altinn:app";
+
+ ///
+ /// Get the URN value for app resource
+ ///
+ public const string AppResource = "urn:altinn:appresource";
+
+ ///
+ /// Get the value for for event id
+ ///
+ public const string EventId = "urn:altinn:event-id";
+
+ ///
+ /// Get the value task id
+ ///
+ public const string TaskId = "urn:altinn:task";
+
+ ///
+ /// Get the value resourceId
+ ///
+ public const string ResourceId = "urn:altinn:resource";
+
+ ///
+ /// Get the value Resource Instance
+ ///
+ public const string ResourceInstance = "urn:altinn:resourceinstance";
+
+ ///
+ /// Get the value eventType
+ ///
+ public const string EventType = "urn:altinn:eventtype";
+
+ ///
+ /// Get the value EventSource
+ ///
+ public const string EventSource = "urn:altinn:eventsource";
+
+ ///
+ /// Get the value scope
+ ///
+ public const string Scope = "urn:scope";
+
+ ///
+ /// Get the value sessionid
+ ///
+ public const string SessionId = "urn:altinn:sessionid";
+
+ ///
+ /// SystemUserUuid urn
+ ///
+ public const string SystemUserUuid = "urn:altinn:systemuser:uuid";
+
+ ///
+ /// xacml string that represents user
+ ///
+ public const string UserAttribute = "urn:altinn:userid";
+
+ ///
+ /// xacml string that represents person universally unique identifier
+ ///
+ public const string PersonUuidAttribute = "urn:altinn:person:uuid";
+
+ ///
+ /// xacml string that represents party
+ ///
+ public const string PartyAttribute = "urn:altinn:partyid";
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Extensions/AuthorizationBuilderExtensions.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Extensions/AuthorizationBuilderExtensions.cs
new file mode 100644
index 00000000..15b32e9c
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Extensions/AuthorizationBuilderExtensions.cs
@@ -0,0 +1,60 @@
+using Altinn.Common.PEP.Authorization;
+using Microsoft.AspNetCore.Authorization;
+
+namespace Altinn.Authorization.PEP.Extensions;
+
+///
+/// Provides extension methods for configuring Altinn-specific authorization policies.
+///
+public static class AuthorizationBuilderExtensions
+{
+ ///
+ /// Adds a claim-based access policy to the authorization builder.
+ ///
+ /// The to which the policy will be added.
+ /// The name of the policy.
+ /// The claim type required by the policy.
+ /// The claim value required by the policy.
+ /// Thrown if , , or is null or empty.
+ /// The updated .
+ public static AuthorizationBuilder AddAltinnPEPClaimAccessPolicy(this AuthorizationBuilder builder, string name, string type, string value)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(name, nameof(name));
+ ArgumentException.ThrowIfNullOrEmpty(type, nameof(type));
+ ArgumentException.ThrowIfNullOrEmpty(value, nameof(value));
+ return builder.AddPolicy(name, policy => policy.Requirements.Add(new ClaimAccessRequirement(type, value)));
+ }
+
+ ///
+ /// Adds a resource-based access policy to the authorization builder.
+ ///
+ /// The to which the policy will be added.
+ /// The name of the policy.
+ /// The identifier of the resource to protect.
+ /// The type of action permitted by the policy.
+ /// Thrown if , , or is null or empty.
+ /// The updated .
+ public static AuthorizationBuilder AddAltinnPEPResourceAccessPolicy(this AuthorizationBuilder builder, string name, string resourceId, string actionType)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(name, nameof(name));
+ ArgumentException.ThrowIfNullOrEmpty(resourceId, nameof(resourceId));
+ ArgumentException.ThrowIfNullOrEmpty(actionType, nameof(actionType));
+ return builder.AddPolicy(name, policy => policy.Requirements.Add(new ResourceAccessRequirement(resourceId, actionType)));
+ }
+
+ ///
+ /// Adds a scope-based access policy to the authorization builder.
+ ///
+ /// The to which the policy will be added.
+ /// The name of the policy.
+ /// An array of scopes required by the policy.
+ /// Thrown if is null or empty or if contains null or empty values.
+ /// Thrown if is null.
+ /// The updated .
+ public static AuthorizationBuilder AddAltinnPEPScopePolicy(this AuthorizationBuilder builder, string name, params string[] scopes)
+ {
+ ArgumentException.ThrowIfNullOrEmpty(name, nameof(name));
+ ArgumentNullException.ThrowIfNull(scopes, nameof(scopes));
+ return builder.AddPolicy(name, policy => policy.Requirements.Add(new ScopeAccessRequirement(scopes)));
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Extensions/ServiceCollectionExtensions.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Extensions/ServiceCollectionExtensions.cs
new file mode 100644
index 00000000..50642eba
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Extensions/ServiceCollectionExtensions.cs
@@ -0,0 +1,26 @@
+using Altinn.Common.PEP.Authorization;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+
+namespace Altinn.Authorization.PEP.Extensions;
+
+///
+/// Provides extension methods for registering Altinn-specific authorization services.
+/// Must be called atleast once if registering using extensions methods from .
+///
+public static class ServiceCollectionExtensions
+{
+ ///
+ /// Adds authorization handlers required for Altinn PEP (Policy Enforcement Point).
+ /// Registers handlers for claim-based, resource-based, and scope-based access control.
+ /// , and
+ ///
+ public static IServiceCollection AddAltinnPEP(this IServiceCollection services)
+ {
+ services.TryAddScoped();
+ services.TryAddScoped();
+ services.TryAddScoped();
+ return services;
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Helpers/DecisionHelper.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Helpers/DecisionHelper.cs
new file mode 100644
index 00000000..37a578c4
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Helpers/DecisionHelper.cs
@@ -0,0 +1,612 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+using System.Text.Json;
+using System.Text.RegularExpressions;
+using Altinn.AccessManagement.Core.Models;
+using Altinn.Authorization.ABAC.Xacml;
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.PEP.Authorization;
+using Altinn.Common.PEP.Constants;
+using Altinn.Common.PEP.Models;
+using Altinn.Common.PEP.Utils;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Routing;
+
+using static Altinn.Authorization.ABAC.Constants.XacmlConstants;
+
+namespace Altinn.Common.PEP.Helpers
+{
+ ///
+ /// Represents a collection of helper methods for creating a decision request
+ ///
+ public static class DecisionHelper
+ {
+ private const string ParamInstanceOwnerPartyId = "instanceOwnerPartyId";
+ private const string ParamInstanceGuid = "instanceGuid";
+ private const string ParamApp = "app";
+ private const string ParamOrg = "org";
+ private const string ParamAppId = "appId";
+ private const string ParamParty = "party";
+ private const string DefaultIssuer = "Altinn";
+ private const string DefaultType = "string";
+ private const string PersonHeaderTrigger = "person";
+ private const string OrganizationHeaderTrigger = "organization";
+ private const string PersonHeader = "Altinn-Party-SocialSecurityNumber";
+ private const string OrganizationNumberHeader = "Altinn-Party-OrganizationNumber";
+ private const string PolicyObligationMinAuthnLevel = "urn:altinn:minimum-authenticationlevel";
+ private const string PolicyObligationMinAuthnLevelOrg = "urn:altinn:minimum-authenticationlevel-org";
+
+ ///
+ /// Create decision request based for policy decision point.
+ ///
+ /// Unique identifier of the organisation responsible for the app.
+ /// Application identifier which is unique within an organisation.
+ /// Claims principal user.
+ /// Policy action type i.e. read, write, delete, instantiate.
+ /// Unique id of the party that is the owner of the instance.
+ /// Unique id to identify the instance.
+ /// The taskid. Will override contexthandler if present
+ /// The decision request.
+ public static XacmlJsonRequestRoot CreateDecisionRequest(string org, string app, ClaimsPrincipal user, string actionType, int instanceOwnerPartyId, Guid? instanceGuid, string taskid = null)
+ {
+ XacmlJsonRequest request = new XacmlJsonRequest();
+ request.AccessSubject = new List();
+ request.Action = new List();
+ request.Resource = new List();
+
+ request.AccessSubject.Add(CreateSubjectCategory(user.Claims));
+ request.Action.Add(CreateActionCategory(actionType));
+ request.Resource.Add(CreateResourceCategory(org, app, instanceOwnerPartyId.ToString(), instanceGuid.ToString(), taskid));
+
+ XacmlJsonRequestRoot jsonRequest = new XacmlJsonRequestRoot() { Request = request };
+
+ return jsonRequest;
+ }
+
+ ///
+ /// Create decision request based for policy decision point.
+ ///
+ /// Unique identifier of the organisation responsible for the app.
+ /// Application identifier which is unique within an organisation.
+ /// Claims principal user.
+ /// Policy action type i.e. read, write, delete, instantiate.
+ /// The decision request.
+ public static XacmlJsonRequestRoot CreateDecisionRequest(string org, string app, ClaimsPrincipal user, string actionType)
+ {
+ XacmlJsonRequest request = new XacmlJsonRequest();
+ request.AccessSubject = new List();
+ request.Action = new List();
+ request.Resource = new List();
+
+ request.AccessSubject.Add(CreateSubjectCategory(user.Claims));
+ request.Action.Add(CreateActionCategory(actionType));
+ request.Resource.Add(CreateResourceCategory(org, app, null, null, null));
+
+ XacmlJsonRequestRoot jsonRequest = new XacmlJsonRequestRoot() { Request = request };
+
+ return jsonRequest;
+ }
+
+ ///
+ /// Create a new to represent a decision request.
+ ///
+ /// The current
+ /// The access requirements
+ /// The route data from a request.
+ /// A decision request
+ public static XacmlJsonRequestRoot CreateDecisionRequest(AuthorizationHandlerContext context, AppAccessRequirement requirement, RouteData routeData)
+ {
+ XacmlJsonRequest request = new XacmlJsonRequest();
+ request.AccessSubject = new List();
+ request.Action = new List();
+ request.Resource = new List();
+
+ string instanceGuid = routeData.Values[ParamInstanceGuid] as string;
+ string app = routeData.Values[ParamApp] as string;
+ string org = routeData.Values[ParamOrg] as string;
+ string instanceOwnerPartyId = routeData.Values[ParamInstanceOwnerPartyId] as string;
+
+ if (string.IsNullOrWhiteSpace(app) && string.IsNullOrWhiteSpace(org))
+ {
+ string appId = routeData.Values[ParamAppId] as string;
+ if (appId != null)
+ {
+ org = appId.Split("/")[0];
+ app = appId.Split("/")[1];
+ }
+ }
+
+ request.AccessSubject.Add(CreateSubjectCategory(context.User.Claims));
+ request.Action.Add(CreateActionCategory(requirement.ActionType));
+ request.Resource.Add(CreateResourceCategory(org, app, instanceOwnerPartyId, instanceGuid, null));
+
+ XacmlJsonRequestRoot jsonRequest = new XacmlJsonRequestRoot() { Request = request };
+
+ return jsonRequest;
+ }
+
+ ///
+ /// Creates a decision request based on input
+ ///
+ ///
+ public static XacmlJsonRequestRoot CreateDecisionRequest(AuthorizationHandlerContext context, ResourceAccessRequirement requirement, RouteData routeData, IHeaderDictionary headers)
+ {
+ XacmlJsonRequest request = new XacmlJsonRequest();
+ request.AccessSubject = new List();
+ request.Action = new List();
+ request.Resource = new List();
+
+ string party = routeData.Values[ParamParty] as string;
+
+ request.AccessSubject.Add(CreateSubjectCategory(context.User.Claims));
+ request.Action.Add(CreateActionCategory(requirement.ActionType));
+
+ int? partyIid = TryParsePartyId(party);
+ if (partyIid.HasValue)
+ {
+ request.Resource.Add(CreateResourceCategoryForResource(requirement.ResourceId, partyIid, null, null));
+ }
+ else if (party.Equals(OrganizationHeaderTrigger) && headers.ContainsKey(OrganizationNumberHeader) && IDFormatDeterminator.IsValidOrganizationNumber(headers[OrganizationNumberHeader]))
+ {
+ request.Resource.Add(CreateResourceCategoryForResource(requirement.ResourceId, null, headers[OrganizationNumberHeader], null));
+ }
+ else if (party.Equals(PersonHeaderTrigger) && headers.ContainsKey(PersonHeader) && IDFormatDeterminator.IsValidSSN(headers[PersonHeader]))
+ {
+ request.Resource.Add(CreateResourceCategoryForResource(requirement.ResourceId, null, null, headers[PersonHeader]));
+ }
+ else
+ {
+ throw new ArgumentException("invalid party " + party);
+ }
+
+ XacmlJsonRequestRoot jsonRequest = new XacmlJsonRequestRoot() { Request = request };
+
+ return jsonRequest;
+ }
+
+ ///
+ /// Create a new with a list of subject attributes based on the given claims.
+ ///
+ /// The list of claims
+ /// A populated subject category
+ public static XacmlJsonCategory CreateSubjectCategory(IEnumerable claims)
+ {
+ XacmlJsonCategory subjectAttributes = new XacmlJsonCategory();
+ subjectAttributes.Attribute = CreateSubjectAttributes(claims);
+
+ return subjectAttributes;
+ }
+
+ ///
+ /// Create a new attribute of type Action with the given action type
+ ///
+ /// The action type
+ /// A value indicating whether the value should be included in the result.
+ /// The created category
+ public static XacmlJsonCategory CreateActionCategory(string actionType, bool includeResult = false)
+ {
+ XacmlJsonCategory actionAttributes = new XacmlJsonCategory();
+ actionAttributes.Attribute = new List();
+ actionAttributes.Attribute.Add(CreateXacmlJsonAttribute(MatchAttributeIdentifiers.ActionId, actionType, DefaultType, DefaultIssuer, includeResult));
+ return actionAttributes;
+ }
+
+ private static List CreateSubjectAttributes(IEnumerable claims)
+ {
+ List attributes = new List();
+
+ XacmlJsonAttribute userIdAttribute = null;
+ XacmlJsonAttribute personUuidAttribute = null;
+ XacmlJsonAttribute partyIdAttribute = null;
+ XacmlJsonAttribute resourceIdAttribute = null;
+ XacmlJsonAttribute legacyOrganizationNumberAttibute = null;
+ XacmlJsonAttribute organizationNumberAttribute = null;
+ XacmlJsonAttribute systemUserAttribute = null;
+
+ // Mapping all claims on user to attributes
+ foreach (Claim claim in claims)
+ {
+ if (IsCamelCaseOrgnumberClaim(claim.Type))
+ {
+ // Set by Altinn authentication this format
+ legacyOrganizationNumberAttibute = CreateXacmlJsonAttribute(AltinnXacmlUrns.OrganizationNumber, claim.Value, DefaultType, claim.Issuer);
+ organizationNumberAttribute = CreateXacmlJsonAttribute(AltinnXacmlUrns.OrganizationNumberAttribute, claim.Value, DefaultType, claim.Issuer);
+ }
+ else if (IsScopeClaim(claim.Type))
+ {
+ attributes.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.Scope, claim.Value, DefaultType, claim.Issuer));
+ }
+ else if (IsJtiClaim(claim.Type))
+ {
+ attributes.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.SessionId, claim.Value, DefaultType, claim.Issuer));
+ }
+ else if (IsSystemUserClaim(claim, out SystemUserClaim userClaim))
+ {
+ systemUserAttribute = CreateXacmlJsonAttribute(AltinnXacmlUrns.SystemUserUuid, userClaim.Systemuser_id[0], DefaultType, claim.Issuer);
+ }
+ else if (IsUserIdClaim(claim.Type))
+ {
+ userIdAttribute = CreateXacmlJsonAttribute(AltinnXacmlUrns.UserAttribute, claim.Value, DefaultType, claim.Issuer);
+ }
+ else if (IsPersonUuidClaim(claim.Type))
+ {
+ personUuidAttribute = CreateXacmlJsonAttribute(AltinnXacmlUrns.PersonUuidAttribute, claim.Value, DefaultType, claim.Issuer);
+ }
+ else if (IsPartyIdClaim(claim.Type))
+ {
+ partyIdAttribute = CreateXacmlJsonAttribute(AltinnXacmlUrns.PartyAttribute, claim.Value, DefaultType, claim.Issuer);
+ }
+ else if (IsResourceClaim(claim.Type))
+ {
+ partyIdAttribute = CreateXacmlJsonAttribute(AltinnXacmlUrns.ResourceId, claim.Value, DefaultType, claim.Issuer);
+ }
+ else if (IsOrganizationNumberAttributeClaim(claim.Type))
+ {
+ // If claimlist contains new format of orgnumber reset any old. To ensure there is not a mismatch
+ organizationNumberAttribute = CreateXacmlJsonAttribute(AltinnXacmlUrns.OrganizationNumberAttribute, claim.Value, DefaultType, claim.Issuer);
+ legacyOrganizationNumberAttibute = null;
+ }
+ else if (IsValidUrn(claim.Type))
+ {
+ attributes.Add(CreateXacmlJsonAttribute(claim.Type, claim.Value, DefaultType, claim.Issuer));
+ }
+ }
+
+ // Adding only one of the subject attributes to make sure we dont have mismatching duplicates for PDP request that potentially could cause issues
+ if (personUuidAttribute != null)
+ {
+ attributes.Add(personUuidAttribute);
+ }
+ else if (userIdAttribute != null)
+ {
+ attributes.Add(userIdAttribute);
+ }
+ else if (partyIdAttribute != null)
+ {
+ attributes.Add(partyIdAttribute);
+ }
+ else if (resourceIdAttribute != null)
+ {
+ attributes.Add(resourceIdAttribute);
+ }
+ else if (systemUserAttribute != null)
+ {
+ // If we have a system user we only add that. No other attributes allowed by PDP
+ attributes.Clear();
+ attributes.Add(systemUserAttribute);
+ }
+ else if (legacyOrganizationNumberAttibute != null)
+ {
+ // For legeacy we set both
+ attributes.Add(legacyOrganizationNumberAttibute);
+ attributes.Add(organizationNumberAttribute);
+ }
+ else if (organizationNumberAttribute != null)
+ {
+ attributes.Add(organizationNumberAttribute);
+ }
+
+ return attributes;
+ }
+
+ private static XacmlJsonCategory CreateResourceCategory(string org, string app, string instanceOwnerPartyId, string instanceGuid, string task, bool includeResult = false)
+ {
+ XacmlJsonCategory resourceCategory = new XacmlJsonCategory();
+ resourceCategory.Attribute = new List();
+
+ if (!string.IsNullOrWhiteSpace(instanceOwnerPartyId))
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.PartyId, instanceOwnerPartyId, DefaultType, DefaultIssuer, includeResult));
+ }
+
+ if (!string.IsNullOrWhiteSpace(instanceGuid) && !string.IsNullOrWhiteSpace(instanceOwnerPartyId))
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.InstanceId, instanceOwnerPartyId + "/" + instanceGuid, DefaultType, DefaultIssuer, includeResult));
+ }
+
+ if (!string.IsNullOrWhiteSpace(org))
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.OrgId, org, DefaultType, DefaultIssuer));
+ }
+
+ if (!string.IsNullOrWhiteSpace(app))
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.AppId, app, DefaultType, DefaultIssuer));
+ }
+
+ if (!string.IsNullOrWhiteSpace(task))
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.TaskId, task, DefaultType, DefaultIssuer));
+ }
+
+ return resourceCategory;
+ }
+
+ private static XacmlJsonCategory CreateResourceCategoryForResource(string resourceid, int? partyId, string organizationnumber, string ssn, bool includeResult = false)
+ {
+ XacmlJsonCategory resourceCategory = new XacmlJsonCategory();
+ resourceCategory.Attribute = new List();
+
+ if (partyId.HasValue)
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.PartyId, partyId.Value.ToString(), DefaultType, DefaultIssuer, includeResult));
+ }
+ else if (!string.IsNullOrEmpty(organizationnumber))
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.OrganizationNumber, organizationnumber, DefaultType, DefaultIssuer, includeResult));
+ }
+ else if (!string.IsNullOrEmpty(ssn))
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.Ssn, ssn, DefaultType, DefaultIssuer, includeResult));
+ }
+
+ if (!string.IsNullOrWhiteSpace(resourceid))
+ {
+ resourceCategory.Attribute.Add(CreateXacmlJsonAttribute(AltinnXacmlUrns.ResourceId, resourceid, DefaultType, DefaultIssuer));
+ }
+
+ return resourceCategory;
+ }
+
+ ///
+ /// Create a new with the given values.
+ ///
+ /// The attribute id
+ /// The attribute value
+ /// The datatype for the attribute value
+ /// The issuer
+ /// A value indicating whether the value should be included in the result.
+ /// A new created attribute
+ public static XacmlJsonAttribute CreateXacmlJsonAttribute(string attributeId, string value, string dataType, string issuer, bool includeResult = false)
+ {
+ XacmlJsonAttribute xacmlJsonAttribute = new XacmlJsonAttribute();
+
+ xacmlJsonAttribute.AttributeId = attributeId;
+ xacmlJsonAttribute.Value = value;
+ xacmlJsonAttribute.DataType = dataType;
+ xacmlJsonAttribute.Issuer = issuer;
+ xacmlJsonAttribute.IncludeInResult = includeResult;
+
+ return xacmlJsonAttribute;
+ }
+
+ private static bool IsValidUrn(string value)
+ {
+ return value.StartsWith("urn:", StringComparison.Ordinal);
+ }
+
+ private static bool IsCamelCaseOrgnumberClaim(string name)
+ {
+ return name.Equals("urn:altinn:orgNumber");
+ }
+
+ private static bool IsScopeClaim(string name)
+ {
+ return name.Equals("scope");
+ }
+
+ private static bool IsUserIdClaim(string name)
+ {
+ return name.Equals(AltinnXacmlUrns.UserAttribute);
+ }
+
+ private static bool IsPersonUuidClaim(string name)
+ {
+ return name.Equals(AltinnXacmlUrns.PersonUuidAttribute);
+ }
+
+ private static bool IsPartyIdClaim(string name)
+ {
+ return name.Equals(AltinnXacmlUrns.PartyAttribute);
+ }
+
+ private static bool IsResourceClaim(string name)
+ {
+ return name.Equals(AltinnXacmlUrns.ResourceId);
+ }
+
+ private static bool IsOrganizationNumberAttributeClaim(string name)
+ {
+ // The new format of orgnumber
+ return name.Equals(AltinnXacmlUrns.OrganizationNumberAttribute);
+ }
+
+ private static bool IsJtiClaim(string name)
+ {
+ return name.Equals("jti");
+ }
+
+ private static bool IsSystemUserClaim(Claim claim, out SystemUserClaim userClaim)
+ {
+ if (claim.Type.Equals("authorization_details"))
+ {
+ userClaim = JsonSerializer.Deserialize(claim.Value);
+ if (userClaim?.Systemuser_id != null && userClaim.Systemuser_id.Count > 0)
+ {
+ return true;
+ }
+
+ return false;
+ }
+ else
+ {
+ userClaim = null;
+ return false;
+ }
+ }
+
+ ///
+ /// Validate the response from PDP
+ ///
+ /// The response to validate
+ /// The
+ /// true or false, valid or not
+ public static bool ValidatePdpDecision(List results, ClaimsPrincipal user)
+ {
+ ArgumentNullException.ThrowIfNull(results, nameof(results));
+ ArgumentNullException.ThrowIfNull(user, nameof(user));
+
+ // We request one thing and then only want one result
+ if (results.Count != 1)
+ {
+ return false;
+ }
+
+ return ValidateDecisionResult(results.First(), user);
+ }
+
+ ///
+ /// Validate the response from PDP
+ ///
+ /// The response to validate
+ /// The
+ /// The result of the validation
+ public static EnforcementResult ValidatePdpDecisionDetailed(List results, ClaimsPrincipal user)
+ {
+ ArgumentNullException.ThrowIfNull(results, nameof(results));
+ ArgumentNullException.ThrowIfNull(user, nameof(user));
+
+ // We request one thing and then only want one result
+ if (results.Count != 1)
+ {
+ return new EnforcementResult() { Authorized = false };
+ }
+
+ return ValidateDecisionResultDetailed(results.First(), user);
+ }
+
+ ///
+ /// Validate the response from PDP
+ ///
+ /// The response to validate
+ /// The
+ /// true or false, valid or not
+ public static bool ValidateDecisionResult(XacmlJsonResult result, ClaimsPrincipal user)
+ {
+ // Checks that the result is nothing else than "permit"
+ if (!result.Decision.Equals(XacmlContextDecision.Permit.ToString()))
+ {
+ return false;
+ }
+
+ // Checks if the result contains obligation
+ if (result.Obligations != null)
+ {
+ List obligationList = result.Obligations;
+ XacmlJsonAttributeAssignment attributeMinLvAuth = GetObligation(PolicyObligationMinAuthnLevel, obligationList);
+
+ // Checks if the obligation contains a minimum authentication level attribute
+ if (attributeMinLvAuth != null)
+ {
+ string minAuthenticationLevel = attributeMinLvAuth.Value;
+ string usersAuthenticationLevel = user.Claims.FirstOrDefault(c => c.Type.Equals("urn:altinn:authlevel")).Value;
+
+ // Checks that the user meets the minimum authentication level
+ if (Convert.ToInt32(usersAuthenticationLevel) < Convert.ToInt32(minAuthenticationLevel))
+ {
+ if (user.Claims.FirstOrDefault(c => c.Type.Equals("urn:altinn:org")) != null)
+ {
+ XacmlJsonAttributeAssignment attributeMinLvAuthOrg = GetObligation(PolicyObligationMinAuthnLevelOrg, obligationList);
+ if (attributeMinLvAuthOrg != null)
+ {
+ if (Convert.ToInt32(usersAuthenticationLevel) >= Convert.ToInt32(attributeMinLvAuthOrg.Value))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ /// Validate the response from PDP
+ ///
+ /// The response to validate
+ /// The
+ /// The result of the validation
+ public static EnforcementResult ValidateDecisionResultDetailed(XacmlJsonResult result, ClaimsPrincipal user)
+ {
+ // Checks that the result is nothing else than "permit"
+ if (!result.Decision.Equals(XacmlContextDecision.Permit.ToString()))
+ {
+ return new EnforcementResult() { Authorized = false };
+ }
+
+ // Checks if the result contains obligation
+ if (result.Obligations != null)
+ {
+ List obligationList = result.Obligations;
+ XacmlJsonAttributeAssignment attributeMinLvAuth = GetObligation(PolicyObligationMinAuthnLevel, obligationList);
+
+ // Checks if the obligation contains a minimum authentication level attribute
+ if (attributeMinLvAuth != null)
+ {
+ string minAuthenticationLevel = attributeMinLvAuth.Value;
+ string usersAuthenticationLevel = user.Claims.FirstOrDefault(c => c.Type.Equals("urn:altinn:authlevel")).Value;
+
+ // Checks that the user meets the minimum authentication level
+ if (Convert.ToInt32(usersAuthenticationLevel) < Convert.ToInt32(minAuthenticationLevel))
+ {
+ if (user.Claims.FirstOrDefault(c => c.Type.Equals("urn:altinn:org")) != null)
+ {
+ XacmlJsonAttributeAssignment attributeMinLvAuthOrg = GetObligation(PolicyObligationMinAuthnLevelOrg, obligationList);
+ if (attributeMinLvAuthOrg != null)
+ {
+ if (Convert.ToInt32(usersAuthenticationLevel) >= Convert.ToInt32(attributeMinLvAuthOrg.Value))
+ {
+ return new EnforcementResult() { Authorized = true };
+ }
+
+ minAuthenticationLevel = attributeMinLvAuthOrg.Value;
+ }
+ }
+
+ return new EnforcementResult()
+ {
+ Authorized = false,
+ FailedObligations = new Dictionary()
+ {
+ { AltinnObligations.RequiredAuthenticationLevel, minAuthenticationLevel }
+ }
+ };
+ }
+ }
+ }
+
+ return new EnforcementResult() { Authorized = true };
+ }
+
+ private static XacmlJsonAttributeAssignment GetObligation(string category, List obligations)
+ {
+ foreach (XacmlJsonObligationOrAdvice obligation in obligations)
+ {
+ XacmlJsonAttributeAssignment assignment = obligation.AttributeAssignment.FirstOrDefault(a => a.Category.Equals(category));
+ if (assignment != null)
+ {
+ return assignment;
+ }
+ }
+
+ return null;
+ }
+
+ private static int? TryParsePartyId(string party)
+ {
+ if (int.TryParse(party, out var partyId))
+ {
+ return partyId;
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Implementation/PDPAppSI.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Implementation/PDPAppSI.cs
new file mode 100644
index 00000000..feeb3e1a
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Implementation/PDPAppSI.cs
@@ -0,0 +1,62 @@
+using System.Security.Claims;
+using System.Text.Json;
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.PEP.Clients;
+using Altinn.Common.PEP.Helpers;
+using Altinn.Common.PEP.Interfaces;
+using Microsoft.Extensions.Logging;
+
+namespace Altinn.Common.PEP.Implementation
+{
+ ///
+ /// App implementation of the authorization service where the app uses the Altinn platform api.
+ ///
+ public class PDPAppSI : IPDP
+ {
+ private readonly ILogger _logger;
+ private readonly AuthorizationApiClient _authorizationApiClient;
+
+ ///
+ /// Initializes a new instance of the class
+ ///
+ /// the handler for logger service
+ /// A typed Http client accessor
+ public PDPAppSI(ILogger logger, AuthorizationApiClient authorizationApiClient)
+ {
+ _logger = logger;
+ _authorizationApiClient = authorizationApiClient;
+ }
+
+ ///
+ public async Task GetDecisionForRequest(XacmlJsonRequestRoot xacmlJsonRequest)
+ {
+ XacmlJsonResponse xacmlJsonResponse = null;
+
+ try
+ {
+ xacmlJsonResponse = await _authorizationApiClient.AuthorizeRequest(xacmlJsonRequest);
+ }
+ catch (Exception e)
+ {
+ _logger.LogError("Unable to retrieve Xacml Json response. An error occured {message}", e.Message);
+ }
+
+ return xacmlJsonResponse;
+ }
+
+ ///
+ public async Task GetDecisionForUnvalidateRequest(XacmlJsonRequestRoot xacmlJsonRequest, ClaimsPrincipal user)
+ {
+ XacmlJsonResponse response = await GetDecisionForRequest(xacmlJsonRequest);
+
+ if (response?.Response == null)
+ {
+ throw new ArgumentNullException("response");
+ }
+
+ _logger.LogInformation("// Altinn PEP // PDPAppSI // Request sent to platform authorization: {xacmlJsonRequest}", JsonSerializer.Serialize(xacmlJsonRequest));
+
+ return DecisionHelper.ValidatePdpDecision(response.Response, user);
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Interfaces/IPDP.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Interfaces/IPDP.cs
new file mode 100644
index 00000000..b633b567
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Interfaces/IPDP.cs
@@ -0,0 +1,28 @@
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+
+namespace Altinn.Common.PEP.Interfaces
+{
+ ///
+ /// This interface describes the minimum set of methods for any implementation of a Policy Decision Point.
+ ///
+ public interface IPDP
+ {
+ ///
+ /// Sends in a request and get response with result of the request
+ ///
+ /// The Xacml Json Request
+ /// The Xacml Json response contains the result of the request
+ Task GetDecisionForRequest(XacmlJsonRequestRoot xacmlJsonRequest);
+
+ ///
+ /// Change this to a better one???????
+ ///
+ /// The Xacml Json Request
+ /// The claims principal
+ /// Returns true if request is permitted and false if not
+ Task GetDecisionForUnvalidateRequest(XacmlJsonRequestRoot xacmlJsonRequest, ClaimsPrincipal user);
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/EnforcementResult.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/EnforcementResult.cs
new file mode 100644
index 00000000..9b3bec68
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/EnforcementResult.cs
@@ -0,0 +1,20 @@
+using System.Collections.Generic;
+
+namespace Altinn.Common.PEP.Models
+{
+ ///
+ /// Represents the result of an authorization enforcement
+ ///
+ public class EnforcementResult
+ {
+ ///
+ /// Value indicating whether enforcement result shows authorized or not
+ ///
+ public bool Authorized { get; set; }
+
+ ///
+ /// Collection of obligations that did not pass validation
+ ///
+ public Dictionary FailedObligations { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/IDFormat.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/IDFormat.cs
new file mode 100644
index 00000000..1582e7a7
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/IDFormat.cs
@@ -0,0 +1,32 @@
+namespace Altinn.Common.PEP.Models
+{
+ ///
+ /// IDFormat is used to communicate the format of a suspected string of either OrgNr or SSN -type.
+ ///
+ ///
+ /// Author: Ole Hansen
+ /// Date: 29/04/2009
+ ///
+ public enum IDFormat : int
+ {
+ ///
+ /// IDFormat is unknown
+ ///
+ Unknown = 0,
+
+ ///
+ /// IDFormat is SSN
+ ///
+ SSN = 1,
+
+ ///
+ /// IDFormat is OrgNr
+ ///
+ OrgNr = 2,
+
+ ///
+ /// IDFormat is Self Identified User
+ ///
+ UserName = 3,
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/OrgClaim.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/OrgClaim.cs
new file mode 100644
index 00000000..08bf7142
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/OrgClaim.cs
@@ -0,0 +1,22 @@
+using System.Text.Json.Serialization;
+
+namespace Altinn.AccessManagement.Core.Models
+{
+ ///
+ /// Organization claim matching structure from maskinporten
+ ///
+ public class OrgClaim
+ {
+ ///
+ /// The authority that defines organization numbers.
+ ///
+ [JsonPropertyName("authority")]
+ public string Authority => "iso6523-actorid-upis";
+
+ ///
+ /// The orgclaim id
+ ///
+ [JsonPropertyName("ID")]
+ public string ID { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/SystemUserClaim.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/SystemUserClaim.cs
new file mode 100644
index 00000000..11728af5
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Models/SystemUserClaim.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+
+namespace Altinn.AccessManagement.Core.Models
+{
+ ///
+ /// System User claim matching structure from maskinporten
+ ///
+ public class SystemUserClaim
+ {
+ ///
+ /// The type
+ ///
+ [JsonPropertyName("type")]
+ public string Type => "urn:altinn:systemuser";
+
+ ///
+ /// The organization that created the system user
+ ///
+ [JsonPropertyName("systemuser_org")]
+ public OrgClaim Systemuser_org { get; set; }
+
+ ///
+ /// The system user id
+ ///
+ [JsonPropertyName("systemuser_id")]
+ public List Systemuser_id { get; set; }
+
+ ///
+ /// The system id
+ ///
+ [JsonPropertyName("system_id")]
+ public string System_id { get; set; }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Utils/IDFormatDeterminator.cs b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Utils/IDFormatDeterminator.cs
new file mode 100644
index 00000000..db0ed197
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Altinn.Authorization.PEP/Utils/IDFormatDeterminator.cs
@@ -0,0 +1,185 @@
+using System.Text.RegularExpressions;
+using Altinn.Common.PEP.Models;
+
+namespace Altinn.Common.PEP.Utils
+{
+ ///
+ /// This class is used to get the ID-type (if any) from a string input.
+ ///
+ public class IDFormatDeterminator
+ {
+ ///
+ /// Method to get the IDFormat from an arbitrary string.
+ /// Non-numeric strings will yield an IDFormat.Unknown
+ /// E.g. "000 000 000", "010170 00000" or "01.01.70 00000"
+ /// Leading and trailing spaces are trimmed off.
+ ///
+ ///
+ /// The string to test, 9 or 11 consecutive digits (e.g. 000000000 or 00000000000)
+ /// or username of self identified user
+ ///
+ ///
+ /// An IDFormat corresponding to the input
+ ///
+ public static IDFormat DetermineIDFormat(string input)
+ {
+ if (input == null)
+ {
+ return IDFormat.Unknown;
+ }
+
+ input = input.Trim();
+
+ if (IsValidOrganizationNumber(input))
+ {
+ return IDFormat.OrgNr;
+ }
+ else if (IsValidSSN(input))
+ {
+ return IDFormat.SSN;
+ }
+ else if (IsValidUserName(input))
+ {
+ return IDFormat.UserName;
+ }
+ else
+ {
+ return IDFormat.Unknown;
+ }
+ }
+
+ ///
+ /// Validates that a given organization number is valid.
+ ///
+ ///
+ /// Organization number to validate
+ ///
+ ///
+ /// true if valid, false otherwise.
+ ///
+ ///
+ /// Validates length, numeric and modulus 11.
+ ///
+ public static bool IsValidOrganizationNumber(string orgNo)
+ {
+ int[] weight = { 3, 2, 7, 6, 5, 4, 3, 2 };
+
+ // Validation only done for 9 digit numbers
+ if (orgNo.Length == 9)
+ {
+ try
+ {
+ int currentDigit = 0;
+ int sum = 0;
+ for (int i = 0; i < orgNo.Length - 1; i++)
+ {
+ currentDigit = int.Parse(orgNo.Substring(i, 1));
+ sum += currentDigit * weight[i];
+ }
+
+ int ctrlDigit = 11 - (sum % 11);
+ if (ctrlDigit == 11)
+ {
+ ctrlDigit = 0;
+ }
+
+ return int.Parse(orgNo.Substring(orgNo.Length - 1)) == ctrlDigit;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Validates that a given social security number is valid.
+ ///
+ ///
+ /// Social security number to validate
+ ///
+ ///
+ /// true if valid, false otherwise.
+ ///
+ ///
+ /// Validates length, numeric and modulus 11.
+ ///
+ public static bool IsValidSSN(string ssnNo)
+ {
+ int[] weightDigit10 = { 3, 7, 6, 1, 8, 9, 4, 5, 2 };
+ int[] weightDigit11 = { 5, 4, 3, 2, 7, 6, 5, 4, 3, 2 };
+
+ // Validation only done for 11 digit numbers
+ if (ssnNo.Length == 11)
+ {
+ try
+ {
+ int currentDigit = 0;
+ int sumCtrlDigit10 = 0;
+ int sumCtrlDigit11 = 0;
+ int ctrlDigit10 = -1;
+ int ctrlDigit11 = -1;
+
+ // Calculate control digits
+ for (int i = 0; i < 9; i++)
+ {
+ currentDigit = int.Parse(ssnNo.Substring(i, 1));
+ sumCtrlDigit10 += currentDigit * weightDigit10[i];
+ sumCtrlDigit11 += currentDigit * weightDigit11[i];
+ }
+
+ ctrlDigit10 = 11 - (sumCtrlDigit10 % 11);
+ if (ctrlDigit10 == 11)
+ {
+ ctrlDigit10 = 0;
+ }
+
+ sumCtrlDigit11 += ctrlDigit10 * weightDigit11[9];
+ ctrlDigit11 = 11 - (sumCtrlDigit11 % 11);
+ if (ctrlDigit11 == 11)
+ {
+ ctrlDigit11 = 0;
+ }
+
+ // Validate control digits in ssn
+ bool digit10Valid = ctrlDigit10 == int.Parse(ssnNo.Substring(9, 1));
+ bool digit11Valid = ctrlDigit11 == int.Parse(ssnNo.Substring(10, 1));
+ return digit10Valid && digit11Valid;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Validates that a given user name is valid.
+ ///
+ ///
+ /// User name to validate
+ ///
+ ///
+ /// true if valid, false otherwise.
+ ///
+ ///
+ /// Validates username with a regular expression
+ ///
+ public static bool IsValidUserName(string siUsername)
+ {
+ string usernameRegex = @"^(?=.*[a-zA-Z._@-])[a-zA-Z0-9._@-]+$";
+ if (Regex.IsMatch(siUsername, usernameRegex))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/src/Directory.Build.props b/src/pkgs/Altinn.Authorization.PEP/src/Directory.Build.props
new file mode 100644
index 00000000..80de5834
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/src/Directory.Build.props
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/Altinn.Authorization.PEP.Tests.csproj b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/Altinn.Authorization.PEP.Tests.csproj
new file mode 100644
index 00000000..40916a6e
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/Altinn.Authorization.PEP.Tests.csproj
@@ -0,0 +1,16 @@
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/AppAccessHandlerTest.cs b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/AppAccessHandlerTest.cs
new file mode 100644
index 00000000..2ad75b38
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/AppAccessHandlerTest.cs
@@ -0,0 +1,380 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Security.Claims;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Altinn.AccessManagement.Core.Models;
+using Altinn.Authorization.ABAC.Xacml;
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.PEP.Configuration;
+using Altinn.Common.PEP.Interfaces;
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ public class AppAccessHandlerTest
+ {
+ private readonly Mock _httpContextAccessorMock;
+ private readonly Mock _pdpMock;
+ private readonly IOptions _generalSettings;
+ private readonly AppAccessHandler _aah;
+
+ public AppAccessHandlerTest()
+ {
+ _httpContextAccessorMock = new Mock();
+ _pdpMock = new Mock();
+ _generalSettings = Options.Create(new PepSettings());
+ _aah = new AppAccessHandler(_httpContextAccessorMock.Object, _pdpMock.Object, new Mock>().Object);
+ }
+
+ ///
+ /// Test case: Send request and get response that fulfills all requirements
+ /// Expected: Context will succeed
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC01Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _aah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Send request and get respons with result deny
+ /// Expected: Context will fail
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC02Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Deny.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _aah.HandleAsync(context);
+
+ // Assert
+ Assert.False(context.HasSucceeded);
+ Assert.True(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Send request and get respons with two results
+ /// Expected: Context will fail
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC03Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+
+ // Add extra result
+ response.Response.Add(new XacmlJsonResult());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _aah.HandleAsync(context);
+
+ // Assert
+ Assert.False(context.HasSucceeded);
+ Assert.True(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Send request and get respons with obligation that contains min authentication level that the user meets
+ /// Expected: context will succeed
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC04Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ AddObligationWithMinAuthLv(response, "2");
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _aah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Send request and get respons with obligation that contains min authentication level that the user do not meet
+ /// Expected: context will fail
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC05Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ AddObligationWithMinAuthLv(response, "3");
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _aah.HandleAsync(context);
+
+ // Assert
+ Assert.False(context.HasSucceeded);
+ Assert.True(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Send request and get response that is null
+ /// Expected: Context will fail
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC06Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+ XacmlJsonResponse response = null;
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => _aah.HandleAsync(context));
+ }
+
+ ///
+ /// Test case: Send request and get response with a result list that is null
+ /// Expected: Context will fail
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC07Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+
+ // Create response with a result list that is null
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = null;
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => _aah.HandleAsync(context));
+ }
+
+ ///
+ /// Test case: Send request verify if the ipaddress from the x-forwarded-for header is received
+ /// Expected: XForwardedForHeader proeprty in request receives the ipaddress from the header
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC08Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ string ipaddress = "18.203.138.153";
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext(ipaddress));
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ AddObligationWithMinAuthLv(response, "2");
+
+ // verify
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _aah.HandleAsync(context);
+ }
+
+ ///
+ /// Test case: Send request and get response that fulfills all requirements with system user
+ /// Expected: Context will succeed
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC09Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContextSystemUser();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _aah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Send request and get response that fulfills all requirements with app user
+ /// Expected: Context will succeed
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC10Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContextAppUser("app_skd_flyttemelding");
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext());
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _aah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+ }
+
+ private ClaimsPrincipal CreateUser()
+ {
+ // Create the user
+ List claims = new List();
+
+ // type, value, valuetype, issuer
+ claims.Add(new Claim("urn:name", "Ola", "string", "org"));
+ claims.Add(new Claim("urn:altinn:authlevel", "2", "string", "org"));
+
+ ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity(claims));
+
+ return user;
+ }
+
+ private ClaimsPrincipal CreateSystemUser()
+ {
+ SystemUserClaim systemUserClaim = new SystemUserClaim
+ {
+ Systemuser_id = new List() { "996a686f-d24d-4d92-a92e-5b3cec4a8cf7" },
+ Systemuser_org = new OrgClaim() { ID = "myOrg" },
+ System_id = "the_matrix"
+ };
+
+ List claims = new List();
+ claims.Add(new Claim("authorization_details", JsonSerializer.Serialize(systemUserClaim), "string", "org"));
+ ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity(claims));
+
+ return user;
+ }
+
+ private ClaimsPrincipal CreateAppUser(string appId)
+ {
+ List claims = new List();
+ claims.Add(new Claim("urn:altinn:resource", appId, "string", "org"));
+ ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity(claims));
+ return user;
+ }
+
+ private HttpContext CreateHttpContext()
+ {
+ HttpContext httpContext = new DefaultHttpContext();
+ httpContext.Request.RouteValues.Add("org", "myOrg");
+ httpContext.Request.RouteValues.Add("app", "myApp");
+ httpContext.Request.RouteValues.Add("instanceGuid", "asdfg");
+ httpContext.Request.RouteValues.Add("InstanceOwnerId", "1000");
+
+ return httpContext;
+ }
+
+ private HttpContext CreateHttpContext(string xForwardedForHeader)
+ {
+ HttpContext httpContext = new DefaultHttpContext();
+ httpContext.Request.RouteValues.Add("org", "myOrg");
+ httpContext.Request.RouteValues.Add("app", "myApp");
+ httpContext.Request.RouteValues.Add("instanceGuid", "asdfg");
+ httpContext.Request.RouteValues.Add("InstanceOwnerId", "1000");
+
+ if (!string.IsNullOrEmpty(xForwardedForHeader))
+ {
+ httpContext.Request.Headers.Add("x-forwarded-for", xForwardedForHeader);
+ }
+
+ return httpContext;
+ }
+
+ private XacmlJsonResponse CreateResponse(string decision)
+ {
+ // Create response
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+
+ // Set result to premit
+ XacmlJsonResult result = new XacmlJsonResult();
+ result.Decision = decision;
+ response.Response.Add(result);
+
+ return response;
+ }
+
+ private XacmlJsonResponse AddObligationWithMinAuthLv(XacmlJsonResponse response, string minAuthLv)
+ {
+ // Add obligation to result with a minimum authentication level attribute
+ XacmlJsonResult result = response.Response[0];
+ XacmlJsonObligationOrAdvice obligation = new XacmlJsonObligationOrAdvice();
+ obligation.AttributeAssignment = new List();
+ XacmlJsonAttributeAssignment authenticationAttribute = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel",
+ Value = minAuthLv
+ };
+ obligation.AttributeAssignment.Add(authenticationAttribute);
+ result.Obligations = new List();
+ result.Obligations.Add(obligation);
+
+ return response;
+ }
+
+ private AuthorizationHandlerContext CreateAuthorizationHandlerContext()
+ {
+ AppAccessRequirement requirement = new AppAccessRequirement("read");
+ ClaimsPrincipal user = CreateUser();
+ Document resource = default(Document);
+ AuthorizationHandlerContext context = new AuthorizationHandlerContext(
+ new[] { requirement },
+ user,
+ resource);
+ return context;
+ }
+
+ private AuthorizationHandlerContext CreateAuthorizationHandlerContextSystemUser()
+ {
+ AppAccessRequirement requirement = new AppAccessRequirement("read");
+ ClaimsPrincipal user = CreateSystemUser();
+ Document resource = default(Document);
+ AuthorizationHandlerContext context = new AuthorizationHandlerContext(
+ new[] { requirement },
+ user,
+ resource);
+ return context;
+ }
+
+ private AuthorizationHandlerContext CreateAuthorizationHandlerContextAppUser(string appId)
+ {
+ AppAccessRequirement requirement = new AppAccessRequirement("read");
+ ClaimsPrincipal user = CreateAppUser(appId);
+ Document resource = default(Document);
+ AuthorizationHandlerContext context = new AuthorizationHandlerContext(
+ new[] { requirement },
+ user,
+ resource);
+ return context;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/DecisionHelperTest.cs b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/DecisionHelperTest.cs
new file mode 100644
index 00000000..56c81e60
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/DecisionHelperTest.cs
@@ -0,0 +1,457 @@
+using System;
+using System.Collections.Generic;
+using System.Security.Claims;
+
+using Altinn.Authorization.ABAC.Xacml;
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.PEP.Constants;
+using Altinn.Common.PEP.Helpers;
+using Altinn.Common.PEP.Models;
+
+using Xunit;
+
+namespace UnitTests
+{
+ public class DecisionHelperTest
+ {
+ private const string Org = "Altinn";
+ private const string App = "App";
+ private const string ActionType = "read";
+ private const int PartyId = 1000;
+
+ ///
+ /// Test case: Send attributes and creates request out of it
+ /// Expected: All values sent in will be created to attributes
+ ///
+ [Fact]
+ public void CreateXacmlJsonRequest_TC01()
+ {
+ // Arrange & Act
+ XacmlJsonRequestRoot requestRoot = DecisionHelper.CreateDecisionRequest(Org, App, CreateUserClaims(false), ActionType, PartyId, null, null);
+ XacmlJsonRequest request = requestRoot.Request;
+
+ // Assert
+ Assert.Equal(2, request.AccessSubject[0].Attribute.Count);
+ Assert.Single(request.Action[0].Attribute);
+ Assert.Equal(3, request.Resource[0].Attribute.Count);
+ }
+
+ ///
+ /// Test case: Send attributes and creates request out of it
+ /// Expected: Only valid urn values sent in will be created to attributes
+ ///
+ [Fact]
+ public void CreateXacmlJsonRequest_TC02()
+ {
+ // Arrange & Act
+ XacmlJsonRequestRoot requestRoot = DecisionHelper.CreateDecisionRequest(Org, App, CreateUserClaims(true), ActionType, PartyId, null, null);
+ XacmlJsonRequest request = requestRoot.Request;
+
+ // Assert
+ Assert.Equal(2, request.AccessSubject[0].Attribute.Count);
+ Assert.Single(request.Action[0].Attribute);
+ Assert.Equal(3, request.Resource[0].Attribute.Count);
+ }
+
+ ///
+ /// Test case: Send attributes and creates request out of it
+ /// Expected: Only valid urn, scope and orgnumber with correct values sent in will be created to attributes
+ ///
+ [Fact]
+ public void CreateXacmlJsonRequest_TC03()
+ {
+ // Arrange & Act
+ XacmlJsonRequestRoot requestRoot = DecisionHelper.CreateDecisionRequest(Org, App, CreateMaskinportenClaims("12313", "altinn.master"), ActionType);
+ XacmlJsonRequest request = requestRoot.Request;
+
+ // Assert
+ Assert.Equal(4, request.AccessSubject[0].Attribute.Count);
+ Assert.Single(request.Action[0].Attribute);
+ Assert.Equal(2, request.Resource[0].Attribute.Count);
+ }
+
+ ///
+ /// Test case: Send attributes and creates request out of it
+ /// Expected: All values sent in will be created to attributes
+ ///
+ [Fact]
+ public void CreateXacmlJsonRequest_TC04()
+ {
+ // Arrange & Act
+ XacmlJsonRequestRoot requestRoot = DecisionHelper.CreateDecisionRequest(Org, App, CreateUserClaims(false), ActionType);
+ XacmlJsonRequest request = requestRoot.Request;
+
+ // Assert
+ Assert.Equal(2, request.AccessSubject[0].Attribute.Count);
+ Assert.Single(request.Action[0].Attribute);
+ Assert.Equal(2, request.Resource[0].Attribute.Count);
+ }
+
+ ///
+ /// Test case: Send attributes and creates request out of it
+ /// Expected: Only valid urn values sent in will be created to attributes
+ ///
+ [Fact]
+ public void CreateXacmlJsonRequest_TC05()
+ {
+ // Arrange & Act
+ XacmlJsonRequestRoot requestRoot = DecisionHelper.CreateDecisionRequest(Org, App, CreateUserClaims(true), ActionType);
+ XacmlJsonRequest request = requestRoot.Request;
+
+ // Assert
+ Assert.Equal(2, request.AccessSubject[0].Attribute.Count);
+ Assert.Single(request.Action[0].Attribute);
+ Assert.Equal(2, request.Resource[0].Attribute.Count);
+ }
+
+ ///
+ /// Test case: Send attributes and creates request out of it
+ /// Expected: Only valid urn, scope and orgnumber with correct values sent in will be created to attributes
+ ///
+ [Fact]
+ public void CreateXacmlJsonRequest_TC06()
+ {
+ // Arrange & Act
+ XacmlJsonRequestRoot requestRoot = DecisionHelper.CreateDecisionRequest(Org, App, CreateMaskinportenClaims("12313", "altinn.master"), ActionType, PartyId, null, null);
+ XacmlJsonRequest request = requestRoot.Request;
+
+ // Assert
+ Assert.Equal(4, request.AccessSubject[0].Attribute.Count);
+ Assert.Single(request.Action[0].Attribute);
+ Assert.Equal(3, request.Resource[0].Attribute.Count);
+ }
+
+ ///
+ /// Test case: Response with result permit
+ /// Expected: Returns true
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC01()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+ XacmlJsonResult xacmlJsonResult = new XacmlJsonResult();
+ xacmlJsonResult.Decision = XacmlContextDecision.Permit.ToString();
+ response.Response.Add(xacmlJsonResult);
+
+ // Act
+ bool result = DecisionHelper.ValidatePdpDecision(response.Response, CreateUserClaims(false));
+
+ // Assert
+ Assert.True(result);
+ }
+
+ ///
+ /// Test case: Respons contains obligation with min authentication level that the user meets
+ /// Expected: Returns true
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC02()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+ XacmlJsonResult xacmlJsonResult = new XacmlJsonResult();
+ xacmlJsonResult.Decision = XacmlContextDecision.Permit.ToString();
+ response.Response.Add(xacmlJsonResult);
+
+ // Add obligation to result with a minimum authentication level attribute
+ XacmlJsonObligationOrAdvice obligation = new XacmlJsonObligationOrAdvice();
+ obligation.AttributeAssignment = new List();
+ XacmlJsonAttributeAssignment authenticationAttribute = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel",
+ Value = "2"
+ };
+ obligation.AttributeAssignment.Add(authenticationAttribute);
+ xacmlJsonResult.Obligations = new List();
+ xacmlJsonResult.Obligations.Add(obligation);
+
+ // Act
+ bool result = DecisionHelper.ValidatePdpDecision(response.Response, CreateUserClaims(false));
+
+ // Assert
+ Assert.True(result);
+ }
+
+ ///
+ /// Test case: Respons contains obligation with min authentication level that the user do not meet
+ /// Expected: Returns false
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC03()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+ XacmlJsonResult xacmlJsonResult = new XacmlJsonResult();
+ xacmlJsonResult.Decision = XacmlContextDecision.Permit.ToString();
+ response.Response.Add(xacmlJsonResult);
+
+ // Add obligation to result with a minimum authentication level attribute
+ XacmlJsonObligationOrAdvice obligation = new XacmlJsonObligationOrAdvice();
+ obligation.AttributeAssignment = new List();
+ XacmlJsonAttributeAssignment authenticationAttribute = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel",
+ Value = "3"
+ };
+ obligation.AttributeAssignment.Add(authenticationAttribute);
+ xacmlJsonResult.Obligations = new List();
+ xacmlJsonResult.Obligations.Add(obligation);
+
+ // Act
+ bool result = DecisionHelper.ValidatePdpDecision(response.Response, CreateUserClaims(false));
+
+ // Assert
+ Assert.False(result);
+ }
+
+ ///
+ /// Test case: Respons with result deny
+ /// Expected: Returns false
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC04()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+ XacmlJsonResult xacmlJsonResult = new XacmlJsonResult();
+ xacmlJsonResult.Decision = XacmlContextDecision.Deny.ToString();
+ response.Response.Add(xacmlJsonResult);
+
+ // Act
+ bool result = DecisionHelper.ValidatePdpDecision(response.Response, CreateUserClaims(false));
+
+ // Assert
+ Assert.False(result);
+ }
+
+ ///
+ /// Test case: Respons with two results
+ /// Expected: Returns false
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC05()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+ XacmlJsonResult xacmlJsonResult = new XacmlJsonResult();
+ xacmlJsonResult.Decision = XacmlContextDecision.Permit.ToString();
+ response.Response.Add(xacmlJsonResult);
+ response.Response.Add(new XacmlJsonResult());
+
+ // Act
+ bool result = DecisionHelper.ValidatePdpDecision(response.Response, CreateUserClaims(false));
+
+ // Assert
+ Assert.False(result);
+ }
+
+ ///
+ /// Test case: Result list is null
+ /// Expected: Throws ArgumentNullException
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC06()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = null;
+
+ // Act & Assert
+ Assert.Throws(() => DecisionHelper.ValidatePdpDecision(response.Response, CreateUserClaims(false)));
+ }
+
+ ///
+ /// Test case: User is null
+ /// Expected: Throws ArgumentNullException
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC07()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+
+ // Act & Assert
+ Assert.Throws(() => DecisionHelper.ValidatePdpDecision(response.Response, null));
+ }
+
+ ///
+ /// Test case: Response contains obligation with min authentication level that the user do not meet, get detailed response
+ /// Expected: Returns false
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC08()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+ XacmlJsonResult xacmlJsonResult = new XacmlJsonResult();
+ xacmlJsonResult.Decision = XacmlContextDecision.Permit.ToString();
+ response.Response.Add(xacmlJsonResult);
+
+ // Add obligation to result with a minimum authentication level attribute
+ XacmlJsonObligationOrAdvice obligation = new XacmlJsonObligationOrAdvice();
+ obligation.AttributeAssignment = new List();
+ string minAuthLevel = "3";
+ XacmlJsonAttributeAssignment authenticationAttribute = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel",
+ Value = minAuthLevel
+ };
+ obligation.AttributeAssignment.Add(authenticationAttribute);
+ xacmlJsonResult.Obligations = new List();
+ xacmlJsonResult.Obligations.Add(obligation);
+
+ // Act
+ EnforcementResult result = DecisionHelper.ValidatePdpDecisionDetailed(response.Response, CreateUserClaims(false));
+
+ // Assert
+ Assert.False(result.Authorized);
+ Assert.Contains(AltinnObligations.RequiredAuthenticationLevel, result.FailedObligations.Keys);
+ Assert.Equal(minAuthLevel, result.FailedObligations[AltinnObligations.RequiredAuthenticationLevel]);
+ }
+
+ ///
+ /// Test case: Response contains obligation with min authentication level that the user do not meet, get detailed response
+ /// Expected: Returns false
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC09()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+ XacmlJsonResult xacmlJsonResult = new XacmlJsonResult();
+ xacmlJsonResult.Decision = XacmlContextDecision.Permit.ToString();
+ response.Response.Add(xacmlJsonResult);
+
+ // Add obligation to result with a minimum authentication level attribute
+ XacmlJsonObligationOrAdvice obligation = new XacmlJsonObligationOrAdvice();
+ obligation.AttributeAssignment = new List();
+ string minAuthLevel = "4";
+ XacmlJsonAttributeAssignment authenticationAttribute = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel",
+ Value = minAuthLevel
+ };
+ obligation.AttributeAssignment.Add(authenticationAttribute);
+
+ XacmlJsonObligationOrAdvice obligationOrg = new XacmlJsonObligationOrAdvice();
+ obligationOrg.AttributeAssignment = new List();
+ string minAuthLevelOrg = "2";
+ XacmlJsonAttributeAssignment authenticationAttributeOrg = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel",
+ Value = minAuthLevelOrg
+ };
+ obligationOrg.AttributeAssignment.Add(authenticationAttributeOrg);
+
+ xacmlJsonResult.Obligations = new List();
+ xacmlJsonResult.Obligations.Add(obligation);
+ xacmlJsonResult.Obligations.Add(obligationOrg);
+
+ // Act
+ EnforcementResult result = DecisionHelper.ValidatePdpDecisionDetailed(response.Response, CreateUserClaims(false));
+
+ // Assert
+ Assert.False(result.Authorized);
+ Assert.Contains(AltinnObligations.RequiredAuthenticationLevel, result.FailedObligations.Keys);
+ Assert.Equal(minAuthLevel, result.FailedObligations[AltinnObligations.RequiredAuthenticationLevel]);
+ }
+
+ ///
+ /// Test case: Response contains obligation with min authentication level that the user do not meet, get detailed response
+ /// Expected: Returns false
+ ///
+ [Fact]
+ public void ValidatePdpDecision_TC10()
+ {
+ // Arrange
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+ XacmlJsonResult xacmlJsonResult = new XacmlJsonResult();
+ xacmlJsonResult.Decision = XacmlContextDecision.Permit.ToString();
+ response.Response.Add(xacmlJsonResult);
+
+ // Add obligation to result with a minimum authentication level attribute
+ XacmlJsonObligationOrAdvice obligation = new XacmlJsonObligationOrAdvice();
+ obligation.AttributeAssignment = new List();
+ string minAuthLevel = "4";
+ XacmlJsonAttributeAssignment authenticationAttribute = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel",
+ Value = minAuthLevel
+ };
+ obligation.AttributeAssignment.Add(authenticationAttribute);
+
+ XacmlJsonObligationOrAdvice obligationOrg = new XacmlJsonObligationOrAdvice();
+ obligationOrg.AttributeAssignment = new List();
+ string minAuthLevelOrg = "2";
+ XacmlJsonAttributeAssignment authenticationAttributeOrg = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel-org",
+ Value = minAuthLevelOrg
+ };
+ obligationOrg.AttributeAssignment.Add(authenticationAttributeOrg);
+
+ xacmlJsonResult.Obligations = new List();
+ xacmlJsonResult.Obligations.Add(obligationOrg);
+ xacmlJsonResult.Obligations.Add(obligation);
+
+ // Act
+ EnforcementResult result = DecisionHelper.ValidatePdpDecisionDetailed(response.Response, CreateUserClaims(false, "ttd"));
+
+ // Assert
+ Assert.True(result.Authorized);
+ Assert.Null(result.FailedObligations);
+ }
+
+ private ClaimsPrincipal CreateUserClaims(bool addExtraClaim, string org = null)
+ {
+ // Create the user
+ List claims = new List();
+
+ // type, value, valuetype, issuer
+ claims.Add(new Claim("urn:altinn:authlevel", "2", "string", "org"));
+ if (org != null)
+ {
+ claims.Add(new Claim("urn:altinn:org", org, "string", "org"));
+ }
+ else
+ {
+ claims.Add(new Claim("urn:name", "Ola", "string", "org"));
+ }
+
+ if (addExtraClaim)
+ {
+ claims.Add(new Claim("a", "a", "string", "a"));
+ }
+
+ ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity(claims));
+ return user;
+ }
+
+ private ClaimsPrincipal CreateMaskinportenClaims(string org, string scope)
+ {
+ // Create the user
+ List claims = new List();
+
+ // type, value, valuetype, issuer
+ claims.Add(new Claim("urn:altinn:authlevel", "2", "string", "org"));
+ claims.Add(new Claim("urn:altinn:orgNumber", org, "string", "org"));
+ if (scope != null)
+ {
+ claims.Add(new Claim("scope", scope, "string", "org"));
+ }
+
+ ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity(claims));
+ return user;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/GlobalSuppressions.cs b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/GlobalSuppressions.cs
new file mode 100644
index 00000000..51d4d2b1
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/GlobalSuppressions.cs
@@ -0,0 +1,8 @@
+// This file is used by Code Analysis to maintain SuppressMessage
+// attributes that are applied to this project.
+// Project-level suppressions either have no target or are given
+// a specific target and scoped to a namespace, type, member, etc.
+
+using System.Diagnostics.CodeAnalysis;
+
+[assembly: SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Test description should be in the name of the test.", Scope = "module")]
diff --git a/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/ResourceAccessHandlerTest.cs b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/ResourceAccessHandlerTest.cs
new file mode 100644
index 00000000..e510ca12
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/ResourceAccessHandlerTest.cs
@@ -0,0 +1,271 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+using Altinn.Authorization.ABAC.Xacml;
+using Altinn.Authorization.ABAC.Xacml.JsonProfile;
+using Altinn.Common.PEP.Configuration;
+using Altinn.Common.PEP.Interfaces;
+
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.Logging;
+using Microsoft.Extensions.Options;
+using Moq;
+using Xunit;
+
+namespace Altinn.Common.PEP.Authorization
+{
+ public class ResourceAccessHandlerTest
+ {
+ private readonly Mock _httpContextAccessorMock;
+ private readonly Mock _pdpMock;
+ private readonly IOptions _generalSettings;
+ private readonly ResourceAccessHandler _rah;
+
+ public ResourceAccessHandlerTest()
+ {
+ _httpContextAccessorMock = new Mock();
+ _pdpMock = new Mock();
+ _generalSettings = Options.Create(new PepSettings());
+ _rah = new ResourceAccessHandler(_httpContextAccessorMock.Object, _pdpMock.Object, new Mock>().Object);
+ }
+
+ ///
+ /// Test case: Send request and get response that fulfills all requirements
+ /// Expected: Context will succeed
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC01Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext("23453546", null, null));
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _rah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Send request and get response that fulfills all requirements
+ /// Expected: Context will succeed
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC02Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext("organization", "991825827", null));
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _rah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+
+ XacmlJsonRequestRoot request = _pdpMock.Invocations[0].Arguments[0] as XacmlJsonRequestRoot;
+ Assert.Equal("urn:altinn:organizationnumber", request.Request.Resource[0].Attribute[0].AttributeId);
+ Assert.Equal("991825827", request.Request.Resource[0].Attribute[0].Value);
+ }
+
+ ///
+ /// Test case: Send request and get response that fulfills all requirements
+ /// Expected: Exception since format of who is incorrect
+ ///
+ [Fact]
+
+ public async Task HandleRequirementAsync_TC03Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext("organization", "991825827M", null));
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ Task Act() => _rah.HandleAsync(context);
+
+ // Assert
+ ArgumentException exception = await Assert.ThrowsAsync(Act);
+ Assert.Equal("invalid party organization", exception.Message);
+ }
+
+ ///
+ /// Test case: Send request and get response that fulfills all requirements
+ /// Expected: True
+ ///
+ [Fact]
+
+ public async Task HandleRequirementAsync_TC04Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext("person", null, "01014922047"));
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _rah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+
+ XacmlJsonRequestRoot request = _pdpMock.Invocations[0].Arguments[0] as XacmlJsonRequestRoot;
+ Assert.Equal("urn:altinn:ssn", request.Request.Resource[0].Attribute[0].AttributeId);
+ Assert.Equal("01014922047", request.Request.Resource[0].Attribute[0].Value);
+ }
+
+ ///
+ /// Test case: Send request and get response that fulfills all requirements
+ /// Expected: Exception since format of who is incorrect
+ ///
+ [Fact]
+
+ public async Task HandleRequirementAsync_TC05Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext("person", null, "a01014922047"));
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ Task Act() => _rah.HandleAsync(context);
+
+ // Assert
+ ArgumentException exception = await Assert.ThrowsAsync(Act);
+ Assert.Equal("invalid party person", exception.Message);
+ }
+
+ ///
+ /// Test case: Send request and verify the XForwardedForHeader property in request
+ /// Expected: Request header does not have a xforwardedforheader and therefore the header property in xacmljsonrequest will be null
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC06Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext("23453546", null, null));
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+
+ // Verify
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.Is(xr => xr.Request.XForwardedForHeader == null))).Returns(Task.FromResult(response));
+
+ // Act
+ await _rah.HandleAsync(context);
+ }
+
+ ///
+ /// Test case: Send request verify if the ipaddress from the x-forwarded-for header is received
+ /// Expected: XForwardedForHeader proeprty in request receives the ipaddress from the header
+ ///
+ [Fact]
+ public async Task HandleRequirementAsync_TC07Async()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthorizationHandlerContext();
+ string ipaddress = "18.203.138.153";
+ _httpContextAccessorMock.Setup(h => h.HttpContext).Returns(CreateHttpContext("organization", "991825827", null, ipaddress));
+ XacmlJsonResponse response = CreateResponse(XacmlContextDecision.Permit.ToString());
+
+ // verify
+ _pdpMock.Setup(a => a.GetDecisionForRequest(It.IsAny())).Returns(Task.FromResult(response));
+
+ // Act
+ await _rah.HandleAsync(context);
+ }
+
+ private ClaimsPrincipal CreateUser()
+ {
+ // Create the user
+ List claims = new List();
+
+ // type, value, valuetype, issuer
+ claims.Add(new Claim("urn:name", "Ola", "string", "org"));
+ claims.Add(new Claim("urn:altinn:authlevel", "2", "string", "org"));
+
+ ClaimsPrincipal user = new ClaimsPrincipal(new ClaimsIdentity(claims));
+
+ return user;
+ }
+
+ private HttpContext CreateHttpContext(string party, string orgHeader, string ssnHeader, string xForwardedForHeader = null)
+ {
+ HttpContext httpContext = new DefaultHttpContext();
+ httpContext.Request.RouteValues.Add("party", party);
+ if (!string.IsNullOrEmpty(orgHeader))
+ {
+ httpContext.Request.Headers.Add("Altinn-Party-OrganizationNumber", orgHeader);
+ }
+
+ if (!string.IsNullOrEmpty(ssnHeader))
+ {
+ httpContext.Request.Headers.Add("Altinn-Party-SocialSecurityNumber", ssnHeader);
+ }
+
+ if (!string.IsNullOrEmpty(xForwardedForHeader))
+ {
+ httpContext.Request.Headers.Add("x-forwarded-for", xForwardedForHeader);
+ }
+
+ return httpContext;
+ }
+
+ private XacmlJsonResponse CreateResponse(string decision)
+ {
+ // Create response
+ XacmlJsonResponse response = new XacmlJsonResponse();
+ response.Response = new List();
+
+ // Set result to premit
+ XacmlJsonResult result = new XacmlJsonResult();
+ result.Decision = decision;
+ response.Response.Add(result);
+
+ return response;
+ }
+
+ private XacmlJsonResponse AddObligationWithMinAuthLv(XacmlJsonResponse response, string minAuthLv)
+ {
+ // Add obligation to result with a minimum authentication level attribute
+ XacmlJsonResult result = response.Response[0];
+ XacmlJsonObligationOrAdvice obligation = new XacmlJsonObligationOrAdvice();
+ obligation.AttributeAssignment = new List();
+ XacmlJsonAttributeAssignment authenticationAttribute = new XacmlJsonAttributeAssignment()
+ {
+ Category = "urn:altinn:minimum-authenticationlevel",
+ Value = minAuthLv
+ };
+ obligation.AttributeAssignment.Add(authenticationAttribute);
+ result.Obligations = new List();
+ result.Obligations.Add(obligation);
+
+ return response;
+ }
+
+ private AuthorizationHandlerContext CreateAuthorizationHandlerContext()
+ {
+ ResourceAccessRequirement requirement = new ResourceAccessRequirement("read", "altinn_access_management_apidelegation");
+ ClaimsPrincipal user = CreateUser();
+ Document resource = default(Document);
+ AuthorizationHandlerContext context = new AuthorizationHandlerContext(
+ new[] { requirement },
+ user,
+ resource);
+ return context;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/ScopeAccessHandlerTest.cs b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/ScopeAccessHandlerTest.cs
new file mode 100644
index 00000000..414c0c3e
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/tests/Altinn.Authorization.PEP.Tests/ScopeAccessHandlerTest.cs
@@ -0,0 +1,168 @@
+using System.Collections.Generic;
+using System.Reflection.Metadata;
+using System.Security.Claims;
+using System.Threading.Tasks;
+
+using Altinn.Common.PEP.Authorization;
+
+using Microsoft.AspNetCore.Authorization;
+
+using Xunit;
+
+namespace UnitTests
+{
+ public class ScopeAccessHandlerTest
+ {
+ private readonly ScopeAccessHandler _sah;
+
+ public ScopeAccessHandlerTest()
+ {
+ _sah = new ScopeAccessHandler();
+ }
+
+ ///
+ /// Test case: Valid scope claim is included in context.
+ /// Expected: Context will succeed.
+ ///
+ [Fact]
+ public async Task HandleAsync_ValidScope_ContextSuccess()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthzHandlerContext("altinn:appdeploy");
+
+ // Act
+ await _sah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Valid scope claim is included in context.
+ /// Expected: Context will succeed.
+ ///
+ [Fact]
+ public async Task HandleAsync_ValidScopeOf2_OneInvalidPresent_ContextSuccess()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthzHandlerContext("altinn:resourceregistry:write altinn:resourceregistry:read", new[] { "altinn:resourceregistry:admin", "altinn:resourceregistry:write" });
+
+ // Act
+ await _sah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Valid scope is missing in context
+ /// Expected: Context will fail.
+ ///
+ [Fact]
+ public async Task HandleAsync_ValidScopeOf2_OneInvalidPresent_ContextFail()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthzHandlerContext("altinn:resourceregistry:read", new[] { "altinn:resourceregistry:admin", "altinn:resourceregistry:write" });
+
+ // Act
+ await _sah.HandleAsync(context);
+
+ // Assert
+ Assert.False(context.HasSucceeded);
+ }
+
+ ///
+ /// Test case: Valid scope claim is included in context.
+ /// Expected: Context will succeed.
+ ///
+ [Fact]
+ public async Task HandleAsync_ValidScopeOf2_ContextSuccess()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthzHandlerContext("altinn:resourceregistry:write", new[] { "altinn:resourceregistry:admin", "altinn:resourceregistry:write" });
+
+ // Act
+ await _sah.HandleAsync(context);
+
+ // Assert
+ Assert.True(context.HasSucceeded);
+ Assert.False(context.HasFailed);
+ }
+
+ ///
+ /// Test case: Invalid scope claim is included in context.
+ /// Expected: Context will fail.
+ ///
+ [Fact]
+ public async Task HandleAsync_InvalidScope_ContextFail()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthzHandlerContext("altinn:invalid");
+
+ // Act
+ await _sah.HandleAsync(context);
+
+ // Assert
+ Assert.False(context.HasSucceeded);
+ }
+
+ ///
+ /// Test case: Empty scope claim is included in context.
+ /// Expected: Context will fail.
+ ///
+ [Fact]
+ public async Task HandleAsync_EmptyScope_ContextFail()
+ {
+ // Arrange
+ AuthorizationHandlerContext context = CreateAuthzHandlerContext(string.Empty);
+
+ // Act
+ await _sah.HandleAsync(context);
+
+ // Assert
+ Assert.False(context.HasSucceeded);
+ }
+
+ private AuthorizationHandlerContext CreateAuthzHandlerContext(string scopeClaim)
+ {
+ ScopeAccessRequirement requirement = new ScopeAccessRequirement("altinn:appdeploy");
+
+ ClaimsPrincipal user = new ClaimsPrincipal(
+ new ClaimsIdentity(
+ new List
+ {
+ new Claim("urn:altinn:scope", scopeClaim, "string", "org"),
+ new Claim("urn:altinn:org", "brg", "string", "org")
+ },
+ "AuthenticationTypes.Federation"));
+
+ AuthorizationHandlerContext context = new AuthorizationHandlerContext(
+ new[] { requirement },
+ user,
+ default(Document));
+ return context;
+ }
+
+ private AuthorizationHandlerContext CreateAuthzHandlerContext(string scopeClaim, string[] requiredScopes)
+ {
+ ScopeAccessRequirement requirement = new ScopeAccessRequirement(requiredScopes);
+
+ ClaimsPrincipal user = new ClaimsPrincipal(
+ new ClaimsIdentity(
+ new List
+ {
+ new Claim("scope", scopeClaim, "string", "org"),
+ new Claim("urn:altinn:org", "brg", "string", "org")
+ },
+ "AuthenticationTypes.Federation"));
+
+ AuthorizationHandlerContext context = new AuthorizationHandlerContext(
+ new[] { requirement },
+ user,
+ default(Document));
+ return context;
+ }
+ }
+}
diff --git a/src/pkgs/Altinn.Authorization.PEP/tests/Directory.Build.props b/src/pkgs/Altinn.Authorization.PEP/tests/Directory.Build.props
new file mode 100644
index 00000000..3c42af90
--- /dev/null
+++ b/src/pkgs/Altinn.Authorization.PEP/tests/Directory.Build.props
@@ -0,0 +1,8 @@
+
+
+
+
+ true
+
+
+
diff --git a/src/pkgs/Directory.Build.props b/src/pkgs/Directory.Build.props
index 8748fdee..20e06d22 100644
--- a/src/pkgs/Directory.Build.props
+++ b/src/pkgs/Directory.Build.props
@@ -1,3 +1,5 @@
-
-
+
+
+
+
\ No newline at end of file
diff --git a/src/pkgs/Directory.Build.targets b/src/pkgs/Directory.Build.targets
new file mode 100644
index 00000000..7e060b76
--- /dev/null
+++ b/src/pkgs/Directory.Build.targets
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+ 9.0.0
+ true
+ Pack
+ *nupkg
+
+ $(MSBuildThisFileDirectory)artifacts\
+
+
+
+
+
+
+
+ false
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/pkgs/Directory.Packages.props b/src/pkgs/Directory.Packages.props
deleted file mode 100644
index e0163e52..00000000
--- a/src/pkgs/Directory.Packages.props
+++ /dev/null
@@ -1,23 +0,0 @@
-
-
-
- true
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-