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);