-
Klaus Fischer authoredKlaus Fischer authored
FluentSender.cs 8.97 KiB
using System.Runtime.Intrinsics.Arm;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.RegularExpressions;
using FitConnect.Encryption;
using FitConnect.Interfaces;
using FitConnect.Models;
using FitConnect.RestService;
using FitConnect.Services;
using FitConnect.Services.Models;
using FitConnect.Services.Models.v1;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace FitConnect;
/// <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 FluentSender : Sender, IFluentSender, IFluentSenderWithDestination,
IFluentSenderWithAttachments, IFluentSenderWithData, IFluentSenderWithService {
private State _nextStep = State.Authenticate;
private OAuthAccessToken? token;
public FluentSender(FitConnectEndpoints endpoints,
IOAuthService oAuthService,
ISubmissionService submissionService,
IRouteService routeService,
IDestinationService destinationService,
ILogger? logger = null) : base(endpoints, oAuthService, submissionService,
routeService, destinationService, logger) {
}
public string? PublicKey { get; set; }
public CreateSubmissionDto? NewSubmission { get; set; }
public Submission? Submission { get; set; }
public IFluentSenderWithDestination FindDestinationId(string leiaKey, string? ags, string? ars,
string? areaId) {
var destinationId = RouteService.GetDestinationIdAsync(leiaKey, ags, ars, areaId).Result;
return WithDestination(destinationId);
}
public IFluentSenderWithDestination FindDestinationId(Destination destination) {
throw new NotImplementedException();
}
public IFluentSenderWithDestination WithDestination(string destinationId) {
if (!Regex.IsMatch(destinationId,
"^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"))
throw new ArgumentException("The destination must be a valid GUID");
Submission = CreateSubmission(destinationId).Result;
return this;
}
public IFluentSenderWithService WithService(string serviceName, string leikaKey) {
throw new NotImplementedException();
}
public IFluentSenderWithData WithData(string data) {
try {
JsonConvert.DeserializeObject(data);
}
catch (Exception e) {
throw new ArgumentException("The data must be valid JSON string", e);
}
Submission!.Data = Data.FromString(data);
return this;
}
/// <summary>
/// Authenticates with the FitConnect API.
/// </summary>
/// <param name="clientId">The client id from the submission service</param>
/// <param name="clientSecret">The client secret</param>
/// <param name="scope">Optional scope for the credentials</param>
/// <returns></returns>
private FluentSender
Authenticate(string clientId, string clientSecret, string? scope = null) {
try {
if (_nextStep != State.Authenticate)
throw new InvalidOperationException("Client not ready for authentication.");
token ??= AuthenticateAsync(clientId, clientSecret, scope).Result;
if (token == null)
throw new AuthenticationException(
"Failed to authenticate with FitConnect. Token is null.");
_nextStep = State.CreateSubmission;
return this;
}
catch (Exception e) {
throw new AuthenticationException("Failed to authenticate with FitConnect", e);
}
}
/// <summary>
/// Creates a new <see cref="Submission" /> on the FitConnect server.
/// </summary>
/// <param name="destinationId">The id of the destination the submission has to be sent to</param>
/// <returns></returns>
/// <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) {
Authenticate(Owner.ClientId, Owner.ClientSecret);
PublicKey = await GetPublicKeyFromDestination(destinationId);
Encryption = new FitEncryption(Logger) { PublicKeyEncryption = PublicKey };
var submission = new Submission() {
DestinationId = destinationId
};
_nextStep = State.UploadAttachments;
return submission;
}
private void IntroduceSubmission(Submission submission) {
if (submission.ServiceType == null)
throw new ArgumentException("Submission has no service type.");
SubmissionService.CreateSubmission((CreateSubmissionDto)submission);
}
private async Task<string> GetPublicKeyFromDestination(string destinationId) {
DestinationService.Token = token!.AccessToken;
SubmissionService.Token = token!.AccessToken;
var publicKey = await DestinationService.GetPublicKey(destinationId);
return publicKey;
}
/// <summary>
///
/// </summary>
/// <param name="attachments"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
/// <exception cref="ArgumentException"></exception>
public IFluentSenderWithAttachments WithAttachments(IEnumerable<Attachment> attachments) {
Submission!.Attachments = new List<Attachment>();
foreach (var attachment in attachments) {
Submission!.Attachments.Add(attachment);
}
if (Submission.ServiceType == null)
throw new ArgumentException("Submission has no service type.");
var created = SubmissionService.CreateSubmission((CreateSubmissionDto)Submission);
Submission.Id = created.SubmissionId;
Submission.CaseId = created.CaseId;
Logger.LogInformation("Submission Id {CreatedSubmissionId}, CaseId {SubmissionCaseId}",
created.SubmissionId, Submission.CaseId);
var encryptedAttachments = Encrypt(PublicKey!, Submission.Attachments);
UploadAttachmentsAsync(Submission.Id!, encryptedAttachments).Wait();
return this;
}
private enum State {
Authenticate,
CreateSubmission,
UploadAttachments,
SendSubmission,
Complete
}
IFluentSender IFluentSenderReady.Submit() {
var metadata = CreateMetadata(Submission);
Logger?.LogDebug(metadata);
Logger?.LogInformation("Sending submission");
var encryptedMeta = Encryption.Encrypt(metadata);
Submission.EncryptedMetadata =
"eyJ6aXAiOiJERUYiLCJraWQiOiJEOUdrck51S2dhRWJSOHM0VkotdEtqRVBYalJpQ0FOWV9NN1NqbHllY0prIiwiY3R5IjoiYXBwbGljYXRpb25cL2pzb24iLCJlbmMiOiJBMjU2R0NNIiwiYWxnIjoiUlNBLU9BRVAtMjU2In0.bX_5aCg25BkWsQ-woCZgxRs0w-6dhyy_ACNJ3XyumlfqhngtDKlYifHR1VWiEWgT4wCBpmruHoTVjxbu9SfHeq6ciZYGXnMxH6Ihn15bnLsSVhtTzOnqWkICuSXnBW1DSOWXxyehn1iiuEWROED4iiLLhHx0teee5w7mSaW94etKjq0PmeOl5W8CF_XpMuvrJG0myEtGJT38KusZi6Omc_gjs72EbBqs9o7i5f0T-mMWScncBy1cuqJQmxRFFot2AZVyxK-UzgV9CfVtytJLhpeAtexVngiDtLaW76_rOaM_jGkUCGcfbXy1RiLSM8XY63bekJhbZJJszDY8wnlYUh6TEimjSP2FjTek9QocnvSkVCg3_t95S5wcoYKU08hdHBVtn_pGRE30EvSeqWOWtVIj9O_QlqrK8nY9i1nvfqxcmj25Lj98mppphETlE2m2__QEoK-1rPE6UjEhs369-u9UGOEuRw7rGfFHGmWUb_17jRJrOjDbdMgvfLmQzsI1elKfpcKlcL6GP0OLglQejfZX1uNUe0_PuVgqI2a5_W0iIsKKMfwLT2Qkq91mRMpjVHzU36YvhZhWmNWchfXojifkBsLrRvz-ACEcJDbtVwoMmnmIUf0FzGR3NZRFdzbTqBtoPRVlX0DZwoiE9YTZsrD44bnE0JYyEu_HZfN7GN4.8N5m-PPDscv_Mopi.oHcKjjnpGMnV6J4lzvn1hW0Ghx8DQKVmzFSGZxYcwtswx7NX3Fg2TWipChtzBeND0GDsHerB4uGpWXh-tXKgKFQf2PG4ETUcyD_dviSCHH2fBuc3NVEIcpwxgG1-aLuYy_lZPPLE8gldFsPp47awJEyIhoFe3kqed_yemQ34kvcJ7q0mpaEj31odrmeWtlM4bxbagJKbrUiGsCTG_67My3P2O1QkTzGnfjFcfEWECCnkLX4wG-51C3Gh8zkM7bjWARgXg21MbPt50u3nBlv02XHLLrJJAsGSUAS0Z9iyuRj75z0ERi1QRwP8OI0hVFqMKevVwXLd5AJrvRmQQJ45wpgCaTtmIikffNGc7JhwWKZIVvy6WBc2JV71guB5V0u_G7glYynQ3d1QnCIAL-PB4eHSpPG20ezX4XVWpff8kHykGJfxQ82D1lT5WKdF99RBb0f0pftuiFrT4gjXqzX21iMV2BKOSExk78umUkU4qx_fC-lK6oFuKoScMQXvS9TWB0OxeayTUu4ttVqDT1ojDSc3T6044-W31VV6LsfmLVd5PaPHgrGpqkfPT1NgG7mw5MeX8OC4.xT8u8qFzZAUVa7Jb9WrDbQ"; //encryptedMeta;
var result = SubmissionService
.SubmitSubmission(Submission.Id!, (SubmitSubmissionDto)Submission).Result;
return this;
}
public IFluentSenderWithService WithServiceType(string serviceName, string leikaKey) {
if (!Regex.IsMatch(leikaKey, "^urn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,.:=@;$_!*'%/?#-]+$"))
throw new ArgumentException("Invalid leika key");
Submission.ServiceType = new ServiceType() {
Name = serviceName,
Identifier = leikaKey
};
return this;
}
}