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

Feature/signing #956

Draft
wants to merge 57 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
58165c1
signing interface cardinality adr
HauklandJ Sep 26, 2024
deb8df4
Feature/signing service (#810)
HauklandJ Oct 2, 2024
913ff6d
summary additions
HauklandJ Oct 2, 2024
cc70d33
add telemetry
HauklandJ Oct 2, 2024
c9efaa7
When calling process next while in a signing task, if no action param…
bjorntore Oct 3, 2024
591dd77
add correspondance mock
HauklandJ Oct 14, 2024
c119d2b
add method for sending notifications
HauklandJ Oct 14, 2024
24351da
Expose endpoint for searching for person using ssn and last name. (#815)
bjorntore Oct 16, 2024
4768258
fix the pros and cons for adr 002
HauklandJ Oct 16, 2024
29069bb
Rename PersonSearchComponent to LookupPersonController.
bjorntore Oct 18, 2024
ff213eb
API Endpoint for getting org from Enhetsregisteret (#847)
cammiida Oct 23, 2024
ab8deab
Feature/singing bt (#868)
HauklandJ Oct 29, 2024
099ee1c
Feature/delegation client (#874)
HauklandJ Oct 31, 2024
224f350
Merge branch 'main' into feature/signing
HauklandJ Oct 31, 2024
08ca893
Remove duplicate Altinn.Platform.Storage.Interface package reference.
bjorntore Nov 11, 2024
90ce095
Fix some warnings so it can be built successfully.
bjorntore Nov 11, 2024
e4de3dc
Draf sulution for selecting the correct ISigneeProvider implementation.
bjorntore Nov 13, 2024
8a2194f
Improve missing signee provider exception message.
bjorntore Nov 18, 2024
39439f5
Add singing services to ServiceCollectionExtensions.
bjorntore Nov 18, 2024
d2ce333
Rename OnSignatureTaskReceived to OnSignatureAccessRightsDelegated. M…
bjorntore Nov 18, 2024
d341e03
Merge branch 'main' into feature/signing
bjorntore Nov 18, 2024
81d22fb
Merge branch 'main' into feature/signing
HauklandJ Nov 20, 2024
01d9665
Use IOtions for platform settings in AccessManagementClient.
bjorntore Nov 22, 2024
10f1ac8
Fix lookup person tests.
bjorntore Nov 22, 2024
fad8c56
Merge branch 'main' into feature/signing
bjorntore Nov 25, 2024
3ca94ab
SigningService: Move private methods below public ones.
bjorntore Nov 27, 2024
1f71b55
Validate app only as contributor on paymentInformation and signature …
bjorntore Nov 29, 2024
ea120e6
update builder to standard set in correspondance (#909)
HauklandJ Dec 2, 2024
edc2207
Merge branch 'main' into feature/signing
bjorntore Dec 2, 2024
279ac1f
Update lookup controllers (#945)
HauklandJ Dec 3, 2024
2017312
Merge branch 'main' into feature/signing
bjorntore Dec 6, 2024
1125be2
Merge branch 'main' into feature/signing
HauklandJ Dec 6, 2024
d101acc
Draft controller code for getting the signee states.
bjorntore Dec 6, 2024
aff6a8f
Merge branch 'main' into feature/signing
bjorntore Dec 6, 2024
184d6b9
Merge branch 'main' into feature/signing
HauklandJ Dec 6, 2024
3b8b214
update swagger
HauklandJ Dec 6, 2024
4be8194
SigningController: Use problem details in bad request response.
bjorntore Dec 9, 2024
07642e3
Feature/persist signee context (#980)
HauklandJ Dec 16, 2024
12cd55e
First version of "data elements to sign" endpoint.
bjorntore Dec 16, 2024
fe5b768
Fix merge error.
bjorntore Dec 16, 2024
d1b5413
Remove 404 from singing controller. Can't see that it returns 404.
bjorntore Dec 17, 2024
ef0a99d
Update openapi spec.
bjorntore Dec 17, 2024
8b83e6c
Merge branch 'main' into feature/signing
bjorntore Dec 17, 2024
79f2d1f
Adds self links to signing data elements (#986)
cammiida Jan 3, 2025
a4426a1
Feature/signing delegation (#982)
HauklandJ Jan 3, 2025
b5dfa5a
simplify lastname handling (#1006)
HauklandJ Jan 6, 2025
313057c
Merge branch 'main' into feature/signing
HauklandJ Jan 6, 2025
f0f70a4
adds party id to signing state response (#1009)
cammiida Jan 7, 2025
7e763b7
Feature/signing notification (#1021)
HauklandJ Jan 15, 2025
2702061
Fix setting signing delegation state (#1034)
cammiida Jan 15, 2025
5791de3
add signing delegation tests (#1038)
HauklandJ Jan 16, 2025
4e4df08
Download signDocuments and synchronize with SigneeContexts. (#1039)
bjorntore Jan 17, 2025
4d75eed
Merge branch 'main' into feature/signing
bjorntore Jan 17, 2025
7c4a7aa
Update swagger.json.
bjorntore Jan 17, 2025
15b7ca2
Update SaveJsonSwagger.verified.txt.
bjorntore Jan 17, 2025
67243b2
Send receipt for completed signing (#1042)
danielskovli Jan 17, 2025
47441eb
implements default signing task validator (#1046)
cammiida Jan 22, 2025
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
42 changes: 42 additions & 0 deletions doc/adr/001-signing-interface.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Internal handling of the signing interface

- Status: Proposed
- Deciders: Johannes Haukland, Bjørn Tore on behalf of Team Apps
- Date: 2024-09-25

## Result

Chosen alternative A2: Unlimited implementations per app.

## Problem context

Apps will expose an interface to app developers for signing logic. The implementation of the interface will
contain logic which derives the relevant signees in the application context.

## Decision drivers

- B1: Support multiple signees per process task.
- B2: Support runtime signees decision for app end users.
- B3: Support different signees for different process tasks.
- B4: It should be clear how to implement in Altinn Studio.
- B5: Nice to have: Keep complexity for app developers low.

## Alternatives considered

- A1: Maximum one implementation of the interface per app.
- A2: Unlimited implementations per app, process task connected to implementation id with `</altinn:signLogicId>` in `<altinn:signatureConfig>`

## Pros and cons

### A1

- Good, because B1, B2 and B3 is supported.
- Bad, because the implementation of the interface must include paths for different tasks to support B3. This undermines B5
- Bad, because the implementation in Altinn studio would be complex
- Adding another signing task would require Studio to append a conditional path to the existing interface implementation

### A2

- Good, because B1, B2 and B3 is supported.
- Good, because implementation for studio is trivial, supporting B4.
- Bad, because more process configuration is required to support B3. This undermines B5
58 changes: 58 additions & 0 deletions doc/adr/002-signing-transaction-handling.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# How we handle transactions for signing

- Status: Proposed
- Deciders: Johannes Haukland, Bjørn Tore on behalf of Team Apps
- Date: 02.10.2024

## Result

A2: State - We store transaction state. We retry, skipping the previously succesful steps.

## Problem context

When we talk about a transaction in this context we mean several ordered steps.
We use two categories to describe a step in the context of a transaction:

1. Idempotent - Completing the step multiple times has the same effect as doing it once
Example: Assigning a role to a person.
2. Non-Idempotent
Example: Sending a notification to a person.

In the case of signing we have two transactions:

#### Setting signees transaction

1. Delegate right to action **sign** on the signing task.
2. Send a notification to the signee using the correspondance API

#### Signing

1. A signee signs data elements
2. The person in charge of moving the process out of the sign task is notified using the correspondance API

We will refer to step 1 as the **action** and step 2 as the **message**

## Decision drivers

- B1: We must not send a **message** unless the **action** is successful
- B2: We must guarantee "at least once delivery" of the **message** if the **action** is successful
- B3: We should guarantee "exactly once delivery" of the **message** if the **action** is successful
- B4: We should not rollback idempotent steps if they are to be redone.

## Alternatives considered

- A1: Rollback - We use a transaction scope. If a step fails then we rollback the previous steps. We retry the transction.
- A2: State - We store transaction state. We retry, skipping the previously succesful steps.

## Pros and cons

List the pros and cons with the alternatives. This should be in regards to the decision drivers.

### A1

- Good, because this alternative adheres to all descision drivers
- Bad, because it does not fullfill the B4 decision driver, leading to unnecessary traffic.

### A2

- Good, because it adheres to all **must** decision drivers.
45 changes: 45 additions & 0 deletions doc/adr/template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# A short descriptive title - what is the outcome of this adr?

- Status: Proposed | Accepted | Exceeded by "adr-name"
- Deciders: People | Team
- Date: Proposal date

## Result

Which alternative was chosen. Do not include any reasoning here.

## Problem context

The problem you want to solve, any relevant context.

## Decision drivers

A list of decision drivers. These are points which can differ in importance. If a point is "nice to have" rather than
"need to have", then prefix the description.

Examples

- B1: The solution make it easier for app developers to develop an app.
- B2: Nice to have: The solution should be simple to implement for out team.

## Alternatives considered

List the alternatives that were considered as a solution to the problem context.

- A1: A solution to the problem.
- A2: Another solution to the problem

## Pros and cons

List the pros and cons with the alternatives. This should be in regards to the decision drivers.

### A1

- Good, because this alternative adheres to B1.
- Optional explanation as to how.
- Bad, because it does not fullfill the B2 decision driver.

### A2

- Good, because this alternative adheres to B2.
- Bad, because it does not fullfill the B1 decision driver.
1 change: 1 addition & 0 deletions src/Altinn.App.Api/Altinn.App.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

<!-- SonarCloud requires a ProjectGuid to separate projects. -->
<ProjectGuid>{E8F29FE8-6B62-41F1-A08C-2A318DD08BB4}</ProjectGuid>
<DebugType>portable</DebugType>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions src/Altinn.App.Api/Controllers/ProcessController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,7 @@ private static string EnsureActionNotTaskType(string actionOrTaskType)
{
case "data":
case "feedback":
case "signing":
return "write";
case "confirmation":
return "confirm";
Expand Down
187 changes: 187 additions & 0 deletions src/Altinn.App.Api/Controllers/SigningController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
using Altinn.App.Api.Infrastructure.Filters;
using Altinn.App.Api.Models;
using Altinn.App.Core.Features.Signing.Interfaces;
using Altinn.App.Core.Features.Signing.Models;
using Altinn.App.Core.Helpers;
using Altinn.App.Core.Helpers.Serialization;
using Altinn.App.Core.Internal.App;
using Altinn.App.Core.Internal.Data;
using Altinn.App.Core.Internal.Instances;
using Altinn.App.Core.Internal.Process;
using Altinn.App.Core.Internal.Process.Elements.AltinnExtensionProperties;
using Altinn.App.Core.Models;
using Altinn.Platform.Storage.Interface.Models;
using Microsoft.AspNetCore.Mvc;
using SigneeState = Altinn.App.Api.Models.SigneeState;

namespace Altinn.App.Api.Controllers;

/// <summary>
/// Controller for handling signing operations.
/// </summary>
[AutoValidateAntiforgeryTokenIfAuthCookie]
[ApiController]
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
[Route("{org}/{app}/instances/{instanceOwnerPartyId:int}/{instanceGuid:guid}/signing")]
public class SigningController : ControllerBase
{
private readonly IInstanceClient _instanceClient;
private readonly IAppMetadata _appMetadata;
private readonly IDataClient _dataClient;
private readonly ModelSerializationService _modelSerialization;
private readonly IProcessReader _processReader;
private readonly ISigningService _signingService;

/// <summary>
/// Initializes a new instance of the <see cref="SigningController"/> class.
/// </summary>
public SigningController(
IServiceProvider serviceProvider,
IInstanceClient instanceClient,
IAppMetadata appMetadata,
IDataClient dataClient,
ModelSerializationService modelSerialization,
IProcessReader processReader
)
{
_instanceClient = instanceClient;
_appMetadata = appMetadata;
_dataClient = dataClient;
_modelSerialization = modelSerialization;
_processReader = processReader;
_signingService = serviceProvider.GetRequiredService<ISigningService>();
}

/// <summary>
/// Get updated signing state for the current signing task.
/// </summary>
/// <param name="org">unique identifier of the organisation responsible for the app</param>
/// <param name="app">application identifier which is unique within an organisation</param>
/// <param name="instanceOwnerPartyId">unique id of the party that this the owner of the instance</param>
/// <param name="instanceGuid">unique id to identify the instance</param>
/// <param name="language">The currently used language by the user (or null if not available)</param>
/// <returns>An object containing updated signee state</returns>
[HttpGet]
[ProducesResponseType(typeof(SingingStateResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetSigneesState(
[FromRoute] string org,
[FromRoute] string app,
[FromRoute] int instanceOwnerPartyId,
[FromRoute] Guid instanceGuid,
[FromQuery] string? language = null
)
{
Instance instance = await _instanceClient.GetInstance(app, org, instanceOwnerPartyId, instanceGuid);
ApplicationMetadata appMetadata = await _appMetadata.GetApplicationMetadata();

var cachedDataMutator = new InstanceDataUnitOfWork(
instance,
_dataClient,
_instanceClient,
appMetadata,
_modelSerialization
);

if (instance.Process.CurrentTask.AltinnTaskType != "signing")
{
return NotSigningTask();
}

AltinnSignatureConfiguration? signingConfiguration = _processReader
.GetAltinnTaskExtension(instance.Process.CurrentTask.ElementId)
?.SignatureConfiguration;

if (signingConfiguration == null)
{
throw new ApplicationConfigException("Signing configuration not found in AltinnTaskExtension");
}

List<SigneeContext> signeeContexts = await _signingService.GetSigneeContexts(
cachedDataMutator,
signingConfiguration
);

var response = new SingingStateResponse
{
SigneeStates = signeeContexts
.Select(signeeContext => new SigneeState
{
Name = signeeContext.PersonSignee?.DisplayName ?? signeeContext.OrganisationSignee?.DisplayName,
Organisation = signeeContext.OrganisationSignee?.DisplayName,
HasSigned = signeeContext.SignDocument is not null,
DelegationSuccessful = signeeContext.SigneeState.IsAccessDelegated,
NotificationSuccessful =
signeeContext.SigneeState
is { SignatureRequestEmailSent: false, SignatureRequestSmsSent: false },
PartyId = signeeContext.Party.PartyId,
})
.ToList(),
};

return Ok(response);
}

/// <summary>
/// Get the data elements being signed in the current signature task.
/// </summary>
/// <param name="org"></param>
/// <param name="app"></param>
/// <param name="instanceOwnerPartyId"></param>
/// <param name="instanceGuid"></param>
/// <param name="language"></param>
/// <returns>An object containing the documents to be signed</returns>
/// <exception cref="ApplicationConfigException"></exception>
[HttpGet("data-elements")]
[ProducesResponseType(typeof(SigningDataElementsResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<IActionResult> GetDataElements(
[FromRoute] string org,
[FromRoute] string app,
[FromRoute] int instanceOwnerPartyId,
[FromRoute] Guid instanceGuid,
[FromQuery] string? language = null
)
{
Instance instance = await _instanceClient.GetInstance(app, org, instanceOwnerPartyId, instanceGuid);

if (instance.Process.CurrentTask.AltinnTaskType != "signing")
{
return NotSigningTask();
}

AltinnSignatureConfiguration? signingConfiguration = _processReader
.GetAltinnTaskExtension(instance.Process.CurrentTask.ElementId)
?.SignatureConfiguration;

if (signingConfiguration == null)
{
throw new ApplicationConfigException("Signing configuration not found in AltinnTaskExtension");
}

List<DataElement> dataElements = instance
.Data.Where(x => signingConfiguration.DataTypesToSign.Contains(x.DataType))
.ToList();

foreach (DataElement dataElement in dataElements)
{
SelfLinkHelper.SetDataAppSelfLinks(instanceOwnerPartyId, instanceGuid, dataElement, Request);
}

SigningDataElementsResponse response = new() { DataElements = dataElements };

return Ok(response);
}

private BadRequestObjectResult NotSigningTask()
{
return BadRequest(
new ProblemDetails
{
Title = "Not a signing task",
Detail = "This endpoint is only callable while the current task is a signing task.",
Status = StatusCodes.Status400BadRequest,
}
);
}
}
Loading
Loading