diff --git a/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Api.Enduser/Controllers/ConnectionsController.cs b/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Api.Enduser/Controllers/ConnectionsController.cs index dea0c8f6f..9c969e181 100644 --- a/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Api.Enduser/Controllers/ConnectionsController.cs +++ b/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Api.Enduser/Controllers/ConnectionsController.cs @@ -903,9 +903,6 @@ public async Task UpdateInstanceRights( [FromBody] RightKeyListDto updateDto, CancellationToken cancellationToken = default) { - return NotFound(); - - /* ToDo: Implement instance support in connection service and uncomment code below when ready. Currently we return the same result as UpdateResourceRights, but with the intention to include instance information in the result once supported in connection service. var byId = AuthenticationHelper.GetPartyUuid(HttpContext); var fromEntity = await EntityService.GetEntity(party, cancellationToken); var toEntity = await EntityService.GetEntity(to, cancellationToken); @@ -928,7 +925,6 @@ public async Task UpdateInstanceRights( } return Ok(); - */ } /// @@ -950,10 +946,6 @@ public async Task RemoveInstance( [Required][FromQuery(Name = "instance")] string instance, CancellationToken cancellationToken = default) { - return NotFound(); - - /* ToDo: Implement instance support in connection service and uncomment code below when ready. Currently we return the same result as RemoveResources, but with the intention to include instance information in the result once supported in connection service. - var byId = AuthenticationHelper.GetPartyUuid(HttpContext); var problem = await ConnectionService.RemoveInstance(from, to, resource, instance, ConfigureConnections, cancellationToken); if (problem is { }) { @@ -961,7 +953,6 @@ public async Task RemoveInstance( } return NoContent(); - */ } /// diff --git a/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Core/Services/SingleRightsService.cs b/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Core/Services/SingleRightsService.cs index 71771df1b..ea7198c04 100644 --- a/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Core/Services/SingleRightsService.cs +++ b/src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Core/Services/SingleRightsService.cs @@ -146,7 +146,7 @@ public async Task> TryWriteDelegationPolicyRules(Entity from, Entity public async Task> TryWriteInstanceDelegationPolicyRules(Entity from, Entity to, Resource resource, string instanceId, List ruleKeys, Entity performedBy, bool ignoreExistingPolicy = false, CancellationToken cancellationToken = default) { - var instanceRules = await GenerateInstanceRules(resource, instanceId, ruleKeys, cancellationToken); + var instanceRules = await GenerateInstanceRules(from, to, resource, instanceId, ruleKeys, performedBy, cancellationToken); var instanceRight = new InstanceRight { @@ -208,7 +208,7 @@ private async Task> GenerateRules(Entity from, Entity to, Reso return rules; } - private async Task> GenerateInstanceRules(Resource resource, string instanceId, List ruleKeys, CancellationToken cancellationToken = default) + private async Task> GenerateInstanceRules(Entity from, Entity to, Resource resource, string instanceId, List ruleKeys, Entity performedBy, CancellationToken cancellationToken = default) { List instanceRules = []; diff --git a/src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/ConnectionService.cs b/src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/ConnectionService.cs index d71f9d96b..6ac7d623c 100644 --- a/src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/ConnectionService.cs +++ b/src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/ConnectionService.cs @@ -1,6 +1,7 @@ using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Text; +using System.Linq; using Altinn.AccessManagement.Core.Clients.Interfaces; using Altinn.AccessManagement.Core.Enums.ResourceRegistry; using Altinn.AccessManagement.Core.Errors; @@ -330,13 +331,14 @@ private async Task> MapConnectionsToInstancePermissi public async Task> UpdateResource(Entity from, Entity to, Resource resourceObj, IEnumerable rightKeys, Entity by, Action configureConnection = null, CancellationToken cancellationToken = default) { - var canDelegate = await ResourceDelegationCheck(by.Id, from.Id, resourceObj?.RefId, ConfigureConnections, cancellationToken: cancellationToken); + var canDelegate = await ResourceDelegationCheck(by.Id, from.Id, resourceObj?.RefId, configureConnection, cancellationToken: cancellationToken); if (canDelegate.IsProblem) { return canDelegate.Problem; } - foreach (var ruleKey in rightKeys) + var keys = rightKeys.ToList(); + foreach (var ruleKey in keys) { if (!canDelegate.Value.Rights.Any(a => a.Right.Key == ruleKey && a.Result)) { @@ -344,7 +346,7 @@ public async Task> UpdateResource(Entity from, Entity to, Resource } } - List result = await singleRightsService.TryWriteDelegationPolicyRules(from, to, resourceObj, rightKeys.ToList(), by, ignoreExistingPolicy: true, cancellationToken: cancellationToken); + List result = await singleRightsService.TryWriteDelegationPolicyRules(from, to, resourceObj, keys, by, ignoreExistingPolicy: true, cancellationToken: cancellationToken); if (!result.All(r => r.CreatedSuccessfully)) { @@ -356,8 +358,15 @@ public async Task> UpdateResource(Entity from, Entity to, Resource public async Task RemoveResource(Guid fromId, Guid toId, string resource, Action configureConnection = null, CancellationToken cancellationToken = default) { - var resourceObj = await dbContext.Resources.AsNoTracking().FirstOrDefaultAsync(t => t.RefId == resource); - return await RemoveResource(fromId, toId, (Guid)resourceObj.Id, configureConnection, cancellationToken); + var resourceObj = await dbContext.Resources.AsNoTracking().FirstOrDefaultAsync(t => t.RefId == resource, cancellationToken); + if (resourceObj == null) + { + return ValidationComposer.Validate( + ResourceValidation.ResourceExists(resourceObj, resource) + ); + } + + return await RemoveResource(fromId, toId, resourceObj.Id, configureConnection, cancellationToken); } public async Task RemoveResource(Guid fromId, Guid toId, Guid resourceId, Action configureConnection = null, CancellationToken cancellationToken = default) @@ -1010,13 +1019,12 @@ public async Task> InstanceDelegationCheck(Guid authent List rights = DelegationCheckHelper.DecomposePolicy(policy, resource); var packages = await CheckPackageForResource(party, authenticatedUserUuid, null, configureConnection, cancellationToken); - bool isMainAdminForFrom = packages.Value.Any(p => p.Result && p.Package.Id == PackageConstants.MainAdministrator.Id); + bool isMainAdminForFrom = packages.Value.Any(p => p.Result == true && p.Package.Id == PackageConstants.MainAdministrator.Id); var roles = await RoleDelegationCheck(party, authenticatedUserUuid, isMainAdminForFrom, cancellationToken); var resources = await GetResourceRights(party, authenticatedUserUuid, resourceDto.Id, null, cancellationToken); - var instances = await GetInstanceRights(party, authenticatedUserUuid, resourceDto.Id, instanceId, RoleConstants.Rightholder, cancellationToken); - ProcessTheAccessToTheRightKeys(rights, packages.Value, roles.Value, resources, instances); + ProcessTheAccessToTheRightKeys(rights, packages.Value, roles.Value, resources); // Map to result IEnumerable checkRights = await MapFromInternalToExternalRights(rights, resource, accessListMode, fromParty, rightKeys, isResourceDelegable, isMaskinPortenSchema, cancellationToken); @@ -1180,13 +1188,14 @@ private void ProcessResourceAllowAccessReasons(List resourceRig /// public async Task> AddResource(Entity from, Entity to, Resource resourceObj, RightKeyListDto rightKeys, Entity by, Action configureConnection = null, CancellationToken cancellationToken = default) { - var canDelegate = await ResourceDelegationCheck(by.Id, from.Id, resourceObj?.RefId, ConfigureConnections, cancellationToken: cancellationToken); + var canDelegate = await ResourceDelegationCheck(by.Id, from.Id, resourceObj?.RefId, configureConnection, cancellationToken: cancellationToken); if (canDelegate.IsProblem) { return canDelegate.Problem; } - foreach (var rightKey in rightKeys.DirectRightKeys) + var keys = rightKeys.DirectRightKeys.ToList(); + foreach (var rightKey in keys) { if (!canDelegate.Value.Rights.Any(a => a.Right.Key == rightKey && a.Result)) { @@ -1200,7 +1209,7 @@ public async Task> AddResource(Entity from, Entity to, Resource res return Problems.MissingConnection; } - List result = await singleRightsService.TryWriteDelegationPolicyRules(from, to, resourceObj, rightKeys.DirectRightKeys.ToList(), by, ignoreExistingPolicy: false, cancellationToken: cancellationToken); + List result = await singleRightsService.TryWriteDelegationPolicyRules(from, to, resourceObj, keys, by, ignoreExistingPolicy: false, cancellationToken: cancellationToken); if (!result.All(r => r.CreatedSuccessfully)) { @@ -1213,15 +1222,19 @@ public async Task> AddResource(Entity from, Entity to, Resource res /// public async Task> AddInstance(Entity from, Entity to, Resource resourceObj, string instanceId, RightKeyListDto rightKeys, Entity by, Action configureConnection = null, CancellationToken cancellationToken = default) { - var canDelegate = await ResourceDelegationCheck(by.Id, from.Id, resourceObj?.RefId, ConfigureConnections, cancellationToken: cancellationToken); + var canDelegate = await ResourceDelegationCheck(by.Id, from.Id, resourceObj?.RefId, configureConnection, cancellationToken: cancellationToken); if (canDelegate.IsProblem) { return canDelegate.Problem; } - if (rightKeys.DirectRightKeys.Any(rightKey => !canDelegate.Value.Rights.Any(a => a.Right.Key == rightKey && a.Result))) + var keys = rightKeys.DirectRightKeys.ToList(); + foreach (var rightKey in keys) { - return Problems.NotAuthorizedForDelegationRequest; + if (!canDelegate.Value.Rights.Any(a => a.Right.Key == rightKey && a.Result)) + { + return Problems.NotAuthorizedForDelegationRequest; + } } var connection = await Get(from.Id, from.Id, to.Id, configureConnections: configureConnection, cancellationToken: cancellationToken); @@ -1230,7 +1243,34 @@ public async Task> AddInstance(Entity from, Entity to, Resource res return Problems.MissingConnection; } - List result = await singleRightsService.TryWriteInstanceDelegationPolicyRules(from, to, resourceObj, instanceId, rightKeys.DirectRightKeys.ToList(), by, ignoreExistingPolicy: false, cancellationToken: cancellationToken); + List result = await singleRightsService.TryWriteInstanceDelegationPolicyRules(from, to, resourceObj, instanceId, keys, by, ignoreExistingPolicy: false, cancellationToken: cancellationToken); + + if (!result.All(r => r.CreatedSuccessfully)) + { + return Problems.DelegationPolicyRuleWriteFailed; + } + + return true; + } + + public async Task> UpdateInstance(Entity from, Entity to, Resource resourceObj, string instanceId, IEnumerable rightKeys, Entity by, Action configureConnection = null, CancellationToken cancellationToken = default) + { + var canDelegate = await InstanceDelegationCheck(by.Id, from.Id, resourceObj?.RefId, instanceId, ConfigureConnections, cancellationToken: cancellationToken); + if (canDelegate.IsProblem) + { + return canDelegate.Problem; + } + + var keys = rightKeys.ToList(); + foreach (var rightKey in keys) + { + if (!canDelegate.Value.Rights.Any(a => a.Right.Key == rightKey && a.Result)) + { + return Problems.NotAuthorizedForDelegationRequest; + } + } + + List result = await singleRightsService.TryWriteInstanceDelegationPolicyRules(from, to, resourceObj, instanceId, keys, by, ignoreExistingPolicy: true, cancellationToken: cancellationToken); if (!result.All(r => r.CreatedSuccessfully)) { @@ -1240,6 +1280,65 @@ public async Task> AddInstance(Entity from, Entity to, Resource res return true; } + public async Task RemoveInstance(Guid fromId, Guid toId, string resource, string instanceId, Action configureConnection = null, CancellationToken cancellationToken = default) + { + var resourceObj = await dbContext.Resources.AsNoTracking().FirstOrDefaultAsync(t => t.RefId == resource, cancellationToken); + if (resourceObj == null) + { + return ValidationComposer.Validate( + ResourceValidation.ResourceExists(resourceObj, resource) + ); + } + + var options = new ConnectionOptions(configureConnection); + var (from, to) = await GetFromAndToEntities(fromId, toId, cancellationToken); + var problem = ValidateWriteOpInput(from, to, options); + if (problem is { }) + { + return problem; + } + + var assignment = await dbContext.Assignments + .AsNoTracking() + .Include(a => a.From) + .Include(a => a.To) + .Where(a => a.FromId == fromId) + .Where(a => a.ToId == toId) + .Where(a => a.RoleId == RoleConstants.Rightholder) + .FirstOrDefaultAsync(cancellationToken); + + if (assignment is null) + { + return null; + } + + problem = ValidateWriteOpInput(assignment.From, assignment.To, options); + if (problem is { }) + { + return problem; + } + + var existingAssignmentInstance = await dbContext.AssignmentInstances + .AsTracking() + .Where(a => a.AssignmentId == assignment.Id) + .Where(a => a.ResourceId == resourceObj.Id) + .Where(a => a.InstanceId == instanceId) + .FirstOrDefaultAsync(cancellationToken); + + if (existingAssignmentInstance is null) + { + return null; + } + + var newVersion = await singleRightsService.ClearPolicyRules(existingAssignmentInstance.PolicyPath, existingAssignmentInstance.PolicyVersion, cancellationToken); + existingAssignmentInstance.PolicyVersion = newVersion; + + dbContext.Remove(existingAssignmentInstance); + await dbContext.SaveChangesAsync(cancellationToken); + + return null; + } + private void ProcessRoleAllowAccessReasons(List rolesAllowAccess, List permisions) { if (rolesAllowAccess.Count > 0) @@ -2017,7 +2116,6 @@ private async Task> GetInstanceRights(Guid? fromId, Guid? PolicyVersion = t.PolicyVersion, Reason = AccessReasonFlag.KeyRole }); - var query = direct .Union(keyRoleResult); diff --git a/src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/Contracts/IConnectionService.cs b/src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/Contracts/IConnectionService.cs index ac4d02660..7f2929d27 100644 --- a/src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/Contracts/IConnectionService.cs +++ b/src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/Contracts/IConnectionService.cs @@ -350,4 +350,47 @@ public interface IConnectionService /// A task that represents the asynchronous operation. The task result contains a Result object indicating whether /// the instance delegation was successfully added. Task> AddInstance(Entity from, Entity to, Resource resourceObj, string instanceId, RightKeyListDto rightKeys, Entity by, Action configureConnection = null, CancellationToken cancellationToken = default); + + /// + /// Updates (replaces) a delegation to a resource instance between two entities with the specified action keys. If not all actions are possible, nothing is performed and a Problem is returned. + /// + /// The source entity from which the delegation originates. + /// The target entity to which the delegation is granted. + /// The resource to associate between the source and target entities. + /// The instance identifier for the resource instance. + /// A list of rule keys that define the permissions or actions allowed for the resource instance. + /// The entity performing the operation. Used for auditing and authorization purposes. + /// An optional delegate to configure connection options for the operation. If null, default connection settings are used. + /// A cancellation token that can be used to cancel the asynchronous operation. + /// A task that represents the asynchronous operation. The task result contains a Result object indicating whether + /// the instance delegation was successfully updated. + Task> UpdateInstance(Entity from, Entity to, Resource resourceObj, string instanceId, IEnumerable rightKeys, Entity by, Action configureConnection = null, CancellationToken cancellationToken = default); + + /// + /// Removes a resource instance (by resource string and instance id) from assignment based on a specific role between two entities. + /// + /// ID of the entity from which the assignment originates. + /// ID of the entity to which the assignment was made. + /// Resource reference id + /// Instance identifier + /// ConnectionOptions + /// + /// Token to monitor for cancellation requests. + /// + /// + /// A task whose result is either null or a . + /// + /// + /// + /// null if the instance assignment was successfully removed, or if no matching assignment was found for the specified parameters. + /// + /// + /// + /// + /// A describing any validation errors that prevented the removal of the instance assignment. + /// + /// + /// + /// + Task RemoveInstance(Guid fromId, Guid toId, string resource, string instanceId, Action configureConnection = null, CancellationToken cancellationToken = default); }