Skip to content

Commit

Permalink
Authorize system user by delegations (#927)
Browse files Browse the repository at this point in the history
* Added support for system user uuid as xacml request attribute
Added support for lookup of delegations using system user uuid
Added new request/response integration tests using user uuid (will fail until mocked delegations are updated)

* delegation wrapper mock update

* - Update AccessManagementWrapper to use the new full DelegationChangeExternal model in order to get all properties correctly from the AccessManagement PIP API
- Added integration tests for both Decision and Authorize endpoints for SystemUser with App delegation and Resource delegation

* force upgrade of npgsql to 8.0.3

* fix sonar cloud issue

---------

Co-authored-by: Jon Kjetil Øye <acn-joye@ai-dev.no>
  • Loading branch information
jonkjetiloye and Jon Kjetil Øye authored Jul 18, 2024
1 parent 5c4e6d9 commit e8284ae
Show file tree
Hide file tree
Showing 24 changed files with 735 additions and 70 deletions.
5 changes: 5 additions & 0 deletions src/Authorization/Constants/XacmlRequestAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,10 @@ public static class XacmlRequestAttribute
/// xacml string that represents person identifier
/// </summary>
public const string PersonIdAttribute = "urn:altinn:person:identifier-no";

/// <summary>
/// xacml attribute urn prefix that represents system user id
/// </summary>
public const string SystemUserIdAttribute = "urn:altinn:systemuser:uuid";
}
}
18 changes: 7 additions & 11 deletions src/Authorization/Controllers/DecisionController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ private async Task<XacmlContextResponse> Authorize(XacmlContextRequest decisionR
return rolesContextResponse;
}

private async Task<XacmlContextResponse> ProcessDelegationResult(XacmlContextRequest decisionRequest, XacmlPolicy resourcePolicy, IEnumerable<DelegationChange> delegations, CancellationToken cancellationToken = default)
private async Task<XacmlContextResponse> ProcessDelegationResult(XacmlContextRequest decisionRequest, XacmlPolicy resourcePolicy, IEnumerable<DelegationChangeExternal> delegations, CancellationToken cancellationToken = default)
{
if (!delegations.IsNullOrEmpty())
{
Expand Down Expand Up @@ -335,18 +335,14 @@ private static bool IsTypeResource(XacmlResourceAttributes resourceAttributes) =

private bool IsIncompleteRequestForDelegation(XacmlResourceAttributes resourceAttributes, XacmlContextRequest decisionRequest) =>
resourceAttributes == null ||
(_delegationContextHandler.GetSubjectUserId(decisionRequest) == 0 && _delegationContextHandler.GetSubjectPartyId(decisionRequest) == 0) ||
(_delegationContextHandler.GetSubjectAttributeMatch(decisionRequest, [XacmlRequestAttribute.UserAttribute, XacmlRequestAttribute.PartyAttribute, XacmlRequestAttribute.SystemUserIdAttribute]) == null) ||
!int.TryParse(resourceAttributes.ResourcePartyValue, out var _) ||
!(IsTypeApp(resourceAttributes) || IsTypeResource(resourceAttributes));

private Action<DelegationChangeInput> WithDefaultGetAllDelegationChangesInput(XacmlResourceAttributes resourceAttributes, XacmlContextRequest decisionRequest) => (input) =>
{
input.Subject = _delegationContextHandler.GetSubjectAttributeMatch(decisionRequest, [XacmlRequestAttribute.UserAttribute, XacmlRequestAttribute.PartyAttribute, XacmlRequestAttribute.SystemUserIdAttribute]);
input.Party = new(AltinnXacmlConstants.MatchAttributeIdentifiers.PartyAttribute, resourceAttributes.ResourcePartyValue);
int subjectUserId = _delegationContextHandler.GetSubjectUserId(decisionRequest);
input.Subject = subjectUserId != 0 ?
new(AltinnXacmlConstants.MatchAttributeIdentifiers.UserAttribute, subjectUserId.ToString()) :
new(AltinnXacmlConstants.MatchAttributeIdentifiers.PartyAttribute, _delegationContextHandler.GetSubjectPartyId(decisionRequest).ToString());
};

private async Task<XacmlContextResponse> AuthorizeUsingDelegations(XacmlContextRequest decisionRequest, XacmlPolicy resourcePolicy, CancellationToken cancellationToken = default)
Expand Down Expand Up @@ -389,7 +385,7 @@ private async Task<XacmlContextResponse> AuthorizeUsingDelegations(XacmlContextR
});
}

private async Task<IEnumerable<DelegationChange>> GetAllCachedDelegationChanges(params Action<DelegationChangeInput>[] actions)
private async Task<IEnumerable<DelegationChangeExternal>> GetAllCachedDelegationChanges(params Action<DelegationChangeInput>[] actions)
{
var delegation = new DelegationChangeInput();
foreach (var action in actions)
Expand All @@ -403,7 +399,7 @@ private async Task<IEnumerable<DelegationChange>> GetAllCachedDelegationChanges(
$"a:{delegation.Resource.FirstOrDefault(r => r.Id == AltinnXacmlConstants.MatchAttributeIdentifiers.OrgAttribute)}/{delegation.Resource.FirstOrDefault(r => r.Id == AltinnXacmlConstants.MatchAttributeIdentifiers.AppAttribute)}",
$"r:{delegation.Resource.FirstOrDefault(r => r.Id == AltinnXacmlConstants.MatchAttributeIdentifiers.ResourceRegistry)}");

if (!_memoryCache.TryGetValue(cacheKey, out IEnumerable<DelegationChange> result))
if (!_memoryCache.TryGetValue(cacheKey, out IEnumerable<DelegationChangeExternal> result))
{
result = await _accessManagement.GetAllDelegationChanges(actions);
var cacheEntryOptions = new MemoryCacheEntryOptions()
Expand All @@ -416,14 +412,14 @@ private async Task<IEnumerable<DelegationChange>> GetAllCachedDelegationChanges(
return result;
}

private async Task<XacmlContextResponse> MakeContextDecisionUsingDelegations(XacmlContextRequest decisionRequest, IEnumerable<DelegationChange> delegations, XacmlPolicy appPolicy, CancellationToken cancellationToken = default)
private async Task<XacmlContextResponse> MakeContextDecisionUsingDelegations(XacmlContextRequest decisionRequest, IEnumerable<DelegationChangeExternal> delegations, XacmlPolicy appPolicy, CancellationToken cancellationToken = default)
{
XacmlContextResponse delegationContextResponse = new XacmlContextResponse(new XacmlContextResult(XacmlContextDecision.NotApplicable)
{
Status = new XacmlContextStatus(XacmlContextStatusCode.Success)
});

foreach (DelegationChange delegation in delegations.Where(d => d.DelegationChangeType != DelegationChangeType.RevokeLast))
foreach (DelegationChangeExternal delegation in delegations.Where(d => d.DelegationChangeType != DelegationChangeType.RevokeLast))
{
XacmlPolicy delegationPolicy = await _prp.GetPolicyVersionAsync(delegation.BlobStoragePolicyPath, delegation.BlobStorageVersionId, cancellationToken);
foreach (XacmlObligationExpression obligationExpression in appPolicy.ObligationExpressions)
Expand Down
126 changes: 126 additions & 0 deletions src/Authorization/Models/DelegationChangeExternal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
using System;
using System.Text.Json.Serialization;
using Altinn.Authorization.Enums;

namespace Altinn.Platform.Authorization.Models
{
/// <summary>
/// This model describes a delegation change as stored in the Authorization postgre DelegationChanges table.
/// </summary>
public class DelegationChangeExternal
{
/// <summary>
/// Gets or sets the delegation change id
/// </summary>
[JsonPropertyName("delegationchangeid")]
public int DelegationChangeId { get; set; }

/// <summary>
/// Gets or sets the resource registry delegation change id
/// </summary>
[JsonPropertyName("resourceregistrydelegationchangeid")]
public int ResourceRegistryDelegationChangeId { get; set; }

/// <summary>
/// Gets or sets the delegation change type
/// </summary>
[JsonPropertyName("delegationchangetype")]
public DelegationChangeType DelegationChangeType { get; set; }

/// <summary>
/// Gets or sets the resource id.
/// </summary>
[JsonPropertyName("resourceid")]
public string ResourceId { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the resourcetype.
/// </summary>
[JsonPropertyName("resourcetype")]
public string ResourceType { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the offeredbypartyid, refering to the party id of the user or organization offering the delegation.
/// </summary>
[JsonPropertyName("offeredbypartyid")]
public int OfferedByPartyId { get; set; }

/// <summary>
/// The uuid of the party the right is on behalf of
/// </summary>
[JsonPropertyName("fromuuid")]
public Guid? FromUuid { get; set; }

/// <summary>
/// The type of party the right is on behalf of (Person, Organization, SystemUser)
/// </summary>
[JsonPropertyName("fromuuidtype")]
public UuidType FromUuidType { get; set; }

/// <summary>
/// Gets or sets the coveredbypartyid, refering to the party id of the organization having received the delegation. Otherwise Null if the recipient is a user.
/// </summary>
[JsonPropertyName("coveredbypartyid")]
public int? CoveredByPartyId { get; set; }

/// <summary>
/// Gets or sets the coveredbyuserid, refering to the user id of the user having received the delegation. Otherwise Null if the recipient is an organization.
/// </summary>
[JsonPropertyName("coveredbyuserid")]
public int? CoveredByUserId { get; set; }

/// <summary>
/// The uuid of the party holding the right
/// </summary>
[JsonPropertyName("touuid")]
public Guid? ToUuid { get; set; }

/// <summary>
/// The type of party holding the right
/// </summary>
[JsonPropertyName("touuidtype")]
public UuidType ToUuidType { get; set; }

/// <summary>
/// Gets or sets the user id of the user that performed the delegation change (either added or removed rules to the policy, or deleted it entirely).
/// </summary>
[JsonPropertyName("performedbyuserid")]
public int? PerformedByUserId { get; set; }

/// <summary>
/// Gets or sets the party id of the user that performed the delegation change (either added or removed rules to the policy, or deleted it entirely).
/// </summary>
[JsonPropertyName("performedbypartyid")]
public int? PerformedByPartyId { get; set; }

/// <summary>
/// The uuid of the party that performed the delegation
/// </summary>
[JsonPropertyName("performedbyuuid")]
public Guid? PerformedByUuid { get; set; }

/// <summary>
/// The type of the party that performed the delegation
/// </summary>
[JsonPropertyName("performedbyuuidtype")]
public UuidType PerformedByUuidType { get; set; }

/// <summary>
/// Gets or sets blobstoragepolicypath.
/// </summary>
[JsonPropertyName("blobstoragepolicypath")]
public string BlobStoragePolicyPath { get; set; }

/// <summary>
/// Gets or sets the blobstorage versionid
/// </summary>
[JsonPropertyName("blobstorageversionid")]
public string BlobStorageVersionId { get; set; }

/// <summary>
/// Gets or sets the created date and timestamp for the delegation change
/// </summary>
[JsonPropertyName("created")]
public DateTime? Created { get; set; }
}
}
44 changes: 44 additions & 0 deletions src/Authorization/Models/UuidType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System.Runtime.Serialization;
using NpgsqlTypes;

namespace Altinn.Authorization.Enums;

/// <summary>
/// Enum defining the different uuids used for defining parts in a delegation
/// </summary>
public enum UuidType
{
/// <summary>
/// Placeholder when type is not specified should only happen when there is no Uuid to match it with
/// </summary>
[EnumMember]
NotSpecified,

/// <summary>
/// Defining a person this could also be identified with "Fødselsnummer"/"Dnummer"
/// </summary>
[EnumMember(Value = "urn:altinn:person:uuid")]
[PgName("urn:altinn:person:uuid")]
Person,

/// <summary>
/// Identifies a unit could also be identified with a Organization number
/// </summary>
[EnumMember(Value = "urn:altinn:organization:uuid")]
[PgName("urn:altinn:organization:uuid")]
Organization,

/// <summary>
/// Identifies a systemuser this is a identifier for machine integration it could also be identified with a unique name
/// </summary>
[EnumMember(Value = "urn:altinn:systemuser:uuid")]
[PgName("urn:altinn:systemuser:uuid")]
SystemUser,

/// <summary>
/// Identifies a enterpriseuser this is marked as obsolete and is used for existing integration is also identified with an unique username
/// </summary>
[EnumMember(Value = "urn:altinn:enterpriseuser:uuid")]
[PgName("urn:altinn:enterpriseuser:uuid")]
EnterpriseUser
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public AccessManagementWrapper(ILogger<AccessManagementWrapper> logger, AccessMa
/// Endpoint to find all delegation changes for a given user, reportee and app/resource context
/// </summary>
/// <returns>Input parameter to the request</returns>
public async Task<IEnumerable<DelegationChange>> GetAllDelegationChanges(DelegationChangeInput input)
public async Task<IEnumerable<DelegationChangeExternal>> GetAllDelegationChanges(DelegationChangeInput input)
{
try
{
Expand All @@ -47,7 +47,7 @@ public async Task<IEnumerable<DelegationChange>> GetAllDelegationChanges(Delegat

if (response.IsSuccessStatusCode)
{
return await response.Content.ReadFromJsonAsync<IEnumerable<DelegationChange>>();
return await response.Content.ReadFromJsonAsync<IEnumerable<DelegationChangeExternal>>();
}

var content = await response.Content.ReadAsStringAsync();
Expand All @@ -70,7 +70,7 @@ public async Task<IEnumerable<DelegationChange>> GetAllDelegationChanges(Delegat
/// </summary>
/// <param name="actions">optional funvation pattern for modifying the request sent to Access Management API</param>
/// <returns></returns>
public async Task<IEnumerable<DelegationChange>> GetAllDelegationChanges(params Action<DelegationChangeInput>[] actions)
public async Task<IEnumerable<DelegationChangeExternal>> GetAllDelegationChanges(params Action<DelegationChangeInput>[] actions)
{
var input = new DelegationChangeInput()
{
Expand Down
5 changes: 5 additions & 0 deletions src/Authorization/Services/Implementation/ContextHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,11 @@ protected async Task EnrichSubjectAttributes(XacmlContextRequest request, string
string subjectOrgnNo = string.Empty;
bool foundLegacyOrgNoAttribute = false;

if (subjectContextAttributes.Attributes.Any(a => a.AttributeId.OriginalString.Equals(XacmlRequestAttribute.SystemUserIdAttribute)) && subjectContextAttributes.Attributes.Count > 1)
{
throw new ArgumentException($"Subject attribute {XacmlRequestAttribute.SystemUserIdAttribute} can only be used by itself and not in combination with other subject identifiers.");
}

foreach (XacmlAttribute xacmlAttribute in subjectContextAttributes.Attributes)
{
if (xacmlAttribute.AttributeId.OriginalString.Equals(XacmlRequestAttribute.UserAttribute))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ public int GetSubjectPartyId(XacmlContextRequest request)
return Convert.ToInt32(subjectAttribute?.AttributeValues.FirstOrDefault()?.Value);
}

/// <summary>
/// Gets the value of the first found attribute matching the prioritized order of xacmlRequestAttributes provided, from the XacmlContextRequest subjects.
/// </summary>
/// <param name="request">The Xacml Context Request</param>
/// <param name="xacmlRequestAttributeIds">Array of xacml request urn attribute identifiers to look for, in prioritized order. First found matching attribute is returned.</param>
/// <returns>The value of the first found matching subject attribute if any exists</returns>
public AttributeMatch GetSubjectAttributeMatch(XacmlContextRequest request, string[] xacmlRequestAttributeIds)
{
XacmlContextAttributes subjectContextAttributes = request.GetSubjectAttributes();
foreach (var attributeId in xacmlRequestAttributeIds)
{
XacmlAttribute subjectAttribute = subjectContextAttributes.Attributes.FirstOrDefault(a => a.AttributeId.OriginalString.Equals(attributeId, StringComparison.OrdinalIgnoreCase));

if (subjectAttribute != null)
{
return new(attributeId, subjectAttribute.AttributeValues.FirstOrDefault()?.Value);
}
}

return null;
}

/// <summary>
/// Gets a XacmlResourceAttributes model from the XacmlContextRequest
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ public interface IAccessManagementWrapper
/// Endpoint to find all delegation changes for a given user, reportee and app/resource context
/// </summary>
/// <returns>Input parameter to the request</returns>
public Task<IEnumerable<DelegationChange>> GetAllDelegationChanges(DelegationChangeInput input);
public Task<IEnumerable<DelegationChangeExternal>> GetAllDelegationChanges(DelegationChangeInput input);

/// <summary>
/// Endpoint to find all delegation changes for a given user, reportee and app/resource context
/// </summary>
/// <returns>optional funvation pattern for modifying the request sent to Access Management API</returns>
public Task<IEnumerable<DelegationChange>> GetAllDelegationChanges(params Action<DelegationChangeInput>[] actions);
public Task<IEnumerable<DelegationChangeExternal>> GetAllDelegationChanges(params Action<DelegationChangeInput>[] actions);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ public interface IDelegationContextHandler : IContextHandler
/// <param name="subjects">The list of PartyIds to be added as subject attributes</param>
public void Enrich(XacmlContextRequest request, List<int> subjects);

/// <summary>
/// Gets the value of the first found attribute matching the prioritized order of xacmlRequestAttributes provided, from the XacmlContextRequest subjects.
/// </summary>
/// <param name="request">The Xacml Context Request</param>
/// <param name="xacmlRequestAttributeIds">Array of xacml request urn attribute identifiers to look for, in prioritized order. First found matching attribute is returned.</param>
/// <returns>The value of the first found matching subject attribute if any exists</returns>
public AttributeMatch GetSubjectAttributeMatch(XacmlContextRequest request, string[] xacmlRequestAttributeIds);

/// <summary>
/// Gets the user id from the XacmlContextRequest subject attribute
/// </summary>
Expand Down
Loading

0 comments on commit e8284ae

Please sign in to comment.