Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend authorized parties with instance delegations #856

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ public AuthorizedParty(SblAuthorizedParty sblAuthorizedParty, bool includeSubuni
/// </summary>
public bool OnlyHierarchyElementWithNoAccess { get; set; }

/// <summary>
/// Gets or sets a collection of all Authorized Instances
/// </summary>
public List<Resource> AuthorizedInstances { get; set; } = [];

/// <summary>
/// Gets or sets a collection of all resource identifier the authorized subject has some access to on behalf of this party
/// </summary>
Expand Down Expand Up @@ -158,4 +163,20 @@ private static string MapAppIdToResourceId(string altinnAppId)

return altinnAppId;
}

/// <summary>
/// Composite Key instances
/// </summary>
public class Resource
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Burde ikke denne hete noe med Instance for ikke å blande den med AuthorizedResources som er av type string

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mm, skal rename den

Copy link
Contributor Author

@andreasisnes andreasisnes Oct 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

blir kanskje ikke så synelig i File Changes PRen. Men klassen er definert inne i klassen AuthorizedParty. Så for å referere til klassen Resource utenfor klassen AuthorizedParty så må man bruke AuthorizedParty.Resource. Uansett, så endret jeg navnet til AuthorizedResource. Så det blir nå AuthorizedParty.AuthorizedResource.

{
/// <summary>
/// Resource ID
/// </summary>
public string ResourceId { get; set; }

/// <summary>
/// Instance ID
/// </summary>
public string InstanceId { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@
/// <returns>All the last InstanceDelegationChange records stored in the database corresponding to the request</returns>
Task<List<InstanceDelegationChange>> GetAllLatestInstanceDelegationChanges(InstanceDelegationSource source, string resourceID, string instanceID, CancellationToken cancellationToken = default);

/// <summary>
/// Returns all received instance delegations from db
/// </summary>
/// <param name="toUuid">party uuid that received the delegation</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/></param>
/// <returns></returns>
Task<List<InstanceDelegationChange>> GetAllCurrentReceivedInstanceDelegations(List<Guid> toUuid, CancellationToken cancellationToken = default);

/// <summary>
/// Returns the last change from db to fetch the current policy version and path to policy file
/// </summary>
Expand Down Expand Up @@ -56,7 +64,7 @@
/// <param name="toUuid">The receiver uuid</param>
/// <param name="toUuidType">The type of uuid the reciver is</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/></param>
Task<DelegationChange> GetCurrentDelegationChange(ResourceAttributeMatchType resourceMatchType, string resourceId, int offeredByPartyId, int? coveredByPartyId, int? coveredByUserId, Guid? toUuid, UuidType toUuidType, CancellationToken cancellationToken = default);

Check warning on line 67 in src/Altinn.AccessManagement.Core/Repositories/Interfaces/IDelegationMetadataRepository.cs

View workflow job for this annotation

GitHub Actions / Analyze

Method has 8 parameters, which is greater than the 7 authorized. (https://rules.sonarsource.com/csharp/RSPEC-107)

/// <summary>
/// Gets all the delegation change records matching the filter values for a complete changelog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
private static bool ValidateAndGetSignificantResourcePartsFromResource(IEnumerable<UrnJsonTypeValue> input, out List<UrnJsonTypeValue> resource, string resourceTag)
{
resource = new List<UrnJsonTypeValue>();

if (input == null || !input.Any())
{
return false;
Expand Down Expand Up @@ -185,7 +185,7 @@

if (resource == null)
{
errors.Add(ValidationErrors.InvalidResource, "appInstanceDelegationRequest.Resource");

Check warning on line 188 in src/Altinn.AccessManagement.Core/Services/AppsInstanceDelegationService.cs

View workflow job for this annotation

GitHub Actions / Analyze

Define a constant instead of using this literal 'appInstanceDelegationRequest.Resource' 6 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)
}
else
{
Expand Down Expand Up @@ -217,7 +217,7 @@
return errorResult;
}

foreach (Right right in delegableRights)

Check warning on line 220 in src/Altinn.AccessManagement.Core/Services/AppsInstanceDelegationService.cs

View workflow job for this annotation

GitHub Actions / Analyze

'delegableRights' is null on at least one execution path. (https://rules.sonarsource.com/csharp/RSPEC-2259)
{
result.ResourceRightDelegationCheckResults.Add(new()
{
Expand Down Expand Up @@ -307,7 +307,7 @@
};
List<RightInternal> rightsAppCantDelegate = new List<RightInternal>();
UrnJsonTypeValue instanceId = KeyValueUrn.CreateUnchecked($"{AltinnXacmlConstants.MatchAttributeIdentifiers.ResourceInstanceAttribute}:{request.InstanceId}", AltinnXacmlConstants.MatchAttributeIdentifiers.ResourceInstanceAttribute.Length + 1);

foreach (RightInternal rightToDelegate in request.Rights)
{
if (CheckIfInstanceIsDelegable(delegableRights, rightToDelegate))
Expand Down Expand Up @@ -418,7 +418,7 @@
{
foreach (AppsInstanceDelegationResponse item in input)
{
RemoveInstanceIdFromResourceForResponse(item);
RemoveInstanceIdFromResourceForResponse(item);
}

return input;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,18 @@
return await Task.FromResult(new List<AuthorizedParty>());
}

private async Task<List<InstanceDelegationChange>> GetInstanceDelegations(int subjectUserId, List<int> subjectPartyIds, CancellationToken cancellationToken)
{
var userId = subjectUserId != 0 ? subjectUserId.SingleToList() : [];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Det blir vel feil å legge inn en userid i en liste over partyids siden disse ikke brukes likt og ikke har samme verdi en user har en party id men den er ikke lik userid

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jaja, var en god del issues med denne logikken. Har skrevet den om nå, ble ganske feil.

userId.AddRange(subjectPartyIds);
var parties = await _contextRetrievalService.GetPartiesAsync(userId, false, cancellationToken);
return await _delegations.GetAllCurrentReceivedInstanceDelegations(parties.Select(p => (Guid)p.PartyUuid).ToList(), cancellationToken);
}

private async Task<List<AuthorizedParty>> BuildAuthorizedParties(int subjectUserId, List<int> subjectPartyIds, bool includeAltinn2AuthorizedParties, bool includeResourcesThroughRoles, CancellationToken cancellationToken)

Check warning on line 175 in src/Altinn.AccessManagement.Core/Services/AuthorizedPartiesService.cs

View workflow job for this annotation

GitHub Actions / Analyze

Refactor this method to reduce its Cognitive Complexity from 70 to the 15 allowed. (https://rules.sonarsource.com/csharp/RSPEC-3776)
{
List<AuthorizedParty> result = new();
List<AuthorizedParty> a3AuthParties = new();
List<AuthorizedParty> result = [];
List<AuthorizedParty> a3AuthParties = [];
SortedDictionary<int, AuthorizedParty> authorizedPartyDict = [];

if ((includeAltinn2AuthorizedParties || includeResourcesThroughRoles) && subjectUserId != 0)
Expand Down Expand Up @@ -285,6 +293,30 @@
authorizedParty.EnrichWithResourceAccess(delegation.ResourceId);
}

var instanceDelegations = await GetInstanceDelegations(subjectUserId, subjectPartyIds, cancellationToken);
var instanceParties = await _contextRetrievalService.GetPartiesByUuids(instanceDelegations.Select(i => i.FromUuid), false, cancellationToken);
foreach (var delegation in instanceDelegations)
{
if (instanceParties.TryGetValue(delegation.FromUuid.ToString(), out var instanceParty))
{
throw new UnreachableException($"Get AuthorizedParties failed to lookup party with uuid {delegation.FromUuid} while building instance delegations list");
}

if (authorizedPartyDict.TryGetValue(instanceParty.PartyId, out var authorizedParty))
{
continue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vil ikke denne continue blokken medføre at bare nye partyid får lagt til nye instanser om du har flere instanser så er det bare den første som blir lagt til og dersom du har en annen tilgang så blir ingen instanser lagt til siden den allerede ligger i dictionarien.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mm, har skrevet om logikken her.

}

authorizedParty = new AuthorizedParty(instanceParty);
authorizedParty.AuthorizedInstances.Add(new()
{
InstanceId = delegation.InstanceId,
ResourceId = delegation.ResourceId,
});
authorizedPartyDict.Add(authorizedParty.PartyId, authorizedParty);
a3AuthParties.Add(authorizedParty);
}

result.AddRange(a3AuthParties);
return result;
}
Expand All @@ -305,4 +337,4 @@
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
_logger.LogError("AccessManagement // AltinnRolesClient // GetDecisionPointRolesForUser // Unexpected HttpStatusCode: {StatusCode}", response.StatusCode);
return new();
}
catch (Exception ex)

Check warning on line 54 in src/Altinn.AccessManagement.Integration/Clients/AltinnRolesClient.cs

View workflow job for this annotation

GitHub Actions / Analyze

Either log this exception and handle it, or rethrow it with some contextual information. (https://rules.sonarsource.com/csharp/RSPEC-2139)
{
_logger.LogError(ex, "AccessManagement // AltinnRolesClient // GetDecisionPointRolesForUser // Exception");
throw;
Expand All @@ -76,7 +76,7 @@
_logger.LogError("AccessManagement // AltinnRolesClient // GetRolesForDelegation // Unexpected HttpStatusCode: {StatusCode}", response.StatusCode);
return new();
}
catch (Exception ex)

Check warning on line 79 in src/Altinn.AccessManagement.Integration/Clients/AltinnRolesClient.cs

View workflow job for this annotation

GitHub Actions / Analyze

Either log this exception and handle it, or rethrow it with some contextual information. (https://rules.sonarsource.com/csharp/RSPEC-2139)
{
_logger.LogError(ex, "AccessManagement // AltinnRolesClient // GetRolesForDelegation // Exception");
throw;
Expand All @@ -102,10 +102,10 @@
_logger.LogError("AccessManagement // AltinnRolesClient // GetAuthorizedPartiesWithRoles // Unexpected HttpStatusCode: {StatusCode} Response: {Content}", response.StatusCode, content);
return new();
}
catch (Exception ex)

Check warning on line 105 in src/Altinn.AccessManagement.Integration/Clients/AltinnRolesClient.cs

View workflow job for this annotation

GitHub Actions / Analyze

Either log this exception and handle it, or rethrow it with some contextual information. (https://rules.sonarsource.com/csharp/RSPEC-2139)
{
_logger.LogError(ex, "AccessManagement // AltinnRolesClient // GetAuthorizedPartiesWithRoles // Exception");
throw;
}
}
}
}
72 changes: 61 additions & 11 deletions src/Altinn.AccessManagement.Persistence/DelegationMetadataRepo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public DelegationMetadataRepo(NpgsqlDataSource conn)
public async Task<List<DelegationChange>> GetAllAppDelegationChanges(string altinnAppId, int offeredByPartyId, int? coveredByPartyId, int? coveredByUserId, CancellationToken cancellationToken = default)
{
using var activity = TelemetryConfig.ActivitySource.StartActivity(ActivityKind.Client);

if (coveredByUserId == null && coveredByPartyId == null)
{
activity?.StopWithError(new ArgumentException($"Both params: {nameof(coveredByUserId)}, {nameof(coveredByPartyId)} cannot be null."));
Expand All @@ -60,7 +60,7 @@ FROM delegation.delegationChanges
AND coveredByPartyId = @coveredByPartyId
";
}

if (coveredByUserId != null)
{
query = /*strpsql*/@$"
Expand Down Expand Up @@ -335,6 +335,56 @@ public async Task<DelegationChange> InsertDelegation(ResourceAttributeMatchType
return await InsertResourceRegistryDelegation(delegationChange, cancellationToken);
}

/// <summary>
/// Fetches all instance delegated to given param
/// </summary>
/// <param name="toUuid">list of parties that has received an instance delegation</param>
/// <param name="cancellationToken">cancellation token</param>
/// <returns></returns>
public async Task<List<InstanceDelegationChange>> GetAllCurrentReceivedInstanceDelegations(List<Guid> toUuid, CancellationToken cancellationToken = default)
{
using var activity = TelemetryConfig.ActivitySource.StartActivity(ActivityKind.Client);

var query = /*strpsql*/ @"
SELECT
instancedelegationchangeid
,delegationchangetype
,instanceDelegationMode
,resourceid
,instanceid
,fromuuid
,fromtype
,touuid
,totype
,performedby
,performedbytype
,blobstoragepolicypath
,blobstorageversionid
,created
FROM
delegation.instancedelegationchanges
AND delegationchangetype != 'revoke_last'
WHERE
touuid = ANY(@toUuid)
GROUP BY
resourceid, touuid, fromuuid;
andreasisnes marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Samme som JK dersom du ikke har instanceId i group by så vil den slå sammen alle instansene desuten er det vel ikke mulig å bruke en group by og hente ut alle kolonner for de kolenne som ikke er grupert på vil kunne være mange verdier så de må ha en agregate funksjon for å finne hvilken som skal brukes. Jeg ville brukt Common table expression her og grupere på de dataene som var viktige og så hente ut max(instancedelegationchangeid) og deretter joine med denne common table expressionen med tabellen på nytt med id som match så ville jeg få ut alle siste endringer.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stemmer, templating før jeg fikk testet spørringen. Endret på spørringen nu

";

try
{
await using var cmd = _conn.CreateCommand(query);
cmd.Parameters.AddWithValue("toUuid", NpgsqlDbType.Array | NpgsqlDbType.Uuid, toUuid);
return await cmd.ExecuteEnumerableAsync(cancellationToken)
.SelectAwait(GetInstanceDelegationChange)
.ToListAsync(cancellationToken);
}
catch (Exception ex)
{
activity?.StopWithError(ex);
throw;
}
}

/// <inheritdoc />
public async Task<InstanceDelegationChange> GetLastInstanceDelegationChange(InstanceDelegationChangeRequest request, CancellationToken cancellationToken = default)
{
Expand Down Expand Up @@ -515,15 +565,15 @@ LatestChanges lc

return await cmd.ExecuteEnumerableAsync(cancellationToken)
.SelectAwait(GetInstanceDelegationChange)
.ToListAsync(cancellationToken);
.ToListAsync(cancellationToken);
}
catch (Exception ex)
{
activity?.StopWithError(ex);
throw;
}
}

private static async ValueTask<InstanceDelegationChange> GetInstanceDelegationChange(NpgsqlDataReader reader)
{
using var activity = TelemetryConfig.ActivitySource.StartActivity();
Expand Down Expand Up @@ -682,7 +732,7 @@ FROM insertAction AS ins
}
}

private async Task<DelegationChange> GetCurrentResourceRegistryDelegation(string resourceId, int offeredByPartyId, int? coveredByPartyId, int? coveredByUserId, Guid? toUuid, UuidType toUuidType, CancellationToken cancellationToken = default)
private async Task<DelegationChange> GetCurrentResourceRegistryDelegation(string resourceId, int offeredByPartyId, int? coveredByPartyId, int? coveredByUserId, Guid? toUuid, UuidType toUuidType, CancellationToken cancellationToken = default)
{
using var activity = TelemetryConfig.ActivitySource.StartActivity(ActivityKind.Client);

Expand Down Expand Up @@ -754,7 +804,7 @@ ORDER BY resourceRegistryDelegationChangeId DESC LIMIT 1
cmd.Parameters.AddWithNullableValue("coveredByUserId", NpgsqlDbType.Integer, coveredByUserId);
cmd.Parameters.AddWithNullableValue(ToUuid, NpgsqlDbType.Uuid, toUuid);
cmd.Parameters.AddWithValue(ToType, toUuidType);

await using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
if (await reader.ReadAsync(cancellationToken))
{
Expand Down Expand Up @@ -980,7 +1030,7 @@ FROM delegation.ResourceRegistryDelegationChanges AS rrdc
INNER JOIN res ON rrdc.resourceId_fk = res.resourceid
WHERE coveredByPartyId = ANY (@coveredByPartyIds)
";

if (offeredByPartyIds != null && offeredByPartyIds.Count > 0)
{
query += /*strpsql*/@"
Expand Down Expand Up @@ -1020,7 +1070,7 @@ FROM delegation.ResourceRegistryDelegationChanges AS rr
public async Task<List<DelegationChange>> GetReceivedResourceRegistryDelegationsForCoveredByUser(int coveredByUserId, List<int> offeredByPartyIds, List<string> resourceRegistryIds = null, List<ResourceType> resourceTypes = null, CancellationToken cancellationToken = default)
{
using var activity = TelemetryConfig.ActivitySource.StartActivity(ActivityKind.Client);

if (coveredByUserId < 1)
{
throw new ArgumentException("CoveredByUserId is required");
Expand Down Expand Up @@ -1118,7 +1168,7 @@ accessmanagement.Resource AS R
AND offeredByPartyId = @offeredByPartyId";
}

if (coveredByPartyId > 0)
if (coveredByPartyId > 0)
{
query += /*strpsql*/@"
AND coveredByPartyId = @coveredByPartyId";
Expand Down Expand Up @@ -1271,7 +1321,7 @@ public async Task<List<DelegationChange>> GetAllDelegationChangesForAuthorizedPa

if (coveredByUserIds == null && coveredByPartyIds == null)
{
return new List<DelegationChange>();
return [];
}

const string query = /*strpsql*/@"
Expand Down Expand Up @@ -1427,4 +1477,4 @@ private static async ValueTask<DelegationChange> GetResourceRegistryDelegationCh
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,9 +90,9 @@
await using var pgcom = _conn.CreateCommand(getAllAppDelegationChanges);

pgcom.Parameters.AddWithValue("_altinnAppId", altinnAppId);
pgcom.Parameters.AddWithValue("_offeredByPartyId", offeredByPartyId);

Check warning on line 93 in src/Altinn.AccessManagement.Persistence/DelegationMetadataRepository.cs

View workflow job for this annotation

GitHub Actions / Analyze

Define a constant instead of using this literal '_offeredByPartyId' 6 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)
pgcom.Parameters.AddWithValue("_coveredByUserId", coveredByUserId.HasValue ? coveredByUserId.Value : DBNull.Value);

Check warning on line 94 in src/Altinn.AccessManagement.Persistence/DelegationMetadataRepository.cs

View workflow job for this annotation

GitHub Actions / Analyze

Define a constant instead of using this literal '_coveredByUserId' 6 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)
pgcom.Parameters.AddWithValue("_coveredByPartyId", coveredByPartyId.HasValue ? coveredByPartyId.Value : DBNull.Value);

Check warning on line 95 in src/Altinn.AccessManagement.Persistence/DelegationMetadataRepository.cs

View workflow job for this annotation

GitHub Actions / Analyze

Define a constant instead of using this literal '_coveredByPartyId' 5 times. (https://rules.sonarsource.com/csharp/RSPEC-1192)

List<DelegationChange> delegationChanges = new List<DelegationChange>();

Expand Down Expand Up @@ -755,4 +755,10 @@
{
throw new NotImplementedException();
}

/// <inheritdoc />
public Task<List<InstanceDelegationChange>> GetAllCurrentReceivedInstanceDelegations(List<Guid> toUuid, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
}
2 changes: 1 addition & 1 deletion src/Altinn.AccessManagement/AccessManagementHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ private static void ConfigureOpenAPI(this WebApplicationBuilder builder)
while (t != null);
chain.Reverse();
return string.Join(".", chain);
};
};
});

builder.Services.AddUrnSwaggerSupport();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,4 @@ public async Task<ActionResult<List<DelegationChangeExternal>>> GetAllDelegation
return _mapper.Map<List<DelegationChangeExternal>>(response.DelegationChanges);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,8 @@ public async Task<ActionResult> Delegation([FromBody] AppsInstanceDelegationRequ
return Ok(_mapper.Map<AppsInstanceDelegationResponseDto>(serviceResult.Value));
}

return StatusCode(StatusCodes.Status206PartialContent, _mapper.Map<AppsInstanceDelegationResponseDto>(serviceResult.Value));
}
return StatusCode(StatusCodes.Status206PartialContent, _mapper.Map<AppsInstanceDelegationResponseDto>(serviceResult.Value));
}

/// <summary>
/// Gets app instance delegation
Expand Down
21 changes: 21 additions & 0 deletions src/Altinn.AccessManagement/Models/AuthorizedPartyExternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,29 @@ public class AuthorizedPartyExternal
/// </summary>
public List<string> AuthorizedRoles { get; set; } = [];

/// <summary>
/// Gets or sets a collection of all Authorized Instances
/// </summary>
public List<Resource> AuthorizedInstances { get; set; } = [];

/// <summary>
/// Gets or sets a set of subunits of this party, which the authorized subject also has some access to.
/// </summary>
public List<AuthorizedPartyExternal> Subunits { get; set; } = [];

/// <summary>
/// Composite Key instances
/// </summary>
public class Resource
{
/// <summary>
/// Resource ID
/// </summary>
public string ResourceId { get; set; }

/// <summary>
/// Instance ID
/// </summary>
public string InstanceId { get; set; }
}
}
Loading
Loading