Skip to content

Commit

Permalink
feat: add support for access packages in policy editor (#13921)
Browse files Browse the repository at this point in the history
  • Loading branch information
mgunnerud authored Jan 3, 2025
1 parent 0c3d521 commit e49c545
Show file tree
Hide file tree
Showing 69 changed files with 1,789 additions and 8 deletions.
21 changes: 21 additions & 0 deletions backend/PolicyAdmin/Models/AccessPackageArea.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#nullable enable

namespace PolicyAdmin.Models
{
public class AccessPackageArea
{
public required string Id { get; set; }

public required string Urn { get; set; }

public required string Name { get; set; }

public string? Description { get; set; }

public string? Icon { get; set; }

public string? AreaGroup { get; set; }

public IEnumerable<AccessPackageOption> Packages { get; set; } = [];
}
}
17 changes: 17 additions & 0 deletions backend/PolicyAdmin/Models/AccessPackageAreaGroup.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace PolicyAdmin.Models
{
public class AccessPackageAreaGroup
{
public required string Id { get; set; }

public required string Urn { get; set; }

public required string Name { get; set; }

public string? Description { get; set; }

public string? Type { get; set; }

public IEnumerable<AccessPackageArea> Areas { get; set; } = [];
}
}
13 changes: 13 additions & 0 deletions backend/PolicyAdmin/Models/AccessPackageOption.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace PolicyAdmin.Models
{
public class AccessPackageOption
{
public required string Id { get; set; }

public required string Urn { get; set; }

public required string Name { get; set; }

public string? Description { get; set; }
}
}
2 changes: 2 additions & 0 deletions backend/PolicyAdmin/Models/PolicyRule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ public class PolicyRule

public List<string>? Subject { get; set; }

public List<string>? AccessPackages { get; set; }

public List<string>? Actions { get; set; }

public List<List<string>>? Resources { get; set; }
Expand Down
15 changes: 14 additions & 1 deletion backend/PolicyAdmin/PolicyConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static ResourcePolicy ConvertPolicy(XacmlPolicy xacmlPolicy)
rule.Description = xr.Description;

rule.Subject = new List<string>();
rule.AccessPackages = new List<string>();
rule.Actions = new List<string>();
rule.Resources = new List<List<string>>();

Expand All @@ -31,7 +32,9 @@ public static ResourcePolicy ConvertPolicy(XacmlPolicy xacmlPolicy)
{
foreach (XacmlAllOf allOf in anyOf.AllOf)
{
List<string>? subject = GetRuleSubjects(allOf);
List<string>? subject = GetRuleSubjects(allOf)?.Where(x => !x.StartsWith("urn:altinn:accesspackage")).ToList();

List<string>? accessPackages = GetRuleSubjects(allOf)?.Where(x => x.StartsWith("urn:altinn:accesspackage")).ToList();

List<string>? resource = GetRuleResources(allOf);

Expand All @@ -42,6 +45,11 @@ public static ResourcePolicy ConvertPolicy(XacmlPolicy xacmlPolicy)
rule.Subject.AddRange(subject);
}

if (accessPackages != null)
{
rule.AccessPackages.AddRange(accessPackages);
}

if (action != null)
{
rule.Actions.AddRange(action);
Expand Down Expand Up @@ -195,6 +203,11 @@ private static XacmlRule ConvertRule(PolicyRule policyRule)
ruleAnyOfs.Add(GetSubjectAnyOfs(policyRule.Subject));
}

if (policyRule.AccessPackages != null && policyRule.AccessPackages.Count > 0)
{
ruleAnyOfs.Add(GetSubjectAnyOfs(policyRule.AccessPackages));
}

if (policyRule.Resources != null && policyRule.Resources.Count > 0)
{
ruleAnyOfs.Add(GetResourceAnyOfs(policyRule.Resources));
Expand Down
8 changes: 8 additions & 0 deletions backend/src/Designer/Controllers/PolicyController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,14 @@ public async Task<ActionResult> GetActionOptions(string org, string app, Cancell
return Ok(actionOptions);
}

[HttpGet]
[Route("accesspackageoptions")]
public async Task<ActionResult> GetAccessPackageOptions(string org, string app, CancellationToken cancellationToken)
{
List<AccessPackageAreaGroup> accessPackageOptions = await _policyOptions.GetAccessPackageOptions(cancellationToken);
return Ok(accessPackageOptions);
}


private ValidationProblemDetails ValidatePolicy(ResourcePolicy policy)
{
Expand Down
46 changes: 46 additions & 0 deletions backend/src/Designer/Controllers/ResourceAdminController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,52 @@ public async Task<ActionResult<List<EuroVocTerm>>> GetEuroVoc(CancellationToken
return sectors;
}

[HttpGet]
[Route("designer/api/accesspackageservices/{accesspackage}/{env}")]
public async Task<ActionResult<List<AccessPackageService>>> GetServicesForAccessPackage(string org, string accesspackage, string env)
{
// POST to get all resources per access package
List<SubjectResources> subjectResources = await _resourceRegistry.GetSubjectResources([accesspackage], env);

// GET full list of resources (with apps) in environment
string cacheKey = $"resourcelist_with_apps${env}";
if (!_memoryCache.TryGetValue(cacheKey, out List<ServiceResource> environmentResources))
{
environmentResources = await _resourceRegistry.GetResourceList(env, false, true);

MemoryCacheEntryOptions cacheEntryOptions = new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
.SetAbsoluteExpiration(new TimeSpan(0, _cacheSettings.DataNorgeApiCacheTimeout, 0));
_memoryCache.Set(cacheKey, environmentResources, cacheEntryOptions);
}

List<AttributeMatchV2> resources = subjectResources.Find(x => x.Subject.Urn == accesspackage)?.Resources;

OrgList orgList = await GetOrgList();
List<AccessPackageService> result = [];

// return resources for all subjectResources
resources?.ForEach(resourceMatch =>
{
ServiceResource fullResource = environmentResources.Find(x => x.Identifier == resourceMatch.Value);

if (fullResource != null)
{
orgList.Orgs.TryGetValue(fullResource.HasCompetentAuthority.Orgcode.ToLower(), out Org organization);

result.Add(new AccessPackageService()
{
Identifier = resourceMatch.Value,
Title = fullResource?.Title,
HasCompetentAuthority = fullResource.HasCompetentAuthority,
LogoUrl = organization.Logo
});
}
});

return result;
}

[HttpGet]
[Route("designer/api/{org}/resources/altinn2linkservices/{env}")]
public async Task<ActionResult<List<AvailableService>>> GetAltinn2LinkServices(string org, string env)
Expand Down
15 changes: 15 additions & 0 deletions backend/src/Designer/Models/AccessPackageService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System.Collections.Generic;

namespace Altinn.Studio.Designer.Models
{
public class AccessPackageService
{
public string Identifier { get; set; }

public Dictionary<string, string> Title { get; set; }

public CompetentAuthority HasCompetentAuthority { get; set; }

public string LogoUrl { get; set; }
}
}
28 changes: 28 additions & 0 deletions backend/src/Designer/Models/AttributeMatchV2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.ComponentModel.DataAnnotations;

namespace Altinn.Studio.Designer.Models
{
/// <summary>
/// This model describes a pair of AttributeId and AttributeValue for use in matching in XACML policies, for instance a resource, a user, a party or an action.
/// </summary>
public class AttributeMatchV2
{
/// <summary>
/// Gets or sets the attribute id for the match
/// </summary>
[Required]
public required string Type { get; set; }

/// <summary>
/// Gets or sets the attribute value for the match
/// </summary>
[Required]
public required string Value { get; set; }

/// <summary>
/// The urn for the attribute
/// </summary>
[Required]
public required string Urn { get; set; }
}
}
9 changes: 9 additions & 0 deletions backend/src/Designer/Models/Dto/SubjectResourcesDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
using System.Collections.Generic;

namespace Altinn.Studio.Designer.Models.Dto
{
public class SubjectResourcesDto
{
public List<SubjectResources> Data { get; set; }
}
}
21 changes: 21 additions & 0 deletions backend/src/Designer/Models/SubjectResources.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#nullable enable
using System.Collections.Generic;

namespace Altinn.Studio.Designer.Models
{
/// <summary>
/// Defines resources that a given subject have access to
/// </summary>
public class SubjectResources
{
/// <summary>
/// The subject
/// </summary>
public required AttributeMatchV2 Subject { get; set; }

/// <summary>
/// List of resources that the given subject has access to
/// </summary>
public required List<AttributeMatchV2> Resources { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -228,19 +228,19 @@ public async Task<List<ServiceResource>> GetResources(string env)
/// Get resource list
/// </summary>
/// <returns>List of all resources</returns>
public async Task<List<ServiceResource>> GetResourceList(string env, bool includeAltinn2)
public async Task<List<ServiceResource>> GetResourceList(string env, bool includeAltinn2, bool includeApps = false)
{

string endpointUrl;

//Checks if not tested locally by passing dev as env parameter
if (!env.ToLower().Equals("dev"))
{
endpointUrl = $"{GetResourceRegistryBaseUrl(env)}{_platformSettings.ResourceRegistryUrl}/resourcelist/?includeApps=false&includeAltinn2={includeAltinn2}";
endpointUrl = $"{GetResourceRegistryBaseUrl(env)}{_platformSettings.ResourceRegistryUrl}/resourcelist/?includeApps={includeApps}&includeAltinn2={includeAltinn2}";
}
else
{
endpointUrl = $"{_platformSettings.ResourceRegistryDefaultBaseUrl}{_platformSettings.ResourceRegistryUrl}/resourcelist/?includeApps=false&includeAltinn2={includeAltinn2}";
endpointUrl = $"{_platformSettings.ResourceRegistryDefaultBaseUrl}{_platformSettings.ResourceRegistryUrl}/resourcelist/?includeApps={includeApps}&includeAltinn2={includeAltinn2}";
}

JsonSerializerOptions options = new JsonSerializerOptions
Expand Down Expand Up @@ -627,6 +627,25 @@ string env
return removeResourceAccessListResponse.StatusCode;
}

public async Task<List<SubjectResources>> GetSubjectResources(List<string> subjects, string env)
{
string resourceRegisterUrl = GetResourceRegistryBaseUrl(env);
string url = $"{resourceRegisterUrl}/resourceregistry/api/v1/resource/bysubjects";

string serializedContent = JsonSerializer.Serialize(subjects, _serializerOptions);
using HttpRequestMessage getSubjectResourcesRequest = new HttpRequestMessage()
{
RequestUri = new Uri(url),
Method = HttpMethod.Post,
Content = new StringContent(serializedContent, Encoding.UTF8, "application/json"),
};
using HttpResponseMessage response = await _httpClient.SendAsync(getSubjectResourcesRequest);
response.EnsureSuccessStatusCode();

SubjectResourcesDto responseContent = await response.Content.ReadAsAsync<SubjectResourcesDto>();
return responseContent.Data;
}

private async Task<List<BrregParty>> GetBrregParties(string url)
{
HttpResponseMessage enheterResponse = await _httpClient.GetAsync(url);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public interface IResourceRegistry
/// Integration point for retrieving the full list of resources
/// </summary>
/// <returns>The resource full list of all resources if exists</returns>
Task<List<ServiceResource>> GetResourceList(string env, bool includeAltinn2);
Task<List<ServiceResource>> GetResourceList(string env, bool includeAltinn2, bool includeApps = false);

/// <summary>
/// Get Resource from Altinn 2 service
Expand Down Expand Up @@ -169,5 +169,7 @@ public interface IResourceRegistry
/// <param name="env">Chosen environment</param>
/// <returns>HTTP status code of the operation. 204 No content if remove was successful</returns>
Task<HttpStatusCode> RemoveResourceAccessList(string org, string resourceId, string listId, string env);

Task<List<SubjectResources>> GetSubjectResources(List<string> subjects, string env);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@ public interface IPolicyOptions
public Task<List<ActionOption>> GetActionOptions(CancellationToken cancellationToken = default);

public Task<List<SubjectOption>> GetSubjectOptions(CancellationToken cancellationToken = default);

public Task<List<AccessPackageAreaGroup>> GetAccessPackageOptions(CancellationToken cancellationToken = default);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
Expand All @@ -13,13 +14,35 @@ public class PolicyOptionsClient : IPolicyOptions
{
private readonly HttpClient _client;
private readonly ILogger<PolicyOptionsClient> _logger;
private readonly JsonSerializerOptions _serializerOptions = new JsonSerializerOptions() { PropertyNameCaseInsensitive = true, };

public PolicyOptionsClient(HttpClient httpClient, ILogger<PolicyOptionsClient> logger)
{
_client = httpClient;
_logger = logger;
}

public async Task<List<AccessPackageAreaGroup>> GetAccessPackageOptions(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
// Temp location. Will be moved to CDN
string url = "https://raw.githubusercontent.com/Altinn/altinn-studio-docs/master/content/authorization/architecture/resourceregistry/accesspackages_hier.json";

List<AccessPackageAreaGroup> accessPackageOptions;

try
{
HttpResponseMessage response = await _client.GetAsync(url, cancellationToken);
string accessPackageOptionsString = await response.Content.ReadAsStringAsync(cancellationToken);
accessPackageOptions = JsonSerializer.Deserialize<List<AccessPackageAreaGroup>>(accessPackageOptionsString, _serializerOptions);
return accessPackageOptions;
}
catch (Exception ex)
{
throw new Exception($"Something went wrong when retrieving Action options", ex);
}
}

public async Task<List<ActionOption>> GetActionOptions(CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
Expand Down
Loading

0 comments on commit e49c545

Please sign in to comment.