Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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 @@ -42,5 +42,14 @@ public interface IRoleClient
/// <param name="includePackageResources">Whether resources from packages should be included.</param>
/// <param name="languageCode">Language code for localization.</param>
Task<IEnumerable<ResourceAM>> GetRoleResources(string roleCode, string variant, bool includePackageResources, string languageCode);

/// <summary>
/// Removes an Altinn 2 role assignment between two parties.
/// </summary>
/// <param name="party">The party performing the action.</param>
/// <param name="from">The right owner (the party that has the role).</param>
/// <param name="to">The right holder (the party the role is assigned to).</param>
/// <param name="roleCode">The role code to remove.</param>
Task RemoveRole(Guid party, Guid from, Guid to, string roleCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public class FeatureFlags
/// Whether to enrich instance delegations with dialogporten lookup data
/// </summary>
public bool EnableDialogportenDialogLookup { get; set; }

/// <summary>
/// Whether to use connections API in backend for agent system users
/// </summary>
Expand All @@ -116,6 +116,11 @@ public class FeatureFlags
/// </summary>
public bool EnableMaskinportenAdministration { get; set; }

/// <summary>
/// Whether to enable deletion of Altinn 2 roles
/// </summary>
public bool EnableRoleDeletion { get; set; }

/// <summary>
/// When true, <c>ReporteeController.ChangeAndRedirect</c> bounces through Altinn 2's
/// <c>/ui/Reportee/ChangeReporteeAndRedirect</c> after setting Altinn 3 cookies, so that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ public class Role
/// </summary>
public Provider Provider { get; set; }

/// <summary>
/// This flag is true if the role assignment can be revoked
/// </summary>
public bool IsRevocable { get; set; }

/// <summary>
/// Construct from Role
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
namespace Altinn.AccessManagement.UI.Core.Services.Interfaces
{
/// <summary>
/// Service for access package logic
/// </summary>
/// Service for access package logic
/// </summary>
public interface IRoleService
{
/// <summary>
Expand Down Expand Up @@ -42,5 +42,14 @@ public interface IRoleService
/// <param name="includePackageResources">Whether to include resources assigned via packages.</param>
/// <param name="languageCode">Language code for localization.</param>
Task<IEnumerable<ResourceAM>> GetRoleResources(string roleCode, string variant, bool includePackageResources, string languageCode);

/// <summary>
/// Removes an Altinn 2 role assignment between two parties.
/// </summary>
/// <param name="party">The party performing the action.</param>
/// <param name="from">The right owner (the party that has the role).</param>
/// <param name="to">The right holder (the party the role is assigned to).</param>
/// <param name="roleCode">The role code to remove.</param>
Task RemoveRole(Guid party, Guid from, Guid to, string roleCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,9 @@ public Task<IEnumerable<AccessPackage>> GetRolePackages(string roleCode, string
/// <inheritdoc />
public Task<IEnumerable<ResourceAM>> GetRoleResources(string roleCode, string variant, bool includePackageResources, string languageCode)
=> _roleClient.GetRoleResources(roleCode, variant, includePackageResources, languageCode);

/// <inheritdoc />
public Task RemoveRole(Guid party, Guid from, Guid to, string roleCode)
=> _roleClient.RemoveRole(party, from, to, roleCode);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Security.Cryptography.Xml" Version="9.0.15" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
private readonly HttpClient _client;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly PlatformSettings _platformSettings;
private readonly IAccessTokenProvider _accessTokenProvider;

Check warning on line 29 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/RoleClient.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Remove this unread private field '_accessTokenProvider' or refactor the code to use its value.

Check warning on line 29 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/RoleClient.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Remove this unread private field '_accessTokenProvider' or refactor the code to use its value.

/// <summary>
/// Initializes a new instance of the <see cref="AccessPackageClient"/> class
Expand Down Expand Up @@ -93,5 +93,22 @@
HttpResponseMessage response = await _client.GetAsync(token, endpointUrl, languageCode: languageCode);
return await ClientUtils.DeserializeIfSuccessfullStatusCode<IEnumerable<ResourceAM>>(response, _logger, "RoleClient // GetRoleResources");
}

/// <inheritdoc />
public async Task RemoveRole(Guid party, Guid from, Guid to, string roleCode)
{
string endpointUrl = $"enduser/connections/roles?party={party}&from={from}&to={to}&rolecode={Uri.EscapeDataString(roleCode ?? string.Empty)}";
string token = JwtTokenUtil.GetTokenFromContext(_httpContextAccessor.HttpContext, _platformSettings.JwtCookieName);

HttpResponseMessage response = await _client.DeleteAsync(token, endpointUrl);
if (response.IsSuccessStatusCode)
{
return;
}

string responseContent = await response.Content.ReadAsStringAsync();
_logger.LogError("AccessManagement.UI // RoleClient.RemoveRole // Unexpected HttpStatusCode: {StatusCode}\n {responseBody}", response.StatusCode, responseContent);

Check warning on line 110 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/RoleClient.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Use PascalCase for named placeholders.

Check warning on line 110 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/RoleClient.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Use PascalCase for named placeholders.

Check warning on line 110 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Integration/Clients/RoleClient.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use PascalCase for named placeholders.

See more on https://sonarcloud.io/project/issues?id=Altinn_altinn-access-management-frontend&issues=AZ4hiE_m6txHQD_wBaIQ&open=AZ4hiE_m6txHQD_wBaIQ&pullRequest=2212
throw new HttpStatusException("StatusError", "Unexpected response status from Access Management", response.StatusCode, _httpContextAccessor.HttpContext?.TraceIdentifier, responseContent);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
/// </summary>
public RoleClientMock(
HttpClient httpClient,
ILogger<AccessManagementClientMock> logger,

Check warning on line 29 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Mocks/Mocks/RoleClientMock.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Update this logger to use its enclosing type.
IHttpContextAccessor httpContextAccessor)
{
_dataFolder = Path.Combine(Path.GetDirectoryName(new Uri(typeof(AccessManagementClientMock).Assembly.Location).LocalPath), "Data");
Expand All @@ -53,7 +53,7 @@

private string GetRolePermissionsDataPath(Guid? from, Guid? to)
{
string folder = Path.Combine(_dataFolder, "Roles", "Permissions");

Check warning on line 56 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Mocks/Mocks/RoleClientMock.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

Define a constant instead of using this literal 'Roles' 4 times.
string shortFileName = $"{ShortenIdentifier(from)}_{ShortenIdentifier(to)}.json";
string shortPath = Path.Combine(folder, shortFileName);

Expand Down Expand Up @@ -81,7 +81,7 @@
}
catch (Exception ex)
{
throw new Exception($"Unexpected error reading mock roles from {dataPath}", ex);

Check warning on line 84 in backend/src/Altinn.AccessManagement.UI/Altinn.AccessManagement.UI.Mocks/Mocks/RoleClientMock.cs

View workflow job for this annotation

GitHub Actions / Continous Integration / Analyze

'System.Exception' should not be thrown by user code.
}
}

Expand All @@ -99,6 +99,13 @@
return Task.FromResult(Util.GetMockData<IEnumerable<ResourceAM>>(dataPath));
}

/// <inheritdoc />
public Task RemoveRole(Guid party, Guid from, Guid to, string roleCode)
{
Util.ThrowExceptionIfTriggerParty(from.ToString());
return Task.CompletedTask;
}

private static string ShortenIdentifier(Guid? id)
{
return id.HasValue ? id.Value.ToString("N")[..8] : "none";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,45 @@ public async Task GetAllRoles_ReturnsCachedResponseOnSubsequentRequest()
roleClientMock.Verify(rc => rc.GetAllRoles(It.IsAny<string>()), Times.Once);
}

[Fact]
public async Task DeleteRole_ReturnsNoContent()
{
Guid party = new("cd35779b-b174-4ecc-bbef-ece13611be7f");
Guid from = new("cd35779b-b174-4ecc-bbef-ece13611be7f");
Guid to = new("167536b5-f8ed-4c5a-8f48-0279507e53ae");

HttpResponseMessage response = await _client.DeleteAsync($"accessmanagement/api/v1/role/roles?party={party}&from={from}&to={to}&rolecode=daglig-leder");

Assert.Equal(HttpStatusCode.NoContent, response.StatusCode);
}

[Fact]
public async Task DeleteRole_WhenRoleCodeMissing_ReturnsBadRequest()
{
Guid party = new("cd35779b-b174-4ecc-bbef-ece13611be7f");
Guid from = new("cd35779b-b174-4ecc-bbef-ece13611be7f");
Guid to = new("167536b5-f8ed-4c5a-8f48-0279507e53ae");

HttpResponseMessage response = await _client.DeleteAsync($"accessmanagement/api/v1/role/roles?party={party}&from={from}&to={to}");

Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);

string message = await response.Content.ReadAsStringAsync();
Assert.Contains("rolecode query parameter must be provided", message, StringComparison.OrdinalIgnoreCase);
}

[Fact]
public async Task DeleteRole_WhenClientReturnsError_ProvidesStatusFromException()
{
Guid party = new("cd35779b-b174-4ecc-bbef-ece13611be7f");
Guid from = new("00000000-0000-0000-0000-000000000404");
Guid to = new("167536b5-f8ed-4c5a-8f48-0279507e53ae");

HttpResponseMessage response = await _client.DeleteAsync($"accessmanagement/api/v1/role/roles?party={party}&from={from}&to={to}&rolecode=daglig-leder");

Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}

private static string ShortenIdentifier(Guid id) => id.ToString("N")[..8];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,43 @@ public async Task<ActionResult<IEnumerable<AccessPackage>>> GetRolePackages(
}
}

/// <summary>
/// Removes an Altinn 2 role assignment from a connection between two parties.
/// </summary>
/// <param name="party">The party performing the action.</param>
/// <param name="from">The right owner (the party that has the role).</param>
/// <param name="to">The right holder (the party the role is assigned to).</param>
/// <param name="roleCode">The role code to remove.</param>
[HttpDelete("roles")]
[Authorize]
public async Task<IActionResult> DeleteRole(
[FromQuery] Guid party,
[FromQuery] Guid from,
[FromQuery] Guid to,
[FromQuery(Name = "rolecode")] string roleCode)
Comment thread
allinox marked this conversation as resolved.
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}

if (string.IsNullOrWhiteSpace(roleCode))
{
return BadRequest("rolecode query parameter must be provided.");
}

try
{
await _roleService.RemoveRole(party, from, to, roleCode);
return NoContent();
}
catch (HttpStatusException ex)
{
Comment thread
allinox marked this conversation as resolved.
string responseContent = ex.Message;
return new ObjectResult(ProblemDetailsFactory.CreateProblemDetails(HttpContext, (int?)ex.StatusCode, "Unexpected HttpStatus response from backend", detail: responseContent));
}
}
Comment thread
allinox marked this conversation as resolved.

/// <summary>
/// Gets the resources available for the specified role.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"EnableDialogportenDialogLookup": false,
"UseConnectionsForAgentSystemuser": true,
"EnableMaskinportenAdministration": true,
"EnableRoleDeletion": true,
"RouteChangeReporteeViaAltinn2": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
"EnableAddSelfToSystemuser": true,
"EnableDialogportenDialogLookup": true,
"UseConnectionsForAgentSystemuser": true,
"EnableMaskinportenAdministration": false
"EnableMaskinportenAdministration": false,
"EnableRoleDeletion": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"AddAllSystemuserCustomers": true,
"EnableAddSelfToSystemuser": true,
"EnableDialogportenDialogLookup": false,
"EnableMaskinportenAdministration": false
"EnableMaskinportenAdministration": false,
"EnableRoleDeletion": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"SystemUserAgentDelegation": false,
"Consent": false,
"AltinnCdn": false,
"ClientDelegation": false,
"ClientDelegation": false,
"Dialogporten": true
},
"KeyVaultSettings": {
Expand Down Expand Up @@ -59,6 +59,7 @@
"EnableAddSelfToSystemuser": true,
"EnableDialogportenDialogLookup": true,
"EnableMaskinportenAdministration": true,
"EnableRoleDeletion": true,
"RouteChangeReporteeViaAltinn2": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"EnableAddSelfToSystemuser": true,
"EnableDialogportenDialogLookup": true,
"EnableMaskinportenAdministration": false,
"EnableRoleDeletion": false,
"UseConnectionsForAgentSystemuser": true
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
"EnableAddSelfToSystemuser": true,
"EnableDialogportenDialogLookup": true,
"UseConnectionsForAgentSystemuser": true,
"EnableMaskinportenAdministration": false
"EnableMaskinportenAdministration": false,
"EnableRoleDeletion": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"AddAllSystemuserCustomers": true,
"EnableAddSelfToSystemuser": false,
"EnableDialogportenDialogLookup": true,
"EnableMaskinportenAdministration": false
"EnableMaskinportenAdministration": false,
"EnableRoleDeletion": false
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
"GeneralSettings": {
"FrontendBaseUrl": "http://localhost:5101",
"Hostname": "localhost",
"AllowedRedirectDomains": [ "altinn.no", "altinn.cloud" ]
"AllowedRedirectDomains": [
"altinn.no",
"altinn.cloud"
]
},
"MockSettings": {
"PDP": false,
Expand Down Expand Up @@ -74,6 +77,7 @@
"EnableDialogportenDialogLookup": false,
"UseConnectionsForAgentSystemuser": false,
"EnableMaskinportenAdministration": false,
"EnableRoleDeletion": false,
"RouteChangeReporteeViaAltinn2": true
}
}
}
29 changes: 16 additions & 13 deletions src/features/amUI/common/DelegationModal/EditModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { forwardRef, useEffect } from 'react';
import { DsDialog } from '@altinn/altinn-components';
import { DsDialog, Snackbar, SnackbarProvider } from '@altinn/altinn-components';

import type { ActionError } from '@/resources/hooks/useActionError';
import type { ServiceResource } from '@/rtk/features/singleRights/singleRightsApi';
Expand Down Expand Up @@ -99,18 +99,21 @@ export const EditModal = forwardRef<HTMLDialogElement, EditModalProps>(
reset();
}}
>
<div className={classes.content}>
{renderModalContent({
resource,
maskinportenScope,
accessPackage,
role,
instance,
toParty,
availableActions,
onSuccess,
})}
</div>
<SnackbarProvider>
<div className={classes.content}>
{renderModalContent({
resource,
maskinportenScope,
accessPackage,
role,
instance,
toParty,
availableActions,
onSuccess,
})}
</div>
<Snackbar />
</SnackbarProvider>
</DsDialog>
);
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
display: flex;
flex-direction: column;
justify-content: center;
gap: 1rem;
gap: var(--ds-spacing-2);
}

.infoContainer {
Expand Down Expand Up @@ -75,3 +75,7 @@
color: var(--ds-color-warning-base-default);
margin-right: 0.3rem;
}

.deleteRoleButtonContainer {
margin-top: var(--ds-spacing-4);
}
Loading
Loading