Skip to content

Commit

Permalink
Download signDocuments and synchronize with SigneeContexts. Enables c…
Browse files Browse the repository at this point in the history
…hecking signature status.
  • Loading branch information
bjorntore committed Jan 16, 2025
1 parent f0f70a4 commit 7858a9d
Show file tree
Hide file tree
Showing 5 changed files with 320 additions and 50 deletions.
48 changes: 34 additions & 14 deletions src/Altinn.App.Api/Controllers/SigningController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
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;
Expand All @@ -23,6 +26,9 @@ namespace Altinn.App.Api.Controllers;
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;

Expand All @@ -32,10 +38,16 @@ public class SigningController : ControllerBase
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>();
}
Expand All @@ -61,6 +73,15 @@ public async Task<IActionResult> GetSigneesState(
)
{
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")
{
Expand All @@ -76,25 +97,24 @@ public async Task<IActionResult> GetSigneesState(
throw new ApplicationConfigException("Signing configuration not found in AltinnTaskExtension");
}

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

Random rnd = new Random();
var response = new SingingStateResponse
{
SigneeStates = signeeContexts
.Select(signeeContext =>
.Select(signeeContext => new SigneeState
{
return new SigneeState
{
Name = signeeContext.PersonSignee?.DisplayName ?? signeeContext.OrganisationSignee?.DisplayName,
Organisation = signeeContext.OrganisationSignee?.DisplayName,
HasSigned = rnd.Next(1, 10) > 5, //TODO: When and where to check if signee has signed?
DelegationSuccessful = signeeContext.SigneeState.IsAccessDelegated is false,
NotificationSuccessful =
signeeContext.SigneeState
is { SignatureRequestEmailSent: false, SignatureRequestSmsSent: false },
PartyId = signeeContext.Party.PartyId,
};
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(),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,8 @@ Task<List<SigneeContext>> ProcessSignees(
CancellationToken ct
);

Task<List<SigneeContext>> GetSigneeContexts(Instance instance, AltinnSignatureConfiguration signatureConfiguration);
Task<List<SigneeContext>> GetSigneeContexts(
IInstanceDataMutator instanceMutator,
AltinnSignatureConfiguration signatureConfiguration
);
}
8 changes: 8 additions & 0 deletions src/Altinn.App.Core/Features/Signing/Models/SigneeContext.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using Altinn.Platform.Register.Models;
using Altinn.Platform.Storage.Interface.Models;

namespace Altinn.App.Core.Features.Signing.Models;

Expand Down Expand Up @@ -34,4 +35,11 @@ internal sealed class SigneeContext
/// </summary>
[JsonPropertyName("personSignee")]
public PersonSignee? PersonSignee { get; set; }

/// <summary>
/// The signature document, if it exists yet.
/// </summary>
/// <remarks>This is not and should not be serialized and persisted in storage, it's looked up on-the-fly when the signee contexts are retrieved through <see cref="SigningService.GetSigneeContexts"/></remarks>
[JsonIgnore]
public SignDocument? SignDocument { get; set; }
}
167 changes: 132 additions & 35 deletions src/Altinn.App.Core/Features/Signing/SigningService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,14 @@
using Altinn.App.Core.Features.Signing.Interfaces;
using Altinn.App.Core.Features.Signing.Mocks;
using Altinn.App.Core.Features.Signing.Models;
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.Elements.AltinnExtensionProperties;
using Altinn.App.Core.Internal.Registers;
using Altinn.App.Core.Models;
using Altinn.Platform.Register.Models;
using Altinn.Platform.Storage.Interface.Models;
using Microsoft.Extensions.Logging;
using JsonException = Newtonsoft.Json.JsonException;

namespace Altinn.App.Core.Features.Signing;

Expand All @@ -26,19 +24,11 @@ internal sealed class SigningService(
ISigningDelegationService signingDelegationService,
// ISigningNotificationService signingNotificationService,
IEnumerable<ISigneeProvider> signeeProviders,
IDataClient dataClient,
IInstanceClient instanceClient,
ModelSerializationService modelSerialization,
IAppMetadata appMetadata,
ILogger<SigningService> logger,
Telemetry? telemetry = null
) : ISigningService
{
private static readonly JsonSerializerOptions _jsonSerializerOptions = new(JsonSerializerDefaults.Web);
private readonly IDataClient _dataClient = dataClient;
private readonly IInstanceClient _instanceClient = instanceClient;
private readonly ModelSerializationService _modelSerialization = modelSerialization;
private readonly IAppMetadata _appMetadata = appMetadata;
private readonly ILogger<SigningService> _logger = logger;
private const string ApplicationJsonContentType = "application/json";

Expand Down Expand Up @@ -66,7 +56,7 @@ CancellationToken ct
{
using Activity? activity = telemetry?.StartAssignSigneesActivity();

var instance = instanceMutator.Instance;
Instance instance = instanceMutator.Instance;
string taskId = instance.Process.CurrentTask.ElementId;

SigneesResult? signeesResult = await GetSignees(instance, signatureConfiguration);
Expand Down Expand Up @@ -127,36 +117,18 @@ CancellationToken ct
}

public async Task<List<SigneeContext>> GetSigneeContexts(
Instance instance,
IInstanceDataMutator instanceMutator,
AltinnSignatureConfiguration signatureConfiguration
)
{
using Activity? activity = telemetry?.StartReadSigneesActivity();
// TODO: Get signees from state
ApplicationMetadata appMetadata = await _appMetadata.GetApplicationMetadata();

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

// ! TODO: Remove nullable
IEnumerable<DataElement> dataElements = cachedDataMutator.GetDataElementsForType(
signatureConfiguration.SigneeStatesDataTypeId!
);
List<SigneeContext> signeeContexts = await DownloadSigneeContexts(instanceMutator, signatureConfiguration);
List<SignDocument> signDocuments = await DownloadSignDocuments(instanceMutator, signatureConfiguration);

DataElement signeeStateDataElement = dataElements.Single();
ReadOnlyMemory<byte> data = await cachedDataMutator.GetBinaryData(signeeStateDataElement);
string asString = Encoding.UTF8.GetString(data.ToArray());
await SynchronizeSigneeContextsWithSignDocuments(instanceMutator, signeeContexts, signDocuments);

var result = JsonSerializer.Deserialize<SigneeContext[]>(asString, _jsonSerializerOptions) ?? [];

return [.. result];

// TODO: Get signees from policy??
return signeeContexts;
}

//TODO: There is already logic for the sign action in the SigningUserAction class. Maybe move most of it here?
Expand Down Expand Up @@ -287,6 +259,131 @@ await organisationClient.GetOrganization(organisationSignee.OrganisationNumber)
}
);
}

return organisationSigneeContexts;
}

private static async Task<List<SigneeContext>> DownloadSigneeContexts(
IInstanceDataMutator instanceMutator,
AltinnSignatureConfiguration signatureConfiguration
)
{
string signeeStatesDataTypeId =
signatureConfiguration.SigneeStatesDataTypeId
?? throw new ApplicationConfigException(
"SigneeStatesDataTypeId is not set in the signature configuration."
);

IEnumerable<DataElement> dataElements = instanceMutator.GetDataElementsForType(signeeStatesDataTypeId);

DataElement signeeStateDataElement =
dataElements.SingleOrDefault()
?? throw new ApplicationException(
$"Failed to find the data element containing signee contexts using dataTypeId {signatureConfiguration.SigneeStatesDataTypeId}."
);

ReadOnlyMemory<byte> data = await instanceMutator.GetBinaryData(signeeStateDataElement);
string signeeStateSerialized = Encoding.UTF8.GetString(data.ToArray());

List<SigneeContext> signeeContexts =
JsonSerializer.Deserialize<List<SigneeContext>>(signeeStateSerialized, _jsonSerializerOptions) ?? [];

return signeeContexts;
}

private static async Task<List<SignDocument>> DownloadSignDocuments(
IInstanceDataMutator instanceMutator,
AltinnSignatureConfiguration signatureConfiguration
)
{
List<DataElement> signatureDataElements = instanceMutator
.Instance.Data.Where(x => x.DataType == signatureConfiguration.SignatureDataType)
.ToList();

List<SignDocument> signDocuments = [];
//TODO: Is GetBinaryData safe to do in parallel? If so, do it.
foreach (DataElement signatureDataElement in signatureDataElements)
{
ReadOnlyMemory<byte> data = await instanceMutator.GetBinaryData(signatureDataElement);
string signDocumentSerialized = Encoding.UTF8.GetString(data.ToArray());

SignDocument signDocument =
JsonSerializer.Deserialize<SignDocument>(signDocumentSerialized, _jsonSerializerOptions)
?? throw new JsonException("Could not deserialize signature document.");

signDocuments.Add(signDocument);
}

return signDocuments;
}

/// <summary>
/// This method exists to ensure we have a SigneeContext for both signees that have been delegated access to sign and signees that have signed using access granted through the policy.xml file.
/// </summary>
private async Task SynchronizeSigneeContextsWithSignDocuments(
IInstanceDataMutator instanceMutator,
List<SigneeContext> signeeContexts,
List<SignDocument> signDocuments
)
{
foreach (SignDocument signDocument in signDocuments)
{
SigneeContext? matchingSigneeContext = signeeContexts.FirstOrDefault(x =>
x.PersonSignee?.SocialSecurityNumber == signDocument.SigneeInfo.PersonNumber
|| x.OrganisationSignee?.OrganisationNumber == signDocument.SigneeInfo.OrganisationNumber
);

if (matchingSigneeContext is not null)
{
// If the signee has been delegated access to sign there will be a matching SigneeContext. Setting the sign document property on this context.
matchingSigneeContext.SignDocument = signDocument;
}
else
{
// If the signee has signed using access granted through the policy.xml file, there is no persisted signee context. We create a signee context on the fly.
Party party = await altinnPartyClient.LookupParty(
new PartyLookup
{
Ssn = signDocument.SigneeInfo.PersonNumber,
OrgNo = signDocument.SigneeInfo.OrganisationNumber,
}
);

PersonSignee? personSignee = party.Person is not null
? new PersonSignee
{
SocialSecurityNumber = party.Person.SSN,
DisplayName = party.Person.Name,
FullName = party.Person.Name,
OnBehalfOfOrganisation = party.Organization?.Name,
}
: null;

OrganisationSignee? organisationSignee = party.Organization is not null
? new OrganisationSignee
{
OrganisationNumber = party.Organization.OrgNumber,
DisplayName = party.Organization.Name,
}
: null;

signeeContexts.Add(
new SigneeContext
{
TaskId = instanceMutator.Instance.Process.CurrentTask.ElementId,
Party = party,
PersonSignee = personSignee,
OrganisationSignee = organisationSignee,
SigneeState = new SigneeState()
{
IsAccessDelegated = true,
SignatureRequestEmailSent = true,
SignatureRequestSmsSent = true,
IsReceiptSent = false,
},
}
);
}
}
}
}
Loading

0 comments on commit 7858a9d

Please sign in to comment.