From 5dbb8aa0871c745949497156b4202e439595e874 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Remi=20Andr=C3=A9=20L=C3=B8voll?=
<56019927+lovoll@users.noreply.github.com>
Date: Tue, 29 Oct 2024 14:22:42 +0100
Subject: [PATCH] Feature/843 revoke instance delegation2 (#857)
* Unsaved files
* Push before stashrecovery
* Stash recovery
* Removed RevokeAllEndpoint
* Removed unused method
* Added test and fixed some duplicate code
* Fixed some code smells
Added Authorization removed for local test
* Unsaved changes missed from merge
---
.../Enums/RevokeStatus.cs | 18 +++
.../Helpers/DelegationHelper.cs | 61 +++++++-
.../Models/AppsInstanceRevokeResponse.cs | 47 +++++++
.../Models/InstanceRightRevokeResult.cs | 26 ++++
.../Services/AppsInstanceDelegationService.cs | 133 +++++++++++++-----
.../IAppsInstanceDelegationService.cs | 2 +-
.../Interfaces/IPolicyAdministrationPoint.cs | 8 ++
.../Services/PolicyAdministrationPoint.cs | 128 ++++++++++++++++-
.../AppsInstanceDelegationController.cs | 38 +++--
.../Enums/DelegationStatusExternal.cs | 2 +-
.../Enums/RevokeStatusExternal.cs | 20 +++
.../Mappers/AccessManagementMapper.cs | 2 +
.../AppsInstanceRevokeResponseDto.cs | 41 ++++++
.../Models/Rights/RightDelegationResultDto.cs | 8 +-
.../Models/Rights/RightRevokeResultDto.cs | 62 ++++++++
.../AppsInstanceDelegationControllerTest.cs | 24 ++++
.../TestDataAppsInstanceDelegation.cs | 35 +++++
.../request.json | 28 ++++
.../response.json | 31 ++++
.../request.json | 28 ++++
.../response.json | 31 ++++
.../request.json | 28 ++++
.../response.json | 31 ++++
.../313FB3847FA1/delegationpolicy.xml | 8 +-
.../N/313FB3847FA1/delegationpolicy.xml | 8 +-
.../N/313FB3847FA1/delegationpolicy.xml | 79 +++++++++++
.../Mocks/DelegationMetadataRepositoryMock.cs | 2 +-
.../Utils/AssertionUtil.cs | 31 +++-
28 files changed, 883 insertions(+), 77 deletions(-)
create mode 100644 src/Altinn.AccessManagement.Core/Enums/RevokeStatus.cs
create mode 100644 src/Altinn.AccessManagement.Core/Models/AppsInstanceRevokeResponse.cs
create mode 100644 src/Altinn.AccessManagement.Core/Models/InstanceRightRevokeResult.cs
create mode 100644 src/Altinn.AccessManagement/Enums/RevokeStatusExternal.cs
create mode 100644 src/Altinn.AccessManagement/Models/InstanceDelegation/AppsInstanceRevokeResponseDto.cs
create mode 100644 src/Altinn.AccessManagement/Models/Rights/RightRevokeResultDto.cs
create mode 100644 test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000001/request.json
create mode 100644 test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000001/response.json
create mode 100644 test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000002/request.json
create mode 100644 test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000002/response.json
create mode 100644 test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000009/request.json
create mode 100644 test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000009/response.json
rename test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000001/{P => N}/313FB3847FA1/delegationpolicy.xml (82%)
create mode 100644 test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000009/N/313FB3847FA1/delegationpolicy.xml
diff --git a/src/Altinn.AccessManagement.Core/Enums/RevokeStatus.cs b/src/Altinn.AccessManagement.Core/Enums/RevokeStatus.cs
new file mode 100644
index 00000000..f6f70708
--- /dev/null
+++ b/src/Altinn.AccessManagement.Core/Enums/RevokeStatus.cs
@@ -0,0 +1,18 @@
+namespace Altinn.AccessManagement.Core.Enums
+{
+ ///
+ /// Enum for different right revoke status responses
+ ///
+ public enum RevokeStatus
+ {
+ ///
+ /// Right was not revoked
+ ///
+ NotRevoked = 0,
+
+ ///
+ /// Right was revoked
+ ///
+ Revoked = 1
+ }
+}
diff --git a/src/Altinn.AccessManagement.Core/Helpers/DelegationHelper.cs b/src/Altinn.AccessManagement.Core/Helpers/DelegationHelper.cs
index 80b208f7..e1106d50 100644
--- a/src/Altinn.AccessManagement.Core/Helpers/DelegationHelper.cs
+++ b/src/Altinn.AccessManagement.Core/Helpers/DelegationHelper.cs
@@ -1,5 +1,4 @@
-using System;
-using System.Text;
+using System.Text;
using Altinn.AccessManagement.Core.Constants;
using Altinn.AccessManagement.Core.Enums;
using Altinn.AccessManagement.Core.Helpers.Extensions;
@@ -531,6 +530,34 @@ public static bool PolicyContainsMatchingInstanceRule(XacmlPolicy policy, Instan
return false;
}
+ ///
+ /// Returns Xacml rule in the provided XacmlPolicy that contains a rule having an identical Resource signature and contains the Action from the InstanceRule null if no match,
+ /// to be used for finding rules to revoke.
+ ///
+ /// A bool
+ public static XacmlRule GetXamlRuleContainsMatchingInstanceRule(XacmlPolicy policy, InstanceRule rule)
+ {
+ string ruleResourceKey = GetAttributeMatchKey(rule.Resource.ToList());
+
+ foreach (XacmlRule policyRule in policy.Rules)
+ {
+ if (!policyRule.Effect.Equals(XacmlEffectType.Permit) || policyRule.Target == null)
+ {
+ continue;
+ }
+
+ bool matchingActionFound = MatchingActionFound(policyRule.Target.AnyOf, rule, out List> policyResourceMatches);
+
+ if (policyResourceMatches.Exists(resourceMatch => GetAttributeMatchKey(resourceMatch) == ruleResourceKey) && matchingActionFound)
+ {
+ rule.RuleId = policyRule.RuleId;
+ return policyRule;
+ }
+ }
+
+ return null;
+ }
+
private static bool MatchingActionFound(ICollection input, InstanceRule rule, out List> policyResourceMatches)
{
policyResourceMatches = [];
@@ -839,6 +866,21 @@ public static IEnumerable GetRightDelegationResul
});
}
+ ///
+ /// Gets the list of Rules as a list of RightDelegationResult
+ ///
+ /// The rules output from a delegation to convert
+ /// List of RightDelegationResult
+ public static IEnumerable GetRightRevokeResultsFromInstanceRules(InstanceRight rules)
+ {
+ return rules.InstanceRules.Select(rule => new InstanceRightRevokeResult
+ {
+ Resource = rule.Resource,
+ Action = rule.Action,
+ Status = rule.CreatedSuccessfully ? RevokeStatus.Revoked : RevokeStatus.NotRevoked
+ });
+ }
+
///
/// Gets the list of Rights as a list of RightDelegationResult
///
@@ -888,6 +930,21 @@ public static (UuidType DelegationType, Guid Uuid) GetUuidTypeAndValueFromParty(
return (UuidType.NotSpecified, Guid.Empty);
}
+ ///
+ /// Gets the list of Rights as a list of RightDelegationResult
+ ///
+ /// The rights to convert
+ /// List of RightDelegationResult
+ public static IEnumerable GetRightRevokeResultsFromFailedInternalRights(List rights)
+ {
+ return rights.Select(right => new InstanceRightRevokeResult
+ {
+ Resource = right.Resource,
+ Action = right.Action,
+ Status = RevokeStatus.NotRevoked
+ });
+ }
+
private static void SetTypeForSingleRule(List keyRolePartyIds, int offeredByPartyId, List coveredBy, int parentPartyId, Rule rule, int? coveredByPartyId, int? coveredByUserId)
{
bool isUserId = TryGetUserIdFromAttributeMatch(coveredBy, out int coveredByUserIdFromRequest);
diff --git a/src/Altinn.AccessManagement.Core/Models/AppsInstanceRevokeResponse.cs b/src/Altinn.AccessManagement.Core/Models/AppsInstanceRevokeResponse.cs
new file mode 100644
index 00000000..1dcf4afa
--- /dev/null
+++ b/src/Altinn.AccessManagement.Core/Models/AppsInstanceRevokeResponse.cs
@@ -0,0 +1,47 @@
+using System.ComponentModel.DataAnnotations;
+using Altinn.AccessManagement.Core.Enums;
+using Altinn.AccessManagement.Core.Models.Register;
+
+namespace Altinn.AccessManagement.Core.Models;
+
+///
+/// Response model for performing revoke of access to a respource from Apps
+///
+public class AppsInstanceRevokeResponse
+{
+ ///
+ /// Gets or sets the urn identifying the party to delegate from
+ ///
+ [Required]
+ public PartyUrn From { get; set; }
+
+ ///
+ /// Gets or sets the urn identifying the party to be delegated to
+ ///
+ [Required]
+ public PartyUrn To { get; set; }
+
+ ///
+ /// Gets or sets the urn identifying the resource of the instance
+ ///
+ [Required]
+ public string ResourceId { get; set; }
+
+ ///
+ /// Gets or sets the urn identifying the instance id
+ ///
+ [Required]
+ public string InstanceId { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the instance delegation is for a parallel task
+ ///
+ [Required]
+ public InstanceDelegationMode InstanceDelegationMode { get; set; }
+
+ ///
+ /// Gets or sets the rights to delegate
+ ///
+ [Required]
+ public IEnumerable Rights { get; set; }
+}
\ No newline at end of file
diff --git a/src/Altinn.AccessManagement.Core/Models/InstanceRightRevokeResult.cs b/src/Altinn.AccessManagement.Core/Models/InstanceRightRevokeResult.cs
new file mode 100644
index 00000000..6b167e9b
--- /dev/null
+++ b/src/Altinn.AccessManagement.Core/Models/InstanceRightRevokeResult.cs
@@ -0,0 +1,26 @@
+using Altinn.AccessManagement.Core.Enums;
+using Altinn.AccessManagement.Core.Models.Rights;
+using Altinn.Urn.Json;
+
+namespace Altinn.AccessManagement.Core.Models;
+
+///
+/// This model describes a single right
+///
+public class InstanceRightRevokeResult
+{
+ ///
+ /// Gets or sets the list of resource matches which uniquely identifies the resource this right applies to.
+ ///
+ public List Resource { get; set; }
+
+ ///
+ /// Gets or sets the set of Attribute Id and Attribute Value for a specific action, to identify the action this right applies to
+ ///
+ public UrnJsonTypeValue Action { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the right was successfully revoked or not
+ ///
+ public RevokeStatus Status { get; set; }
+}
\ No newline at end of file
diff --git a/src/Altinn.AccessManagement.Core/Services/AppsInstanceDelegationService.cs b/src/Altinn.AccessManagement.Core/Services/AppsInstanceDelegationService.cs
index 5507e769..b101169b 100644
--- a/src/Altinn.AccessManagement.Core/Services/AppsInstanceDelegationService.cs
+++ b/src/Altinn.AccessManagement.Core/Services/AppsInstanceDelegationService.cs
@@ -220,8 +220,61 @@ public async Task> DelegationCheck(AppsI
///
public async Task> Delegate(AppsInstanceDelegationRequest request, CancellationToken cancellationToken = default)
+ {
+ (ValidationErrorBuilder Errors, InstanceRight RulesToHandle, List RightsAppCantHandle) input = await SetUpDelegationOrRevokeRequest(request, cancellationToken);
+
+ if (input.Errors.TryBuild(out var errorResult))
+ {
+ return errorResult;
+ }
+
+ AppsInstanceDelegationResponse result = new()
+ {
+ From = request.From,
+ To = request.To,
+ ResourceId = request.ResourceId,
+ InstanceId = request.InstanceId,
+ InstanceDelegationMode = request.InstanceDelegationMode
+ };
+
+ List rights = await DelegateRights(input.RulesToHandle, input.RightsAppCantHandle, cancellationToken);
+ result.Rights = rights;
+ result = RemoveInstanceIdFromResourceForDelegationResponse(result);
+
+ return result;
+ }
+
+ ///
+ public async Task> Revoke(AppsInstanceDelegationRequest request, CancellationToken cancellationToken = default)
+ {
+ (ValidationErrorBuilder Errors, InstanceRight RulesToHandle, List RightsAppCantHandle) input = await SetUpDelegationOrRevokeRequest(request, cancellationToken);
+
+ if (input.Errors.TryBuild(out var errorResult))
+ {
+ return errorResult;
+ }
+
+ AppsInstanceRevokeResponse result = new()
+ {
+ From = request.From,
+ To = request.To,
+ ResourceId = request.ResourceId,
+ InstanceId = request.InstanceId,
+ InstanceDelegationMode = request.InstanceDelegationMode
+ };
+
+ List rights = await RevokeRights(input.RulesToHandle, input.RightsAppCantHandle, cancellationToken);
+ result.Rights = rights;
+ result = RemoveInstanceIdFromResourceForRevokeResponse(result);
+
+ return result;
+ }
+
+ private async Task<(ValidationErrorBuilder Errors, InstanceRight RulesToHandle, List RightsAppCantHandle)> SetUpDelegationOrRevokeRequest(AppsInstanceDelegationRequest request, CancellationToken cancellationToken = default)
{
ValidationErrorBuilder errors = default;
+ List rightsAppCantHandle = null;
+ InstanceRight rulesToHandle = null;
// Fetch from and to from partyuuid
(UuidType Type, Guid? Uuid) from = await TranslatePartyUuidToPersonOrganizationUuid(request.From);
@@ -273,13 +326,14 @@ public async Task> Delegate(AppsInstanceD
}
}
- if (errors.TryBuild(out var errorResult))
- {
- return errorResult;
+ if (!errors.IsEmpty)
+ {
+ return (errors, rulesToHandle, rightsAppCantHandle);
}
- // Perform delegation
- InstanceRight rulesToDelegate = new InstanceRight
+ rightsAppCantHandle = [];
+
+ rulesToHandle = new InstanceRight
{
FromUuid = (Guid)from.Uuid,
FromType = from.Type,
@@ -292,46 +346,45 @@ public async Task> Delegate(AppsInstanceD
InstanceDelegationMode = request.InstanceDelegationMode,
InstanceDelegationSource = request.InstanceDelegationSource,
};
- List rightsAppCantDelegate = new List();
- UrnJsonTypeValue instanceId = KeyValueUrn.CreateUnchecked($"{AltinnXacmlConstants.MatchAttributeIdentifiers.ResourceInstanceAttribute}:{request.InstanceId}", AltinnXacmlConstants.MatchAttributeIdentifiers.ResourceInstanceAttribute.Length + 1);
- foreach (RightInternal rightToDelegate in request.Rights)
+ UrnJsonTypeValue instanceId = KeyValueUrn.CreateUnchecked($"{AltinnXacmlConstants.MatchAttributeIdentifiers.ResourceInstanceAttribute}:{request.InstanceId}", AltinnXacmlConstants.MatchAttributeIdentifiers.ResourceInstanceAttribute.Length + 1);
+
+ foreach (RightInternal rightToHandle in request.Rights)
{
- if (CheckIfInstanceIsDelegable(delegableRights, rightToDelegate))
+ if (CheckIfInstanceIsDelegable(delegableRights, rightToHandle))
{
- rightToDelegate.Resource.Add(instanceId);
- rulesToDelegate.InstanceRules.Add(new InstanceRule
+ rightToHandle.Resource.Add(instanceId);
+ rulesToHandle.InstanceRules.Add(new InstanceRule
{
- Resource = rightToDelegate.Resource,
- Action = rightToDelegate.Action
+ Resource = rightToHandle.Resource,
+ Action = rightToHandle.Action
});
}
else
{
- rightsAppCantDelegate.Add(rightToDelegate);
+ rightsAppCantHandle.Add(rightToHandle);
}
}
- AppsInstanceDelegationResponse result = new()
- {
- From = request.From,
- To = request.To,
- ResourceId = request.ResourceId,
- InstanceId = request.InstanceId,
- InstanceDelegationMode = request.InstanceDelegationMode
- };
-
- List rights = await DelegateRights(rulesToDelegate, rightsAppCantDelegate, cancellationToken);
- result.Rights = rights;
- result = RemoveInstanceIdFromResourceForResponse(result);
-
- return result;
+ return (errors, rulesToHandle, rightsAppCantHandle);
}
- ///
- public Task> Revoke(AppsInstanceDelegationRequest request, CancellationToken cancellationToken = default)
+ private async Task> RevokeRights(InstanceRight rulesToRevoke, List rightsAppCantRevoke, CancellationToken cancellationToken)
{
- throw new NotImplementedException();
+ List rights = new List();
+
+ if (rulesToRevoke.InstanceRules.Count > 0)
+ {
+ InstanceRight delegationResult = await _pap.TryWriteInstanceRevokePolicyRules(rulesToRevoke, cancellationToken);
+ rights.AddRange(DelegationHelper.GetRightRevokeResultsFromInstanceRules(delegationResult));
+ }
+
+ if (rightsAppCantRevoke.Count > 0)
+ {
+ rights.AddRange(DelegationHelper.GetRightRevokeResultsFromFailedInternalRights(rightsAppCantRevoke));
+ }
+
+ return rights;
}
private async Task> DelegateRights(InstanceRight rulesToDelegate, List rightsAppCantDelegate, CancellationToken cancellationToken)
@@ -397,21 +450,31 @@ public async Task>> Get(AppsInstance
}
List result = await _pip.GetInstanceDelegations(request, cancellationToken);
- result = RemoveInstanceIdFromResourceForResponseList(result);
+ result = RemoveInstanceIdFromResourceForDelegationResponseList(result);
return result;
}
+
+ private static AppsInstanceRevokeResponse RemoveInstanceIdFromResourceForRevokeResponse(AppsInstanceRevokeResponse input)
+ {
+ foreach (var right in input.Rights)
+ {
+ right.Resource.RemoveAll(r => r.HasValue && r.Value.PrefixSpan.ToString() == AltinnXacmlConstants.MatchAttributeIdentifiers.ResourceInstanceAttribute);
+ }
+
+ return input;
+ }
- private static List RemoveInstanceIdFromResourceForResponseList(List input)
+ private static List RemoveInstanceIdFromResourceForDelegationResponseList(List input)
{
foreach (AppsInstanceDelegationResponse item in input)
{
- RemoveInstanceIdFromResourceForResponse(item);
+ RemoveInstanceIdFromResourceForDelegationResponse(item);
}
return input;
}
- private static AppsInstanceDelegationResponse RemoveInstanceIdFromResourceForResponse(AppsInstanceDelegationResponse input)
+ private static AppsInstanceDelegationResponse RemoveInstanceIdFromResourceForDelegationResponse(AppsInstanceDelegationResponse input)
{
foreach (var right in input.Rights)
{
diff --git a/src/Altinn.AccessManagement.Core/Services/Interfaces/IAppsInstanceDelegationService.cs b/src/Altinn.AccessManagement.Core/Services/Interfaces/IAppsInstanceDelegationService.cs
index 3e5a9f83..7ef6f480 100644
--- a/src/Altinn.AccessManagement.Core/Services/Interfaces/IAppsInstanceDelegationService.cs
+++ b/src/Altinn.AccessManagement.Core/Services/Interfaces/IAppsInstanceDelegationService.cs
@@ -30,7 +30,7 @@ public interface IAppsInstanceDelegationService
/// the request data collected in a dto
/// The
/// Boolean whether the app instance delegation was revoked
- public Task> Revoke(AppsInstanceDelegationRequest request, CancellationToken cancellationToken = default);
+ public Task> Revoke(AppsInstanceDelegationRequest request, CancellationToken cancellationToken = default);
///
/// Gets app instance delegation
diff --git a/src/Altinn.AccessManagement.Core/Services/Interfaces/IPolicyAdministrationPoint.cs b/src/Altinn.AccessManagement.Core/Services/Interfaces/IPolicyAdministrationPoint.cs
index 6d775f82..2a6a0c5f 100644
--- a/src/Altinn.AccessManagement.Core/Services/Interfaces/IPolicyAdministrationPoint.cs
+++ b/src/Altinn.AccessManagement.Core/Services/Interfaces/IPolicyAdministrationPoint.cs
@@ -33,6 +33,14 @@ public interface IPolicyAdministrationPoint
/// The list of instance rules with created Id and result status
Task TryWriteInstanceDelegationPolicyRules(InstanceRight rules, CancellationToken cancellationToken = default);
+ ///
+ /// Trys to sort and revoke the set of rules as delegation policy files in blob storage.
+ ///
+ /// The set of instance rules to be delegated
+ /// CancellationToke
+ /// The list of instance rules with created Id and result status
+ Task TryWriteInstanceRevokePolicyRules(InstanceRight rules, CancellationToken cancellationToken = default);
+
///
/// Trys to sort and delete the set of rules matching the list of ruleMatches to delete from delegation policy files in blob storage.
///
diff --git a/src/Altinn.AccessManagement.Core/Services/PolicyAdministrationPoint.cs b/src/Altinn.AccessManagement.Core/Services/PolicyAdministrationPoint.cs
index 85a1e0be..ada940bc 100644
--- a/src/Altinn.AccessManagement.Core/Services/PolicyAdministrationPoint.cs
+++ b/src/Altinn.AccessManagement.Core/Services/PolicyAdministrationPoint.cs
@@ -56,6 +56,74 @@ public async Task WritePolicyAsync(string org, string app, Stream fileStre
return response?.GetRawResponse()?.Status == (int)HttpStatusCode.Created;
}
+ private async Task WriteInstanceRevokePolicyInternal(InstanceRight rules, CancellationToken cancellationToken = default)
+ {
+ // Check for a current delegation change from postgresql
+ (XacmlPolicy ExistingDelegationPolicy, string PolicyPath) policyData = await GetExistingPolicy(null, rules, cancellationToken);
+ string policyPath = policyData.PolicyPath;
+
+ // if no delegations exist all revoke must already be performed
+ if (policyData.ExistingDelegationPolicy == null)
+ {
+ return true;
+ }
+
+ var policyClient = _policyFactory.Create(policyPath);
+
+ if (!await policyClient.PolicyExistsAsync(cancellationToken))
+ {
+ return false;
+ }
+
+ string leaseId = await policyClient.TryAcquireBlobLease(cancellationToken);
+ if (leaseId != null)
+ {
+ try
+ {
+ // Build delegation XacmlPolicy either as a new policy or add rules to existing
+ XacmlPolicy delegationPolicy = BuildInstanceRevokePolicy(policyData.ExistingDelegationPolicy, rules);
+
+ // Write delegation policy to blob storage
+ MemoryStream dataStream = PolicyHelper.GetXmlMemoryStreamFromXacmlPolicy(delegationPolicy);
+ Response blobResponse =
+ await policyClient.WritePolicyConditionallyAsync(dataStream, leaseId, cancellationToken);
+ Response httpResponse = blobResponse.GetRawResponse();
+ if (httpResponse.Status != (int)HttpStatusCode.Created)
+ {
+ int status = httpResponse.Status;
+ string reasonPhrase = httpResponse.ReasonPhrase;
+ _logger.LogError(
+ "Writing of delegation policy at path: {policyPath} failed. Response Status Code:\n{status}. Response Reason Phrase:\n{reasonPhrase}",
+ policyPath,
+ status,
+ reasonPhrase);
+ return false;
+ }
+
+ int rulesInPolicy = delegationPolicy.Rules.Count;
+ DelegationChangeType changeType;
+ if (rulesInPolicy > 0)
+ {
+ changeType = DelegationChangeType.Revoke;
+ }
+ else
+ {
+ changeType = DelegationChangeType.RevokeLast;
+ }
+
+ // Update db and use new version from latest update
+ return await WritePolicyUpdateToDB(policyPath, blobResponse.Value.VersionId, changeType, rules, cancellationToken);
+ }
+ finally
+ {
+ policyClient.ReleaseBlobLease(leaseId, CancellationToken.None);
+ }
+ }
+
+ _logger.LogInformation("Could not acquire blob lease lock on delegation policy at path: {policyPath}", policyPath);
+ return false;
+ }
+
private async Task WriteInstanceDelegationPolicyInternal(string policyPath, InstanceRight rules, CancellationToken cancellationToken = default)
{
// Check for a current delegation change from postgresql
@@ -95,7 +163,7 @@ private async Task WriteInstanceDelegationPolicyInternal(string policyPath
}
// Update db and use new version from latest update
- return await WritePolicyUpdateToDB(policyPath, blobResponse.Value.VersionId, rules, cancellationToken);
+ return await WritePolicyUpdateToDB(policyPath, blobResponse.Value.VersionId, DelegationChangeType.Grant, rules, cancellationToken);
}
finally
{
@@ -107,12 +175,12 @@ private async Task WriteInstanceDelegationPolicyInternal(string policyPath
return false;
}
- private async Task WritePolicyUpdateToDB(string policyPath, string versionId, InstanceRight rules, CancellationToken cancellationToken)
+ private async Task WritePolicyUpdateToDB(string policyPath, string versionId, DelegationChangeType changeType, InstanceRight rules, CancellationToken cancellationToken)
{
// Update db and use new version from latest update
InstanceDelegationChange instanceDelegationChange = new InstanceDelegationChange
{
- DelegationChangeType = DelegationChangeType.Grant,
+ DelegationChangeType = changeType,
InstanceDelegationMode = rules.InstanceDelegationMode,
ResourceId = rules.ResourceId,
InstanceId = rules.InstanceId,
@@ -202,6 +270,21 @@ private static XacmlPolicy BuildInstanceDelegationPolicy(XacmlPolicy existingDel
return delegationPolicy;
}
+ private static XacmlPolicy BuildInstanceRevokePolicy(XacmlPolicy delegationPolicy, InstanceRight rules)
+ {
+ // Build delegation XacmlPolicy by removing the rules revoked
+ foreach (InstanceRule rule in rules.InstanceRules)
+ {
+ XacmlRule xacmlRule = DelegationHelper.GetXamlRuleContainsMatchingInstanceRule(delegationPolicy, rule);
+ if (xacmlRule != null)
+ {
+ delegationPolicy.Rules.Remove(xacmlRule);
+ }
+ }
+
+ return delegationPolicy;
+ }
+
///
public async Task TryWriteInstanceDelegationPolicyRules(InstanceRight rules, CancellationToken cancellationToken = default)
{
@@ -245,6 +328,45 @@ public async Task TryWriteInstanceDelegationPolicyRules(InstanceR
return rules;
}
+ ///
+ public async Task TryWriteInstanceRevokePolicyRules(InstanceRight rules, CancellationToken cancellationToken = default)
+ {
+ bool writePolicySuccess = false;
+
+ try
+ {
+ writePolicySuccess = await WriteInstanceRevokePolicyInternal(rules, cancellationToken);
+ }
+ catch (Exception ex)
+ {
+ bool validPath = DelegationHelper.TryGetDelegationPolicyPathFromInstanceRule(rules, out string path);
+ if (validPath)
+ {
+ _logger.LogError(ex, "An exception occured while processing authorization rules for delegation on delegation policy path: {path}", path);
+ }
+ else
+ {
+ string unsortablesJson = JsonSerializer.Serialize(rules);
+ _logger.LogError("One or more rules could not be processed because of incomplete input:\n{unsortablesJson}", unsortablesJson);
+ }
+ }
+
+ foreach (InstanceRule rule in rules.InstanceRules)
+ {
+ if (writePolicySuccess)
+ {
+ rule.CreatedSuccessfully = true;
+ }
+ else
+ {
+ rule.RuleId = string.Empty;
+ rule.CreatedSuccessfully = false;
+ }
+ }
+
+ return rules;
+ }
+
///
public async Task> TryWriteDelegationPolicyRules(List rules, CancellationToken cancellationToken = default)
{
diff --git a/src/Altinn.AccessManagement/Controllers/ResourceOwnerAPI/AppsInstanceDelegationController.cs b/src/Altinn.AccessManagement/Controllers/ResourceOwnerAPI/AppsInstanceDelegationController.cs
index 56c5aea0..3ddc2b50 100644
--- a/src/Altinn.AccessManagement/Controllers/ResourceOwnerAPI/AppsInstanceDelegationController.cs
+++ b/src/Altinn.AccessManagement/Controllers/ResourceOwnerAPI/AppsInstanceDelegationController.cs
@@ -95,11 +95,18 @@ public async Task DelegationCheck([FromRoute] string resourceId, [
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
public async Task Delegation([FromBody] AppsInstanceDelegationRequestDto appInstanceDelegationRequestDto, [FromRoute] string resourceId, [FromRoute] string instanceId, [FromHeader(Name = "PlatformAccessToken")] string token, CancellationToken cancellationToken = default)
{
+ ResourceIdUrn.ResourceId? performer = GetOrgAppFromToken(token);
+
+ if (performer == null)
+ {
+ return Forbid();
+ }
+
AppsInstanceDelegationRequest request = _mapper.Map(appInstanceDelegationRequestDto);
request.ResourceId = resourceId;
request.InstanceId = instanceId;
- request.PerformedBy = GetOrgAppFromToken(token);
+ request.PerformedBy = performer;
request.InstanceDelegationSource = Core.Enums.InstanceDelegationSource.App;
request.InstanceDelegationMode = Core.Enums.InstanceDelegationMode.Normal;
@@ -145,7 +152,7 @@ public async Task Get([FromRoute] string resourceId, [FromRoute] s
ResourceIdUrn.ResourceId? performer = GetOrgAppFromToken(token);
if (performer == null)
- {
+ {
return Forbid();
}
@@ -171,7 +178,6 @@ public async Task Get([FromRoute] string resourceId, [FromRoute] s
return Ok(result);
}
- /*
///
/// Revokes access to an app instance
///
@@ -183,26 +189,31 @@ public async Task Get([FromRoute] string resourceId, [FromRoute] s
/// Result
[HttpPost]
[Authorize(Policy = AuthzConstants.PLATFORM_ACCESS_AUTHORIZATION)]
- [Route("v1/apps/instancedelegation/{resourceId}/{instanceId}/revoke")]
+ [Route("v1/app/delegationrevoke/resource/{resourceId}/instance/{instanceId}")]
[Consumes(MediaTypeNames.Application.Json)]
[Produces(MediaTypeNames.Application.Json)]
[ProducesResponseType(typeof(AppsInstanceDelegationResponseDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(void), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(void), StatusCodes.Status403Forbidden)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
- public async Task Revoke([FromBody] AppsInstanceDelegationRequestDto appInstanceDelegationRequestDto, [FromRoute] string resourceId, [FromRoute] string instanceId, [FromHeader(Name = "PlatformAccessToken")] string token, CancellationToken cancellationToken = default)
+ public async Task Revoke([FromBody] AppsInstanceDelegationRequestDto appInstanceDelegationRequestDto, [FromRoute] string resourceId, [FromRoute] string instanceId, [FromHeader(Name = "PlatformAccessToken")] string token, CancellationToken cancellationToken = default)
{
AppsInstanceDelegationRequest request = _mapper.Map(appInstanceDelegationRequestDto);
+ ResourceIdUrn.ResourceId? performer = GetOrgAppFromToken(token);
+
+ if (performer == null)
+ {
+ return Forbid();
+ }
+
request.ResourceId = resourceId;
request.InstanceId = instanceId;
request.InstanceDelegationSource = Core.Enums.InstanceDelegationSource.App;
+ request.PerformedBy = performer;
- List performedBy = GetOrgAppFromToken(token);
- request.PerformedBy = performedBy;
-
- Result serviceResult = await _appInstanceDelegationService.Revoke(request, cancellationToken);
-
+ Result serviceResult = await _appInstanceDelegationService.Revoke(request, cancellationToken);
+
if (serviceResult.IsProblem)
{
return serviceResult.Problem?.ToActionResult();
@@ -210,16 +221,15 @@ public async Task Revoke([FromBody] AppsInstanceDelegationRequestD
// Check result
int totalDelegations = request.Rights.Count();
- int validDelegations = serviceResult.Value.Rights.Count(r => r.Status == Core.Enums.DelegationStatus.Delegated);
+ int validDelegations = serviceResult.Value.Rights.Count(r => r.Status == Core.Enums.RevokeStatus.Revoked);
if (validDelegations == totalDelegations)
{
- return Ok(_mapper.Map(serviceResult.Value));
+ return Ok(_mapper.Map(serviceResult.Value));
}
- return StatusCode(StatusCodes.Status206PartialContent, _mapper.Map(serviceResult.Value));
+ return StatusCode(StatusCodes.Status206PartialContent, _mapper.Map(serviceResult.Value));
}
- */
///
/// delegating app from the platform token
diff --git a/src/Altinn.AccessManagement/Enums/DelegationStatusExternal.cs b/src/Altinn.AccessManagement/Enums/DelegationStatusExternal.cs
index cb34ae67..2388f81e 100644
--- a/src/Altinn.AccessManagement/Enums/DelegationStatusExternal.cs
+++ b/src/Altinn.AccessManagement/Enums/DelegationStatusExternal.cs
@@ -17,4 +17,4 @@ public enum DelegationStatusExternal
/// Right was delegated
///
Delegated = 1
-}
+}
\ No newline at end of file
diff --git a/src/Altinn.AccessManagement/Enums/RevokeStatusExternal.cs b/src/Altinn.AccessManagement/Enums/RevokeStatusExternal.cs
new file mode 100644
index 00000000..fd51fd55
--- /dev/null
+++ b/src/Altinn.AccessManagement/Enums/RevokeStatusExternal.cs
@@ -0,0 +1,20 @@
+using System.Text.Json.Serialization;
+
+namespace Altinn.AccessManagement.Enums;
+
+///
+/// Enum for different right revoke status responses
+///
+[JsonConverter(typeof(JsonStringEnumConverter))]
+public enum RevokeStatusExternal
+{
+ ///
+ /// Right was not revoked
+ ///
+ NotRevoked = 0,
+
+ ///
+ /// Right was revoked
+ ///
+ Revoked = 1
+}
diff --git a/src/Altinn.AccessManagement/Mappers/AccessManagementMapper.cs b/src/Altinn.AccessManagement/Mappers/AccessManagementMapper.cs
index 6dc3498b..321a91d9 100644
--- a/src/Altinn.AccessManagement/Mappers/AccessManagementMapper.cs
+++ b/src/Altinn.AccessManagement/Mappers/AccessManagementMapper.cs
@@ -89,6 +89,8 @@ public AccessManagementMapper()
CreateMap();
CreateMap();
CreateMap();
+ CreateMap();
+ CreateMap();
CreateMap();
}
}
diff --git a/src/Altinn.AccessManagement/Models/InstanceDelegation/AppsInstanceRevokeResponseDto.cs b/src/Altinn.AccessManagement/Models/InstanceDelegation/AppsInstanceRevokeResponseDto.cs
new file mode 100644
index 00000000..10005162
--- /dev/null
+++ b/src/Altinn.AccessManagement/Models/InstanceDelegation/AppsInstanceRevokeResponseDto.cs
@@ -0,0 +1,41 @@
+using System.ComponentModel.DataAnnotations;
+using Altinn.AccessManagement.Core.Models.Register;
+using Altinn.Urn.Json;
+
+namespace Altinn.AccessManagement.Models;
+
+///
+/// Request model for performing revoke of access to a resource from Apps
+///
+public class AppsInstanceRevokeResponseDto
+{
+ ///
+ /// Gets or sets the urn identifying the party to delegate from
+ ///
+ [Required]
+ public UrnJsonTypeValue From { get; set; }
+
+ ///
+ /// Gets or sets the urn identifying the party to be delegated to
+ ///
+ [Required]
+ public UrnJsonTypeValue To { get; set; }
+
+ ///
+ /// Gets or sets a value identifying the resource of the instance
+ ///
+ [Required]
+ public string ResourceId { get; set; }
+
+ ///
+ /// Gets or sets a value identifying the instance id
+ ///
+ [Required]
+ public string InstanceId { get; set; }
+
+ ///
+ /// Gets or sets the rights to delegate
+ ///
+ [Required]
+ public IEnumerable Rights { get; set; }
+}
\ No newline at end of file
diff --git a/src/Altinn.AccessManagement/Models/Rights/RightDelegationResultDto.cs b/src/Altinn.AccessManagement/Models/Rights/RightDelegationResultDto.cs
index 71ecf3a6..75339152 100644
--- a/src/Altinn.AccessManagement/Models/Rights/RightDelegationResultDto.cs
+++ b/src/Altinn.AccessManagement/Models/Rights/RightDelegationResultDto.cs
@@ -43,11 +43,7 @@ public static IEnumerable GetExamples(ExampleDataOptio
{
"type": "urn:altinn:resource",
"value": "app_ttd_apps-test"
- },
- {
- "type": "urn:altinn:resource:instance-id",
- "value": "0191579e-72bc-7977-af5d-f9e92af4393b"
- },
+ },
{
"type": "urn:altinn:resource:task",
"value": "task_1"
@@ -63,4 +59,4 @@ public static IEnumerable GetExamples(ExampleDataOptio
""";
yield return JsonSerializer.Deserialize(json, SerializerOptions);
}
-}
+}
\ No newline at end of file
diff --git a/src/Altinn.AccessManagement/Models/Rights/RightRevokeResultDto.cs b/src/Altinn.AccessManagement/Models/Rights/RightRevokeResultDto.cs
new file mode 100644
index 00000000..c2d8c02b
--- /dev/null
+++ b/src/Altinn.AccessManagement/Models/Rights/RightRevokeResultDto.cs
@@ -0,0 +1,62 @@
+using System.Text.Json;
+using Altinn.AccessManagement.Core.Models.Rights;
+using Altinn.AccessManagement.Enums;
+using Altinn.Swashbuckle.Examples;
+using Altinn.Swashbuckle.Filters;
+using Altinn.Urn.Json;
+
+namespace Altinn.AccessManagement.Models;
+
+///
+/// This model describes a single right
+///
+[SwaggerExampleFromExampleProvider]
+public class RightRevokeResultDto : IExampleDataProvider
+{
+ private static readonly JsonSerializerOptions SerializerOptions = new(JsonSerializerDefaults.Web);
+
+ ///
+ /// Gets or sets the list of resource matches which uniquely identifies the resource this right applies to.
+ ///
+ public IEnumerable Resource { get; set; }
+
+ ///
+ /// Gets or sets the set of Attribute Id and Attribute Value for a specific action, to identify the action this right applies to
+ ///
+ public UrnJsonTypeValue Action { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether the right was successfully delegated or not
+ ///
+ public RevokeStatusExternal Status { get; set; }
+
+ ///
+ /// Example data provider for RightV2
+ ///
+ /// Options
+ ///
+ public static IEnumerable GetExamples(ExampleDataOptions options)
+ {
+ var json = """
+ {
+ "resource": [
+ {
+ "type": "urn:altinn:resource",
+ "value": "app_ttd_apps-test"
+ },
+ {
+ "type": "urn:altinn:resource:task",
+ "value": "task_1"
+ }
+ ],
+ "action":
+ {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "read"
+ },
+ "status": "Revoked"
+ }
+ """;
+ yield return JsonSerializer.Deserialize(json, SerializerOptions);
+ }
+}
diff --git a/test/Altinn.AccessManagement.Tests/Controllers/ResourceOwnerAPI/AppsInstanceDelegationControllerTest.cs b/test/Altinn.AccessManagement.Tests/Controllers/ResourceOwnerAPI/AppsInstanceDelegationControllerTest.cs
index 9c3b643c..056f220f 100644
--- a/test/Altinn.AccessManagement.Tests/Controllers/ResourceOwnerAPI/AppsInstanceDelegationControllerTest.cs
+++ b/test/Altinn.AccessManagement.Tests/Controllers/ResourceOwnerAPI/AppsInstanceDelegationControllerTest.cs
@@ -85,6 +85,30 @@ public async Task AppsInstanceDelegationController_ValidToken_Delegate_OK(string
AssertionUtil.AssertAppsInstanceDelegationResponseDto(expected, actual);
}
+ ///
+ /// Test case: POST apps/instancedelegation/{resourceId}/{instanceId}
+ /// with a valid delegation
+ /// Expected: - Should return 200 OK
+ /// Reason: See testdat cases for details
+ ///
+ [Theory]
+ [MemberData(nameof(TestDataAppsInstanceDelegation.RevokeReadForAppOnlyExistingPolicyRevokeLast), MemberType = typeof(TestDataAppsInstanceDelegation))]
+ [MemberData(nameof(TestDataAppsInstanceDelegation.RevokeReadForAppMultipleExistingPolicyRevoke), MemberType = typeof(TestDataAppsInstanceDelegation))]
+ [MemberData(nameof(TestDataAppsInstanceDelegation.RevokeReadForAppNoExistingPolicyRevokeLast), MemberType = typeof(TestDataAppsInstanceDelegation))]
+ public async Task AppsInstanceDelegationController_ValidToken_Revoke_OK(string platformToken, AppsInstanceDelegationRequestDto request, string resourceId, string instanceId, AppsInstanceRevokeResponseDto expected)
+ {
+ var client = GetTestClient(platformToken);
+
+ // Act
+ HttpResponseMessage response = await client.PostAsync($"accessmanagement/api/v1/app/delegationrevoke/resource/{resourceId}/instance/{instanceId}", new StringContent(JsonSerializer.Serialize(request), Encoding.UTF8, MediaTypeNames.Application.Json));
+
+ // Assert
+ Assert.Equal(HttpStatusCode.OK, response.StatusCode);
+
+ AppsInstanceRevokeResponseDto actual = JsonSerializer.Deserialize(await response.Content.ReadAsStringAsync(), options);
+ AssertionUtil.AssertAppsInstanceRevokeResponseDto(expected, actual);
+ }
+
///
/// Test case: POST apps/instancedelegation/{resourceId}/{instanceId}
/// with a valid delegation
diff --git a/test/Altinn.AccessManagement.Tests/Data/AuthorizedParties/TestDataAppsInstanceDelegation.cs b/test/Altinn.AccessManagement.Tests/Data/AuthorizedParties/TestDataAppsInstanceDelegation.cs
index 5746915c..e541a352 100644
--- a/test/Altinn.AccessManagement.Tests/Data/AuthorizedParties/TestDataAppsInstanceDelegation.cs
+++ b/test/Altinn.AccessManagement.Tests/Data/AuthorizedParties/TestDataAppsInstanceDelegation.cs
@@ -29,6 +29,8 @@ public static class TestDataAppsInstanceDelegation
private static readonly string ListOfDelegationsForAnInstance = "00000000-0000-0000-0000-000000000008";
+ private static readonly string RevokeOneOfExistingDelegations = "00000000-0000-0000-0000-000000000009";
+
///
/// Test case: GET v1/apps/instancedelegation/{resourceId}/{instanceId}/delegationcheck
/// with:
@@ -93,6 +95,39 @@ public static class TestDataAppsInstanceDelegation
}
};
+ public static TheoryData RevokeReadForAppOnlyExistingPolicyRevokeLast() => new()
+ {
+ {
+ PrincipalUtil.GetAccessToken("ttd", "am-devtest-instancedelegation"),
+ GetRequest("Revoke", AppId, InstanceIdParallelExistingPolicy),
+ AppId,
+ InstanceIdParallelExistingPolicy,
+ GetExpectedResponse("Revoke", AppId, InstanceIdParallelExistingPolicy)
+ }
+ };
+
+ public static TheoryData RevokeReadForAppMultipleExistingPolicyRevoke() => new()
+ {
+ {
+ PrincipalUtil.GetAccessToken("ttd", "am-devtest-instancedelegation"),
+ GetRequest("Revoke", AppId, RevokeOneOfExistingDelegations),
+ AppId,
+ RevokeOneOfExistingDelegations,
+ GetExpectedResponse("Revoke", AppId, RevokeOneOfExistingDelegations)
+ }
+ };
+
+ public static TheoryData RevokeReadForAppNoExistingPolicyRevokeLast() => new()
+ {
+ {
+ PrincipalUtil.GetAccessToken("ttd", "am-devtest-instancedelegation"),
+ GetRequest("Revoke", AppId, InstanceIdNewPolicyNoResponceOnWrite),
+ AppId,
+ InstanceIdNewPolicyNoResponceOnWrite,
+ GetExpectedResponse("Revoke", AppId, InstanceIdNewPolicyNoResponceOnWrite)
+ }
+ };
+
///
/// Test case: POST v1/apps/instancedelegation/{resourceId}/{instanceId}
/// with:
diff --git a/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000001/request.json b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000001/request.json
new file mode 100644
index 00000000..07f188ca
--- /dev/null
+++ b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000001/request.json
@@ -0,0 +1,28 @@
+{
+ "from": {
+ "type": "urn:altinn:party:uuid",
+ "value": "0268B99A-5817-4BBF-9B62-D90B16D527EA"
+ },
+ "to": {
+ "type": "urn:altinn:party:uuid",
+ "value": "CE4BA72B-D111-404F-95B5-313FB3847FA1"
+ },
+ "rights": [
+ {
+ "resource": [
+ {
+ "type": "urn:altinn:resource",
+ "value": "app_ttd_am-devtest-instancedelegation"
+ },
+ {
+ "type": "urn:altinn:task",
+ "value": "task_1"
+ }
+ ],
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "read"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000001/response.json b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000001/response.json
new file mode 100644
index 00000000..15d1db1f
--- /dev/null
+++ b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000001/response.json
@@ -0,0 +1,31 @@
+{
+ "from": {
+ "type": "urn:altinn:party:uuid",
+ "value": "0268B99A-5817-4BBF-9B62-D90B16D527EA"
+ },
+ "to": {
+ "type": "urn:altinn:party:uuid",
+ "value": "CE4BA72B-D111-404F-95B5-313FB3847FA1"
+ },
+ "resourceid": "app_ttd_am-devtest-instancedelegation",
+ "instanceid": "00000000-0000-0000-0000-000000000001",
+ "rights": [
+ {
+ "resource": [
+ {
+ "type": "urn:altinn:resource",
+ "value": "app_ttd_am-devtest-instancedelegation"
+ },
+ {
+ "type": "urn:altinn:task",
+ "value": "task_1"
+ }
+ ],
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "read"
+ },
+ "status": "Revoked"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000002/request.json b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000002/request.json
new file mode 100644
index 00000000..07f188ca
--- /dev/null
+++ b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000002/request.json
@@ -0,0 +1,28 @@
+{
+ "from": {
+ "type": "urn:altinn:party:uuid",
+ "value": "0268B99A-5817-4BBF-9B62-D90B16D527EA"
+ },
+ "to": {
+ "type": "urn:altinn:party:uuid",
+ "value": "CE4BA72B-D111-404F-95B5-313FB3847FA1"
+ },
+ "rights": [
+ {
+ "resource": [
+ {
+ "type": "urn:altinn:resource",
+ "value": "app_ttd_am-devtest-instancedelegation"
+ },
+ {
+ "type": "urn:altinn:task",
+ "value": "task_1"
+ }
+ ],
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "read"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000002/response.json b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000002/response.json
new file mode 100644
index 00000000..c4033761
--- /dev/null
+++ b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000002/response.json
@@ -0,0 +1,31 @@
+{
+ "from": {
+ "type": "urn:altinn:party:uuid",
+ "value": "0268B99A-5817-4BBF-9B62-D90B16D527EA"
+ },
+ "to": {
+ "type": "urn:altinn:party:uuid",
+ "value": "CE4BA72B-D111-404F-95B5-313FB3847FA1"
+ },
+ "resourceid": "app_ttd_am-devtest-instancedelegation",
+ "instanceid": "00000000-0000-0000-0000-000000000002",
+ "rights": [
+ {
+ "resource": [
+ {
+ "type": "urn:altinn:resource",
+ "value": "app_ttd_am-devtest-instancedelegation"
+ },
+ {
+ "type": "urn:altinn:task",
+ "value": "task_1"
+ }
+ ],
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "read"
+ },
+ "status": "Revoked"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000009/request.json b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000009/request.json
new file mode 100644
index 00000000..07f188ca
--- /dev/null
+++ b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000009/request.json
@@ -0,0 +1,28 @@
+{
+ "from": {
+ "type": "urn:altinn:party:uuid",
+ "value": "0268B99A-5817-4BBF-9B62-D90B16D527EA"
+ },
+ "to": {
+ "type": "urn:altinn:party:uuid",
+ "value": "CE4BA72B-D111-404F-95B5-313FB3847FA1"
+ },
+ "rights": [
+ {
+ "resource": [
+ {
+ "type": "urn:altinn:resource",
+ "value": "app_ttd_am-devtest-instancedelegation"
+ },
+ {
+ "type": "urn:altinn:task",
+ "value": "task_1"
+ }
+ ],
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "read"
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000009/response.json b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000009/response.json
new file mode 100644
index 00000000..78ff9c06
--- /dev/null
+++ b/test/Altinn.AccessManagement.Tests/Data/Json/AppsInstanceDelegation/Revoke/app_ttd_am-devtest-instancedelegation/00000000-0000-0000-0000-000000000009/response.json
@@ -0,0 +1,31 @@
+{
+ "from": {
+ "type": "urn:altinn:party:uuid",
+ "value": "0268B99A-5817-4BBF-9B62-D90B16D527EA"
+ },
+ "to": {
+ "type": "urn:altinn:party:uuid",
+ "value": "CE4BA72B-D111-404F-95B5-313FB3847FA1"
+ },
+ "resourceid": "app_ttd_am-devtest-instancedelegation",
+ "instanceid": "00000000-0000-0000-0000-000000000009",
+ "rights": [
+ {
+ "resource": [
+ {
+ "type": "urn:altinn:resource",
+ "value": "app_ttd_am-devtest-instancedelegation"
+ },
+ {
+ "type": "urn:altinn:task",
+ "value": "task_1"
+ }
+ ],
+ "action": {
+ "type": "urn:oasis:names:tc:xacml:1.0:action:action-id",
+ "value": "read"
+ },
+ "status": "Revoked"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000001/P/313FB3847FA1/delegationpolicy.xml b/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000001/N/313FB3847FA1/delegationpolicy.xml
similarity index 82%
rename from test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000001/P/313FB3847FA1/delegationpolicy.xml
rename to test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000001/N/313FB3847FA1/delegationpolicy.xml
index fe51d365..988c6d20 100644
--- a/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000001/P/313FB3847FA1/delegationpolicy.xml
+++ b/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000001/N/313FB3847FA1/delegationpolicy.xml
@@ -16,12 +16,8 @@
- ttd
-
-
-
- am-devtest-instancedelegation
-
+ app_ttd_am-devtest-instancedelegation
+
task_1
diff --git a/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000003/N/313FB3847FA1/delegationpolicy.xml b/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000003/N/313FB3847FA1/delegationpolicy.xml
index 3c9036a3..bd807f23 100644
--- a/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000003/N/313FB3847FA1/delegationpolicy.xml
+++ b/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000003/N/313FB3847FA1/delegationpolicy.xml
@@ -16,12 +16,8 @@
- ttd
-
-
-
- am-devtest-instancedelegation
-
+ app_ttd_am-devtest-instancedelegation
+
task_1
diff --git a/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000009/N/313FB3847FA1/delegationpolicy.xml b/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000009/N/313FB3847FA1/delegationpolicy.xml
new file mode 100644
index 00000000..d3d613c7
--- /dev/null
+++ b/test/Altinn.AccessManagement.Tests/Data/blobs/input/Instance/app_ttd_am-devtest-instancedelegation/000000000009/N/313FB3847FA1/delegationpolicy.xml
@@ -0,0 +1,79 @@
+
+
+ Delegation policy containing all delegated rights/actions from urn:altinn:organization:uuid:0268b99a-5817-4bbf-9b62-d90b16d527ea to urn:altinn:person:uuid:ce4ba72b-d111-404f-95b5-313fb3847fa1, for the resource; app_ttd_am-devtest-instancedelegation
+
+
+ Delegation of a right/action from urn:altinn:organization:uuid:0268b99a-5817-4bbf-9b62-d90b16d527ea to urn:altinn:person:uuid:ce4ba72b-d111-404f-95b5-313fb3847fa1, for the resource: app_ttd_am-devtest-instancedelegation, by: urn:altinn:resource:app_ttd_am-devtest-instancedelegation
+
+
+
+
+ ce4ba72b-d111-404f-95b5-313fb3847fa1
+
+
+
+
+
+
+
+ app_ttd_am-devtest-instancedelegation
+
+
+
+ task_1
+
+
+
+ 00000000-0000-0000-0000-000000000009
+
+
+
+
+
+
+
+ read
+
+
+
+
+
+
+
+ Delegation of a right/action from urn:altinn:organization:uuid:0268b99a-5817-4bbf-9b62-d90b16d527ea to urn:altinn:person:uuid:ce4ba72b-d111-404f-95b5-313fb3847fa1, for the resource: app_ttd_am-devtest-instancedelegation, by: urn:altinn:resource:app_ttd_am-devtest-instancedelegation
+
+
+
+
+ ce4ba72b-d111-404f-95b5-313fb3847fa1
+
+
+
+
+
+
+
+ app_ttd_am-devtest-instancedelegation
+
+
+
+ task_1
+
+
+
+ 00000000-0000-0000-0000-000000000009
+
+
+
+
+
+
+
+ sign
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/Altinn.AccessManagement.Tests/Mocks/DelegationMetadataRepositoryMock.cs b/test/Altinn.AccessManagement.Tests/Mocks/DelegationMetadataRepositoryMock.cs
index 971d8951..305d57c2 100644
--- a/test/Altinn.AccessManagement.Tests/Mocks/DelegationMetadataRepositoryMock.cs
+++ b/test/Altinn.AccessManagement.Tests/Mocks/DelegationMetadataRepositoryMock.cs
@@ -87,7 +87,7 @@ public Task GetLastInstanceDelegationChange(InstanceDe
switch (request.Instance)
{
case "00000000-0000-0000-0000-000000000001":
-
+ case "00000000-0000-0000-0000-000000000009":
return Task.FromResult(new InstanceDelegationChange
{
FromUuidType = request.FromType,
diff --git a/test/Altinn.AccessManagement.Tests/Utils/AssertionUtil.cs b/test/Altinn.AccessManagement.Tests/Utils/AssertionUtil.cs
index 671a59ed..9572fec1 100644
--- a/test/Altinn.AccessManagement.Tests/Utils/AssertionUtil.cs
+++ b/test/Altinn.AccessManagement.Tests/Utils/AssertionUtil.cs
@@ -591,7 +591,34 @@ public static void AssertAppsInstanceDelegationResponseDto(AppsInstanceDelegatio
Assert.Equal(expected.To.Value, actual.To.Value);
Assert.Equal(expected.ResourceId, actual.ResourceId);
Assert.Equal(expected.InstanceId, actual.InstanceId);
- AssertionUtil.AssertCollections(expected.Rights.ToList(), actual.Rights.ToList(), AssertRights);
+ AssertionUtil.AssertCollections(expected.Rights.ToList(), actual.Rights.ToList(), AssertDelegationRights);
+ }
+
+ public static void AssertAppsInstanceRevokeResponseDto(AppsInstanceRevokeResponseDto expected, AppsInstanceRevokeResponseDto actual)
+ {
+ Assert.NotNull(actual);
+ Assert.NotNull(expected);
+
+ AssertPartyUrn(expected.From, actual.From);
+ Assert.Equal(expected.To.Value, actual.To.Value);
+ Assert.Equal(expected.ResourceId, actual.ResourceId);
+ Assert.Equal(expected.InstanceId, actual.InstanceId);
+ AssertionUtil.AssertCollections(expected.Rights.ToList(), actual.Rights.ToList(), AssertRevokeRights);
+ }
+
+ ///
+ /// Assert that two have the same property in the same positions.
+ ///
+ /// An instance with the expected values.
+ /// The instance to verify.
+ public static void AssertDelegationRights(RightDelegationResultDto expected, RightDelegationResultDto actual)
+ {
+ Assert.NotNull(actual);
+ Assert.NotNull(expected);
+
+ AssertActionUrn(expected.Action, actual.Action);
+ Assert.Equal(expected.Status, actual.Status);
+ AssertionUtil.AssertCollections(expected.Resource.ToList(), actual.Resource.ToList(), AssertResource);
}
///
@@ -599,7 +626,7 @@ public static void AssertAppsInstanceDelegationResponseDto(AppsInstanceDelegatio
///
/// An instance with the expected values.
/// The instance to verify.
- public static void AssertRights(RightDelegationResultDto expected, RightDelegationResultDto actual)
+ public static void AssertRevokeRights(RightRevokeResultDto expected, RightRevokeResultDto actual)
{
Assert.NotNull(actual);
Assert.NotNull(expected);