Newer
Older
using System.Security;
using System.Text.RegularExpressions;
using Autofac;
using FitConnect.Models;
using FitConnect.Models.Api.Metadata;
using Microsoft.Extensions.Logging;
using Attachment = FitConnect.Models.Attachment;
using Data = FitConnect.Models.Api.Metadata.Data;
using Metadata = FitConnect.Models.Metadata;
/// <summary>
/// The fluent implementation for the <see cref="Sender" /> to encapsulate the FitConnect API.
/// </summary>
/// <example>
/// Reference for FluentSender
/// <code>
/// client.Sender
/// .Authenticate(clientId!, clientSecret!)
/// .CreateSubmission(new Submission { Attachments = new List() })
/// .UploadAttachments()
/// .WithData(data)
/// .Subm@it();
/// </code>
/// </example>
public class Sender : FitConnectClient, ISender {
internal Sender(FitConnectEnvironment environment, string clientId, string clientSecret,
ILogger? logger = null) : base(environment, clientId, clientSecret, logger) {
public Sender(FitConnectEnvironment environment, string clientId, string clientSecret,
IContainer container) : base(environment, clientId, clientSecret,
container) {
}
public async Task<string> GetPublicKeyForDestinationAsync(string destinationId) {
var publicKey = await DestinationService.GetPublicKey(destinationId);
var keyIsValid = new CertificateHelper(Logger).ValidateCertificateJsonWebKeyString(
publicKey,
VerifiedCertificatesAreMandatory ? LogLevel.Error : LogLevel.Warning);
public async Task<Submission> SendAsync(SendableSubmission submission) {
var sendable = await CreateSubmission(submission.DestinationId);
sendable.AddServiceType(submission.ServiceName!, submission.LeikaKey!);
sendable!.Attachments = new List<Attachment>();
if (submission.Attachments != null)
foreach (var attachment in submission.Attachments)
sendable!.Attachments.Add(attachment);
var created =
await SubmissionService.CreateSubmissionAsync((CreateSubmissionDto)sendable);
sendable.Id = created.SubmissionId;
sendable.CaseId = created.CaseId;
Logger?.LogInformation("Submission Id {CreatedSubmissionId}, CaseId {SubmissionCaseId}",
created.SubmissionId, sendable.CaseId);
var encryptedAttachments = Encrypt(PublicKey!, sendable.Attachments);
await UploadAttachmentsAsync(sendable.Id!, encryptedAttachments);
return await Submit(sendable);
/// <summary>
/// Sender for pre-encrypted data
/// </summary>
/// <param name="submission"></param>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public async Task<Submitted> SendAsync(SendableEncryptedSubmission submission) {
var sendable = await CreateSubmission(submission.DestinationId);
sendable.AddServiceType(submission.ServiceName!, submission.LeikaKey!);
sendable.EncryptedData = submission.Data;
sendable.EncryptedMetadata = submission.Metadata;
announcedSubmission.AnnouncedAttachments = submission.Attachments?.Keys.ToList();
var created =
await SubmissionService.CreateSubmissionAsync(announcedSubmission);
sendable.Id = created.SubmissionId;
sendable.CaseId = created.CaseId;
Logger?.LogInformation("Submission Id {CreatedSubmissionId}, CaseId {SubmissionCaseId}",
created.SubmissionId, sendable.CaseId);
Logger?.LogInformation("Uploading pre encrypted attachments");
await UploadAttachmentsAsync(sendable.Id!, submission.Attachments!);
public async Task<List<Route>> FindDestinationId(string leiaKey, string? ags = null,
string? ars = null,
string? areaId = null) {
if (ags == null && ars == null && areaId == null)
throw new ArgumentException("One of the following must be provided: ags, ars, areaId");
var routes = await RouteService.GetDestinationIdAsync(leiaKey, ags, ars, areaId);
Logger?.LogInformation("Received destinations: {Destinations}",
routes.Select(d => d.DestinationId).Aggregate((a, b) => a + "," + b));
return routes;
}
private async Task<Submission> Submit(Submission submission) {
if (submission == null) {
Logger?.LogCritical("Submission is null on submit");
throw new InvalidOperationException("Submission is not ready");
if (submission.EncryptedMetadata == null) {
var metadata = CreateMetadata(submission);
Logger?.LogTrace("MetaData: {Metadata}", metadata);
var valid = await JsonHelper.VerifyMetadata(metadata, null);
if (!valid) {
Logger?.LogError("Sending submission aborted due to validation errors");
throw new InvalidOperationException("Submission is not ready");
}
Logger?.LogInformation("MetaData validation check, done");
Logger?.LogInformation("Sending submission");
var encryptedMeta = Encryption.Encrypt(metadata);
Logger?.LogTrace("Encrypted metadata: {EncryptedMeta}", encryptedMeta);
submission.EncryptedMetadata = encryptedMeta;
if (submission.EncryptedData == null)
if (submission.Data != null)
submission.EncryptedData = Encryption.Encrypt(submission.Data);
var result = await SubmissionService
.SubmitSubmission(submission.Id!, (SubmitSubmissionDto)submission);
Logger?.LogInformation("Submission sent");
return submission;
/// Creates a new <see cref="SendableSubmission" /> on the FitConnect server.
/// <param name="destinationId">The id of the destination the submission has to be sent to</param>
/// <exception cref="InvalidOperationException">If sender is not authenticated</exception>
/// <exception cref="ArgumentException">If submission is not ready to be sent</exception>
private async Task<Submission> CreateSubmission(string destinationId) {
PublicKey = await GetPublicKeyForDestinationAsync(destinationId);
Encryption = new FitEncryption(Logger) { PublicKeyEncryption = PublicKey };
var submission = new Submission {
DestinationId = destinationId
};
return submission;
}
/// Create Metadata incl. Hash
/// <param name="submission"></param>
internal static string CreateMetadata(Submission submission,
DataHash? dataHash = null) {
Hash = dataHash ?? FitEncryption.CalculateHash(submission.Data!),
SubmissionSchema = new Fachdatenschema {
SchemaUri = submission.ServiceType.Identifier,
MimeType = submission.DataMimeType ??
MediaTypeNames.Application.Json
}
};
var contentStructure = new ContentStructure {
Data = data,
Description = a.Description,
AttachmentId = a.Id,
Schema = Metadata.SchemaUrl,
ContentStructure = contentStructure,
PublicServiceType = new Verwaltungsleistung {
Identifier = submission.ServiceType.Identifier,
Name = submission.ServiceType.Name
}
};
return JsonConvert.SerializeObject(metaData);
/// </summary>
/// <param name="filter"></param>
/// <param name="offset"></param>
/// <param name="limit"></param>
/// <returns></returns>
public async Task<IEnumerable<Area>> GetAreas(string filter, int offset = 0,
var dto = await RouteService.GetAreas(filter, offset, limit);
// totalCount = dto?.TotalCount ?? 0;
return dto?.Areas ?? new List<Area>();
}
/// Uploading the encrypted data to the server
/// <param name="submissionId">Submissions ID</param>
/// <param name="encryptedAttachments">Encrypted attachments with id and content</param>
private async Task<bool> UploadAttachmentsAsync(string submissionId,
try {
foreach (var (id, content) in encryptedAttachments) {
Logger?.LogInformation("Uploading attachment {Id}", id);
await SubmissionService.UploadAttachment(submissionId, id, content);
}
return true;
}
catch (Exception e) {
Logger?.LogError("Error Uploading attachment {message}", e.Message);
throw;
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
private static void SubmissionSenderGuards(ISendableSubmission submission) {
if (submission.DestinationId == null)
throw new ArgumentNullException(nameof(SendableSubmission.DestinationId));
if (!Regex.IsMatch(submission.DestinationId, GuidPattern))
throw new ArgumentException("The destination must be a valid GUID");
if (submission.ServiceName == null)
throw new ArgumentNullException(nameof(SendableSubmission.ServiceName));
if (submission.LeikaKey == null)
throw new ArgumentNullException(nameof(SendableSubmission.LeikaKey));
if (submission.Data == null)
throw new ArgumentNullException(nameof(SendableSubmission.Data));
}
}
public static class SubmissionSenderExtension {
public static void AddServiceType(this Submission submission, string serviceName,
string leikaKey) {
if (string.IsNullOrWhiteSpace(leikaKey) || !Regex.IsMatch(leikaKey,
FitConnectClient.LeikaKeyPattern))
throw new ArgumentException("Invalid leika key");
submission.ServiceType = new ServiceType {
Name = serviceName,
Identifier = leikaKey
};
}
public static void AddData(this Submission submission, string data) {
try {
JsonConvert.DeserializeObject(data);
}
catch (Exception e) {
throw new ArgumentException("The data must be valid JSON string", e);
}
submission.Data = data;
}