Skip to content

Commit b9dead2

Browse files
TheTechArchRune T. LarsenCopilot
authored
Feature/updated delegationcheck consent (#2583)
* First iteration of updated delegation check * seeding * Updated for testing * Removed moq and seeds data * Removed var * Added more unit tests for consents * Merge * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Namespace fix * Another try to rename * Fixed unused namespace * Namespace * Namspaces * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Fixed so code rabit understand it is correct. It was correct already since ToString = id * File fix * Code smell * Code smell * cleaned up namespaces --------- Co-authored-by: Rune T. Larsen <rune.tommeras-larsen@digdir.no> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
1 parent 86d0dab commit b9dead2

28 files changed

+933
-162
lines changed

src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Api.Enduser/Controllers/ConnectionsController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,7 @@ public async Task<IActionResult> CheckResource(
673673
Guid authenticatedUserUuid = AuthenticationHelper.GetPartyUuid(HttpContext);
674674
string languageCode = this.GetLanguageCode();
675675

676-
var result = await ConnectionService.ResourceDelegationCheck(authenticatedUserUuid, party, resource, ConfigureConnections, languageCode, cancellationToken);
676+
var result = await ConnectionService.ResourceDelegationCheck(authenticatedUserUuid, party, resource, ConfigureConnections, languageCode, cancellationToken: cancellationToken);
677677
if (result.IsProblem)
678678
{
679679
if (result.Problem.Equals(Core.Errors.Problems.InvalidResource))

src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Core/Constants/AltinnXacmlConstants.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,11 @@ public static class MatchAttributeIdentifiers
200200
/// Gets the value action id for request consent
201201
/// </summary>
202202
public const string RequestconsentAction = "requestconsent";
203+
204+
/// <summary>
205+
/// Delegation attribute match identifier used for user controlled delegation
206+
/// </summary>
207+
public const string Delegation = "urn:altinn:resource:delegation";
203208
}
204209

205210
/// <summary>
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace Altinn.AccessManagement.Core.Models
8+
{
9+
/// <summary>
10+
/// Result of a consent delegation check, containing the actions the user is authorized to delegate.
11+
/// </summary>
12+
public class ConsentDelegationCheckResult
13+
{
14+
/// <summary>
15+
/// Gets or sets a value indicating whether the delegation check was successful.
16+
/// </summary>
17+
public bool IsSuccess { get; set; }
18+
19+
/// <summary>
20+
/// Gets or sets the list of action URNs (e.g. "urn:oasis:names:tc:xacml:1.0:action:action-id:read")
21+
/// that the authenticated user is authorized to delegate on behalf of the party.
22+
/// </summary>
23+
public IEnumerable<string> DelegatableActions { get; set; } = [];
24+
}
25+
}

src/apps/Altinn.AccessManagement/src/Altinn.AccessManagement.Core/Services/ConsentService.cs

Lines changed: 39 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
using System;
2-
using System.Diagnostics;
1+
using System.Diagnostics;
32
using System.Diagnostics.Metrics;
43
using System.Text;
54
using Altinn.AccessManagement.Core.Clients.Interfaces;
65
using Altinn.AccessManagement.Core.Configuration;
76
using Altinn.AccessManagement.Core.Constants;
8-
using Altinn.AccessManagement.Core.Enums;
97
using Altinn.AccessManagement.Core.Errors;
108
using Altinn.AccessManagement.Core.Models;
119
using Altinn.AccessManagement.Core.Models.Consent;
@@ -16,11 +14,11 @@
1614
using Altinn.AccessManagement.Core.Services.Interfaces;
1715
using Altinn.Authorization.Api.Contracts.Register;
1816
using Altinn.Authorization.ProblemDetails;
19-
using Altinn.Platform.Profile.Models;
2017
using Altinn.Platform.Register.Models;
2118
using Microsoft.Extensions.Caching.Memory;
2219
using Microsoft.Extensions.Logging;
2320
using Microsoft.Extensions.Options;
21+
using static Altinn.Authorization.ABAC.Constants.XacmlConstants;
2422

2523
namespace Altinn.AccessManagement.Core.Services
2624
{
@@ -30,11 +28,10 @@ namespace Altinn.AccessManagement.Core.Services
3028
/// <remarks>
3129
/// Service responsible for consent functionality
3230
/// </remarks>
33-
public class ConsentService: IConsent
31+
public class ConsentService : IConsent
3432
{
3533
private readonly IConsentRepository _consentRepository;
36-
private readonly IPartiesClient _partiesClient ;
37-
private readonly ISingleRightsService _singleRightsService;
34+
private readonly IPartiesClient _partiesClient;
3835
private readonly IResourceRegistryClient _resourceRegistryClient;
3936
private readonly IAMPartyService _ampartyService;
4037
private readonly IMemoryCache _memoryCache;
@@ -43,6 +40,7 @@ public class ConsentService: IConsent
4340
private readonly GeneralSettings _generalSettings;
4441
private readonly IAltinn2ConsentClient _altinn2ConsentClient;
4542
private readonly ILogger<ConsentService> _logger;
43+
private readonly IConsentDelegationCheckService _consentDelegationCheckService;
4644

4745
// histograms for sub-step durations (seconds)
4846
private readonly Histogram<double>? _getA2Histogram;
@@ -55,30 +53,30 @@ public class ConsentService: IConsent
5553
private const string ResourceParam = "Resource";
5654

5755
public ConsentService(
58-
ILogger<ConsentService> logger,
59-
IConsentRepository consentRepository,
60-
IAltinn2ConsentClient altinn2ConsentClient,
61-
IPartiesClient partiesClient,
62-
ISingleRightsService singleRightsService,
63-
IResourceRegistryClient resourceRegistryClient,
64-
IAMPartyService ampartyService,
65-
IMemoryCache memoryCache,
66-
IProfileClient profileClient,
67-
TimeProvider timeProvider,
56+
ILogger<ConsentService> logger,
57+
IConsentRepository consentRepository,
58+
IAltinn2ConsentClient altinn2ConsentClient,
59+
IPartiesClient partiesClient,
60+
IResourceRegistryClient resourceRegistryClient,
61+
IAMPartyService ampartyService,
62+
IMemoryCache memoryCache,
63+
IProfileClient profileClient,
64+
TimeProvider timeProvider,
6865
IOptions<GeneralSettings> generalSettings,
69-
IMeterFactory meterFactory)
66+
IMeterFactory meterFactory,
67+
IConsentDelegationCheckService consentDelegationCheckService)
7068
{
7169
_logger = logger;
7270
_consentRepository = consentRepository;
7371
_altinn2ConsentClient = altinn2ConsentClient;
7472
_partiesClient = partiesClient;
75-
_singleRightsService = singleRightsService;
7673
_resourceRegistryClient = resourceRegistryClient;
7774
_ampartyService = ampartyService;
7875
_memoryCache = memoryCache;
7976
_profileClient = profileClient;
8077
_timeProvider = timeProvider;
8178
_generalSettings = generalSettings.Value;
79+
_consentDelegationCheckService = consentDelegationCheckService;
8280

8381
var meter = meterFactory.Create("Altinn.AccessManagement.ConsentMigration");
8482
_getA2Histogram = meter.CreateHistogram<double>("consent_migration_get_a2_duration_seconds", unit: "s", description: "Seconds to get a single consent from Altinn2");
@@ -739,55 +737,41 @@ private async Task<bool> AuthorizeUserForConsentRequest(Guid userUuid, ConsentRe
739737

740738
private async Task<bool> AuthorizeForConsentRight(Party party, NewUserProfile profile, ConsentRight consentRight)
741739
{
742-
DelegationCheckResponse response = await GetDelegatableRightsForConsentResource(party, profile, consentRight);
743-
744-
if (response.RightDelegationCheckResults != null)
740+
if (consentRight.Resource == null || consentRight.Resource.Count != 1)
745741
{
746-
foreach (string action in consentRight.Action)
747-
{
748-
bool actionMatch = false;
749-
foreach (RightDelegationCheckResult result in response.RightDelegationCheckResults)
750-
{
751-
if (result.Action.Value.Equals(action, StringComparison.InvariantCultureIgnoreCase) && result.Status.Equals(DelegableStatus.Delegable))
752-
{
753-
actionMatch = true;
754-
break;
755-
}
756-
}
757-
758-
if (!actionMatch)
759-
{
760-
return false;
761-
}
762-
}
742+
return false;
763743
}
764-
else
744+
745+
if (profile.UserUuid == null || party.PartyUuid == null || party.PartyUuid == default)
765746
{
766747
return false;
767748
}
768749

769-
return true;
770-
}
750+
// A ConsentRight should only have one resource. Currently no support for subresources as part of a consent request.
751+
ConsentResourceAttribute resource = consentRight.Resource[0];
771752

772-
private async Task<DelegationCheckResponse> GetDelegatableRightsForConsentResource(Party party, NewUserProfile profile, ConsentRight consentRight)
773-
{
774-
RightsDelegationCheckRequest rightsDelegationCheckRequest = new()
775-
{
776-
From = [new AttributeMatch { Id = AltinnXacmlConstants.MatchAttributeIdentifiers.PartyAttribute, Value = party.PartyId.ToString() }]
777-
};
753+
ConsentDelegationCheckResult checkResult = await _consentDelegationCheckService.CheckDelegatableRights(
754+
authenticatedUserUuid: profile.UserUuid.Value,
755+
partyUuid: party.PartyUuid.Value,
756+
resourceIdentifier: resource.Value);
778757

779-
if (consentRight.Resource != null && consentRight.Resource.Count == 1)
758+
if (!checkResult.IsSuccess)
780759
{
781-
// A ConsentRight Should only have one resource. Currently no support for subresources as part of a consent request.
782-
ConsentResourceAttribute resource = consentRight.Resource[0];
783-
rightsDelegationCheckRequest.Resource = [new AttributeMatch { Id = resource.Type, Value = resource.Value }];
760+
return false;
784761
}
785-
else
762+
763+
foreach (string action in consentRight.Action)
786764
{
787-
return null;
765+
bool actionMatch = checkResult.DelegatableActions.Any(a =>
766+
a.Replace(MatchAttributeIdentifiers.ActionId + ":", string.Empty).Equals(action, StringComparison.OrdinalIgnoreCase));
767+
768+
if (!actionMatch)
769+
{
770+
return false;
771+
}
788772
}
789773

790-
return await _singleRightsService.RightsDelegationCheck(profile.UserId, 3, rightsDelegationCheckRequest);
774+
return true;
791775
}
792776

793777
/// <summary>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using Altinn.AccessManagement.Core.Models;
2+
3+
namespace Altinn.AccessManagement.Core.Services.Interfaces;
4+
5+
/// <summary>
6+
/// Service for performing delegation checks for consent scenarios.
7+
/// Uses the new delegation check that supports access packages, roles, and direct resource rights.
8+
/// </summary>
9+
public interface IConsentDelegationCheckService
10+
{
11+
/// <summary>
12+
/// Checks which rights the authenticated user can delegate on behalf of the specified party for a given resource.
13+
/// The resource's Delegable flag is ignored since consent only needs to verify user access, not re-delegation capability.
14+
/// </summary>
15+
/// <param name="authenticatedUserUuid">The UUID of the authenticated user performing the delegation.</param>
16+
/// <param name="partyUuid">The UUID of the party on whose behalf the delegation check is performed.</param>
17+
/// <param name="resourceIdentifier">The resource identifier (e.g. "skd_samtykketest").</param>
18+
/// <param name="cancellationToken">Cancellation token.</param>
19+
/// <returns>A <see cref="ConsentDelegationCheckResult"/> indicating which actions the user can delegate.</returns>
20+
Task<ConsentDelegationCheckResult> CheckDelegatableRights(Guid authenticatedUserUuid, Guid partyUuid, string resourceIdentifier, CancellationToken cancellationToken = default);
21+
}

src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Extensions/ServiceCollectionExtensions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ public static IServiceCollection AddAccessMgmtCore(this IServiceCollection servi
4747
services.AddScoped<IClientDelegationService, ClientDelegationService>();
4848
services.AddScoped<IRequestService, RequestService>();
4949
services.AddScoped<IAuthorizedPartiesService, AuthorizedPartiesServiceEf>();
50+
services.AddScoped<IConsentDelegationCheckService, ConsentDelegationCheckService>();
5051

5152
services.AddScoped<IAuthorizationScopeProvider, DefaultAuthorizationScopeProvider>();
5253
services.AddScoped<IAuthorizationHandler, ScopeConditionAuthorizationHandler>();

src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/ConnectionService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -902,7 +902,7 @@ public async Task<Result<IEnumerable<RoleDtoCheck>>> RoleDelegationCheck(Guid pa
902902
}
903903

904904
/// <inheritdoc />
905-
public async Task<Result<ResourceCheckDto>> ResourceDelegationCheck(Guid authenticatedUserUuid, Guid party, string resource, Action<ConnectionOptions> configureConnection = null, string languageCode = "nb", CancellationToken cancellationToken = default)
905+
public async Task<Result<ResourceCheckDto>> ResourceDelegationCheck(Guid authenticatedUserUuid, Guid party, string resource, Action<ConnectionOptions> configureConnection = null, string languageCode = "nb", bool ignoreDelegableFlag = false, CancellationToken cancellationToken = default)
906906
{
907907
// Get fromParty
908908
MinimalParty fromParty = await partyService.GetByUid(party, cancellationToken);
@@ -945,7 +945,7 @@ public async Task<Result<ResourceCheckDto>> ResourceDelegationCheck(Guid authent
945945
}
946946

947947
ResourceAccessListMode accessListMode = resourceMetadata.AccessListMode;
948-
bool isResourceDelegable = resourceMetadata.Delegable;
948+
bool isResourceDelegable = ignoreDelegableFlag || resourceMetadata.Delegable;
949949

950950
// Decompose policy into resource/tasks
951951
List<Models.Right> rights = DelegationCheckHelper.DecomposePolicy(policy, resource);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using Altinn.AccessManagement.Core.Models;
2+
using Altinn.AccessManagement.Core.Services.Interfaces;
3+
using Altinn.AccessMgmt.Core.Services.Contracts;
4+
5+
namespace Altinn.AccessMgmt.Core.Services;
6+
7+
/// <summary>
8+
/// Adapter that implements <see cref="IConsentDelegationCheckService"/> using the new
9+
/// <see cref="IConnectionService.ResourceDelegationCheck"/> method which supports access packages.
10+
/// </summary>
11+
public class ConsentDelegationCheckService(IConnectionService connectionService) : IConsentDelegationCheckService
12+
{
13+
/// <inheritdoc />
14+
public async Task<ConsentDelegationCheckResult> CheckDelegatableRights(Guid authenticatedUserUuid, Guid partyUuid, string resourceIdentifier, CancellationToken cancellationToken = default)
15+
{
16+
var result = await connectionService.ResourceDelegationCheck(
17+
authenticatedUserUuid: authenticatedUserUuid,
18+
party: partyUuid,
19+
resource: resourceIdentifier,
20+
ignoreDelegableFlag: true,
21+
cancellationToken: cancellationToken);
22+
23+
if (result.IsProblem)
24+
{
25+
return new ConsentDelegationCheckResult { IsSuccess = false };
26+
}
27+
28+
var delegatableActions = result.Value.Rights
29+
.Where(r => r.Result && r.Right.Action is not null)
30+
.Select(r => r.Right.Action.Urn())
31+
.ToList();
32+
33+
return new ConsentDelegationCheckResult
34+
{
35+
IsSuccess = true,
36+
DelegatableActions = delegatableActions
37+
};
38+
}
39+
}

src/apps/Altinn.AccessManagement/src/Altinn.AccessMgmt.Core/Services/Contracts/IConnectionService.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -186,13 +186,14 @@ public interface IConnectionService
186186
/// Method to check if a resource is delegable by an authenticated user on behalf of a party
187187
/// </summary>
188188
/// <param name="authenticatedUserUuid">The authenticated user</param>
189-
/// <param name="party">The party performing the checl on behalf of</param>
189+
/// <param name="party">The party performing the check on behalf of</param>
190190
/// <param name="resource">The resource id to check</param>
191191
/// <param name="configureConnection">ConnectionOptions</param>
192192
/// <param name="languageCode">the requested language code fallback "nb"</param>
193+
/// <param name="ignoreDelegableFlag">When true, the resource's Delegable flag is ignored and only the user's access is checked. Used for consent scenarios where re-delegation should not be allowed but access verification is still needed.</param>
193194
/// <param name="cancellationToken">The <see cref="CancellationToken"/></param>
194-
/// <returns>The result on all the resource/action that is delegable on the resource and a reason behinf if the user can or can not delegate a given action</returns>
195-
Task<Result<ResourceCheckDto>> ResourceDelegationCheck(Guid authenticatedUserUuid, Guid party, string resource, Action<ConnectionOptions> configureConnection = null, string languageCode = "nb", CancellationToken cancellationToken = default);
195+
/// <returns>The result on all the resource/action that is delegable on the resource and a reason behind if the user can or can not delegate a given action</returns>
196+
Task<Result<ResourceCheckDto>> ResourceDelegationCheck(Guid authenticatedUserUuid, Guid party, string resource, Action<ConnectionOptions> configureConnection = null, string languageCode = "nb", bool ignoreDelegableFlag = false, CancellationToken cancellationToken = default);
196197

197198
/// <summary>
198199
/// Method to check if a resource instance is delegable by an authenticated user on behalf of a party

src/apps/Altinn.AccessManagement/test/AccessMgmt.Tests/Controllers/Altinn2RightsControllerTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using System.Net;
1+
using System.Net;
22
using System.Net.Http.Headers;
33
using System.Net.Mime;
44
using System.Text;

0 commit comments

Comments
 (0)