Skip to content
Snippets Groups Projects
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;
    }
}