using System.Net; using System.Security.Authentication; using Autofac; using FitConnect.Encryption; using FitConnect.Models; using FitConnect.Models.v1.Api; using FitConnect.Services; using FitConnect.Services.Interfaces; using FitConnect.Services.Models.v1.Submission; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; using NJsonSchema; using NJsonSchema.Validation; using SecurityEventToken = FitConnect.Models.SecurityEventToken; namespace FitConnect; public abstract class FitConnectClient { public const string GuidPattern = "^[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}$"; public const string LeikaKeyPattern = "^urn:[a-z0-9][a-z0-9-]{0,31}:[a-z0-9()+,.:=@;$_!*'%/?#-]+$"; private readonly string? _privateKeyDecryption; private readonly string? _privateKeySigning; private readonly string? _publicKeyEncryption; private readonly string? _publicKeySignatureVerification; protected readonly bool VerifiedCertificatesAreMandatory; protected FitConnectClient(FitConnectEnvironment environment, string clientId, string clientSecret, ILogger? logger = null, string? privateKeyDecryption = null, string? privateKeySigning = null, string? publicKeyEncryption = null, string? publicKeySignatureVerification = null ) : this() { _privateKeyDecryption = privateKeyDecryption; _privateKeySigning = privateKeySigning; _publicKeyEncryption = publicKeyEncryption; _publicKeySignatureVerification = publicKeySignatureVerification; OAuthService = new OAuthService(environment.TokenUrl, "V1", clientId, clientSecret, logger); SubmissionService = new SubmissionService(environment.SubmissionUrl[0], OAuthService, publicKeySignatureVerification == null ? null : new JsonWebKey(publicKeySignatureVerification), logger: logger); RouteService = new RouteService(environment.RoutingUrl, logger: logger); CasesService = new CasesService(environment.SubmissionUrl[0], OAuthService, logger: logger); DestinationService = new DestinationService(environment.SubmissionUrl[0], OAuthService, logger: logger); Logger = logger; VerifiedCertificatesAreMandatory = environment.VerifiedCertificatesAreMandatory; } protected FitConnectClient(FitConnectEnvironment environment, string clientId, string clientSecret, IContainer container, string? privateKeyDecryption = null, string? privateKeySigning = null, string? publicKeyEncryption = null, string? publicKeySignatureVerification = null ) : this() { _privateKeyDecryption = privateKeyDecryption; _privateKeySigning = privateKeySigning; _publicKeyEncryption = publicKeyEncryption; _publicKeySignatureVerification = publicKeySignatureVerification; OAuthService = container.Resolve<IOAuthService>(); SubmissionService = container.Resolve<ISubmissionService>(); RouteService = container.Resolve<IRouteService>(); CasesService = container.Resolve<ICasesService>(); DestinationService = container.Resolve<IDestinationService>(); Logger = container.Resolve<ILogger>(); } private FitConnectClient() { // Take care of OAuthService if make the constructor public Encryption = new FitEncryption(Logger); } public IOAuthService OAuthService { get; } = null!; // To resolve the warning, is only applicable for the **private** constructor protected ISubmissionService SubmissionService { get; } = null!; protected IRouteService RouteService { get; } = null!; protected IDestinationService DestinationService { get; } = null!; protected ICasesService CasesService { get; } = null!; protected ILogger? Logger { get; } protected FitEncryption Encryption { get; set; } /// <summary> /// Validates <paramref name="dataString" /> against <paramref name="schemaString" /> and returns /// the validation errors if any appear /// </summary> public static async Task<ICollection<ValidationError>> ValidateSchemaAsync( string dataString, string schemaString) { var schema = await JsonSchema.FromJsonAsync(schemaString); return schema.Validate(dataString); } public void SetProxy(WebProxy proxy) { OAuthService.Proxy = proxy; SubmissionService.Proxy = proxy; DestinationService.Proxy = proxy; RouteService.Proxy = proxy; } public WebProxy? GetProxy() { return OAuthService.Proxy; } /// <summary> /// Retrieve the events for the submission /// </summary> /// <param name="caseId"></param> /// <param name="destinationId"></param> /// <param name="skipTest"></param> /// <returns></returns> public async Task<List<SecurityEventToken>> GetEventLogAsync(string caseId, string destinationId, bool skipTest = false) { var events = (await SubmissionService.GetEventLogAsync(caseId, destinationId, skipTest)) ?.Select(SecurityEventToken.FromJwtEncodedString).ToList() ?? new List<SecurityEventToken>(); return events; } /// <summary> /// Retrieve the events for the submission /// </summary> /// <param name="submission"></param> /// <param name="skipTest"></param> /// <returns></returns> public async Task<List<SecurityEventToken>> GetEventLogAsync( SubmissionForPickupDto submission, bool skipTest = false) { if (submission?.CaseId == null || submission.DestinationId == null) throw new ArgumentNullException(nameof(submission)); var events = await SubmissionService .GetEventLogAsync(submission.CaseId, submission.DestinationId, skipTest); return events?.Select(SecurityEventToken.FromJwtEncodedString).ToList() ?? new List<SecurityEventToken>(); } /// <summary> /// /// </summary> /// <param name="submission"></param> /// <returns></returns> public async Task<SubmissionState> GetStatusForSubmissionAsync( SentSubmission submission) { var eventLog = await GetEventLogAsync(submission.CaseId, submission.DestinationId); var status = eventLog.Last(e => e.SubmissionId == submission.SubmissionId); List<Problems>? problems = null; if (status.Events?.AcceptSubmissionEvent != null) { if (!(status.Events.AcceptSubmissionEvent.AuthenticationTags ?? throw new InvalidOperationException()).AreEqualTo(submission .AuthenticationTags)) { throw new AuthenticationTagException("Authentication tags are not equal"); } problems = status.Events.AcceptSubmissionEvent.Problems; } else if (status.Events?.RejectSubmissionEvent != null) problems = status.Events.RejectSubmissionEvent.Problems; return new SubmissionState(status.EventType, problems); } /// <summary> /// Encrypt attachments (Anhänge) /// </summary> /// <param name="publicKey"></param> /// <param name="attachment"></param> /// <returns></returns> public KeyValuePair<string, string> Encrypt(string publicKey, Attachment attachment) { var content = Encryption.Encrypt(attachment.Content ?? Array.Empty<byte>(), publicKey); return new KeyValuePair<string, string>(attachment.Id, content); } /// <summary> /// Encrypt attachments (Anhänge) /// </summary> /// <param name="publicKey">Public key for encryption</param> /// <param name="attachments">List of attachments to encrypt</param> /// <returns></returns> public Dictionary<string, string> Encrypt(string publicKey, IEnumerable<Attachment> attachments) { return attachments.Select(a => Encrypt(publicKey, a)) .ToDictionary(p => p.Key, p => p.Value); } } public static class FitConnectClientExtensions { /// <summary> /// </summary> /// <param name="host"></param> /// <param name="port"></param> /// <param name="username"></param> /// <param name="password"></param> /// <returns></returns> public static T WithProxy<T>(this T caller, string host, int port, string? username = null, string? password = null) where T : FitConnectClient { var proxy = new WebProxy(host, port); if (username != null && password != null) proxy.Credentials = new NetworkCredential(username, password); caller.SetProxy(proxy); return caller; } }