diff --git a/Documentation/working_notes.md b/Documentation/working_notes.md index 91b76b4a0db6dd3058c0fe7392d4bf4edd2a3c84..089b933e93f1064fa4bc9dc0825798d333bf6d8c 100644 --- a/Documentation/working_notes.md +++ b/Documentation/working_notes.md @@ -25,13 +25,24 @@ ## Links +### Documentation + - [SDK-Konzept im Wiki](https://wiki.fit-connect.fitko.dev/de/Konzeption/Konzeption_SDK) - [inoffizielles Python-SDK](https://github.com/codedust/fitconnect-sdk-python) - [Project management](https://wiki.fit-connect.fitko.dev/de/PM_PUBLIC/Projektvorgehensmodell) - [Wiki SDK Description](https://wiki.fit-connect.fitko.dev/de/PM_PUBLIC/Epics/SDK_Initialisierung) - [Containing GitLab](https://git.fitko.de/) - [Board filtered for SDK](https://git.fitko.de/fit-connect/planning/-/boards/44?label_name%5B%5D=component%3A%3ASDK) -- [Documentation](https://docs.fitko.de/fit-connect/docs/getting-started/first-steps/) +- [FitConnect First Steps](https://docs.fitko.de/fit-connect/docs/getting-started/first-steps/) - [Security Event Token Requirements](https://wiki.fit-connect.fitko.dev/de/Konzeption/Security_Event_Token_Future) - [glossary](https://docs.fitko.de/fit-connect/docs/glossary/) +### Helper +- [Create C# class from JSON schema](https://app.quicktype.io/?l=csharp) + +## Values for testing + +| Topic | Value | Description | +|:-------------------|:---------------|:----------------------------------------| +| Leika Key | 99099002067003 | Deutsche Staatsangehörigkeit beantragen | +| Gemeinde Schlüssel | 09 3 72 126 | Furth im Wald | \ No newline at end of file diff --git a/DummyClient/Program.cs b/DummyClient/Program.cs index c60290a01226fe8d94fc2f2aab570b5a1510fcde..6152e9850e85ec03a55979738eaade640c1805dd 100644 --- a/DummyClient/Program.cs +++ b/DummyClient/Program.cs @@ -9,6 +9,7 @@ var clientSecret = ""; PublicKey publicKey; ILogger _logger; Client client; +X509Certificate2 certificate; /* * The easy way to call the FitConnect API @@ -29,8 +30,8 @@ async Task AbstractCall() { void FluentSenderCall() { client.Sender .Authenticate(clientId!, clientSecret!) - .CreateSubmission(new Submission()) - .UploadAttachments(new List<Attachment>()) + .CreateSubmission(new Submission { Attachments = new List<Attachment>() }) + .UploadAttachments() .SendSubmission(new Metadata(), new Data()); } @@ -61,7 +62,7 @@ void FluentSubscriberCall() { async Task DetailSenderCall() { var sender = new Sender( - FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development), certificate); var submissionDto = sender.AddSubmission(new Submission()); var encryptedAttachments = sender.Encrypt(publicKey, new List<Attachment>()); @@ -79,7 +80,7 @@ async Task DetailSenderCall() { async Task DetailSubscriberCall() { var subscriber = new Subscriber( - FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development), certificate); var submissions = await subscriber.GetSubmissionsAsync("destinationId"); /* .... diff --git a/E2ETests/SenderTest.cs b/E2ETests/SenderTest.cs index 1a08672b9973011adaaee9371d281788b3ce3d5b..c5010494aa61c15ff044c161cbfde0b8bbac87bc 100644 --- a/E2ETests/SenderTest.cs +++ b/E2ETests/SenderTest.cs @@ -1,7 +1,11 @@ using System; using System.IO; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; using FitConnect; +using FitConnect.Security; using FluentAssertions; +using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using NUnit.Framework; @@ -44,7 +48,8 @@ public class SenderTest { [SetUp] public void Setup() { _sender = new Sender( - FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development), + RsaEncryption.CreateSelfSignedCertificate()); } [Test] diff --git a/FitConnect/BaseClasses/FunctionalBaseClass.cs b/FitConnect/BaseClasses/FunctionalBaseClass.cs index 0483bf91e4eeec23934e796155c68e7d0290e239..f8a291fdef8cf1e032599de57e31912dd0294b44 100644 --- a/FitConnect/BaseClasses/FunctionalBaseClass.cs +++ b/FitConnect/BaseClasses/FunctionalBaseClass.cs @@ -11,24 +11,26 @@ public abstract class FunctionalBaseClass { protected readonly FitConnectApiService ApiService; public readonly IEncryption Encryption; protected readonly ILogger? Logger; + protected readonly X509Certificate2 Certificate; internal Client Owner { get; set; } /// <summary> /// Constructor for the FunctionalBaseClass /// </summary> - /// <param name="logger">ILogger implementation</param> /// <param name="endpoints">FitConnect endpoints</param> /// <param name="certificate">The Encryption certificate</param> + /// <param name="logger">ILogger implementation</param> /// <example> /// new Sender(logger, FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)) /// </example> - protected FunctionalBaseClass(ILogger? logger, FitConnectEndpoints? endpoints, - X509Certificate2? certificate) { + protected FunctionalBaseClass(FitConnectEndpoints? endpoints, + X509Certificate2 certificate, ILogger? logger) { Endpoints = endpoints ?? FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development); Logger = logger; + Certificate = certificate; Encryption = new RsaEncryption(logger, certificate); ApiService = new FitConnectApiService(Endpoints, logger); } @@ -43,7 +45,8 @@ public abstract class FunctionalBaseClass { /// <param name="clientSecret"></param> /// <param name="scope"></param> /// <returns></returns> - public Task<OAuthAccessToken?> AuthenticateAsync(string clientId, string clientSecret, string? scope) { + public Task<OAuthAccessToken?> AuthenticateAsync(string clientId, string clientSecret, + string? scope) { return ApiService.OAuthService.AuthenticateAsync(clientId, clientSecret, scope); } diff --git a/FitConnect/FluentSender.cs b/FitConnect/FluentSender.cs index 2867a5bf671fd65232fec799f01f32809eeec577..028ef8fb38f09f39b9baabed637a82c493e2c895 100644 --- a/FitConnect/FluentSender.cs +++ b/FitConnect/FluentSender.cs @@ -30,6 +30,7 @@ public class FluentSender : Sender { /// <exception cref="InvalidOperationException"></exception> /// <exception cref="ArgumentException"></exception> public FluentSender CreateSubmission(Submission submission) { + this.Submission = submission; if (token == null) throw new InvalidOperationException("You must authenticate first."); @@ -41,26 +42,36 @@ public class FluentSender : Sender { return this; } + public Submission Submission { get; set; } + public FluentSender SendSubmission() { throw new NotImplementedException(); } - public FluentSender UploadAttachments(List<Attachment> attachments) { + public FluentSender UploadAttachments() { if (NewSubmission == null) throw new InvalidOperationException("You must create a submission first."); - if (attachments.Count == 0) + if (Submission.Attachments.Count == 0) return this; - var encryptedAttachments = Encrypt(PublicKey, attachments); + var encryptedAttachments = Encrypt(PublicKey, Submission.Attachments); UploadAttachmentsAsync(encryptedAttachments).Wait(); return this; } public FluentSender SendSubmission(Metadata metadata, Data? data = null) { - throw new NotImplementedException(); + if (NewSubmission == null) + throw new InvalidOperationException("You must create a submission first."); + + Submission.Metadata = metadata; + Submission.Data = data; + var submitSubmissionDto = CreateSubmitSubmissionDto(Submission); + ApiService.SubmissionService.SubmitSubmission(Submission.Id, submitSubmissionDto); + return this; } + public FluentSender(FitConnectEndpoints endpoints, X509Certificate2? certificate = null, ILogger? logger = null) : base(endpoints, certificate, logger) { diff --git a/FitConnect/Models/FitConnectException.cs b/FitConnect/Models/FitConnectException.cs new file mode 100644 index 0000000000000000000000000000000000000000..70fecf2f3bf2e3888512c0837561e9f98401d5db --- /dev/null +++ b/FitConnect/Models/FitConnectException.cs @@ -0,0 +1,17 @@ +namespace FitConnect.Models; + +/// <summary> +/// Representation of FitConnect error responses +/// </summary> +public class FitConnectException : Exception { + public enum ErrorTypeEnum { + Unknown, + } + + public ErrorTypeEnum ErrorType { get; set; } + + public FitConnectException(string message, ErrorTypeEnum errorType = ErrorTypeEnum.Unknown, + Exception? innerException = null) : base(message, innerException) { + ErrorType = errorType; + } +} diff --git a/FitConnect/Models/Metadata.cs b/FitConnect/Models/Metadata.cs index 8caccfe8ed2d4f0decaf77e6b527e09c2c2a8ec7..c9b798a0cc8f1bbcdae795cf166d42bccede59cd 100644 --- a/FitConnect/Models/Metadata.cs +++ b/FitConnect/Models/Metadata.cs @@ -1,5 +1,21 @@ +using System.Security.Cryptography.X509Certificates; +using FitConnect.Models; + namespace FitConnect.Models; -public record Metadata; +public interface IEncrypt { + public string Encrypt(PublicKey publicKey); +} + +public class Metadata : IEncrypt { + public string Encrypt(PublicKey publicKey) { + return ""; + } +} + -public record Data; +public class Data : IEncrypt { + public string Encrypt(PublicKey publicKey) { + return ""; + } +} diff --git a/FitConnect/Models/ServiceType.cs b/FitConnect/Models/ServiceType.cs index 236a8ceb53a64ecde082ca9499c79a86a3776d02..aee540c41d4e5ab046c98d4f913040843db4c074 100644 --- a/FitConnect/Models/ServiceType.cs +++ b/FitConnect/Models/ServiceType.cs @@ -2,7 +2,7 @@ using FitConnect.Services.Models; namespace FitConnect.Models; -public record ServiceType { +public class ServiceType { public string? Name { get; set; } public string? Description { get; set; } @@ -23,4 +23,7 @@ public record ServiceType { Name = model.Name }; } + + public bool IsValid() => + (!string.IsNullOrWhiteSpace(Name)) && (!string.IsNullOrWhiteSpace(Identifier)); } diff --git a/FitConnect/Models/Submission.cs b/FitConnect/Models/Submission.cs index e12ed517037f720824acf4940e6ea5a076e848d2..c58dd0492e0ff3fe9482cebea09e5068d487f127 100644 --- a/FitConnect/Models/Submission.cs +++ b/FitConnect/Models/Submission.cs @@ -1,9 +1,10 @@ +using System.Security.Cryptography.X509Certificates; using FitConnect.Services.Models; namespace FitConnect.Models; -public record Submission { - public string? Id { get; set; } +public class Submission { + public string Id { get; set; } public string DestinationId { get; init; } public string? CaseId { get; set; } @@ -13,9 +14,22 @@ public record Submission { public Callback? Callback { get; set; } + public bool IsSubmissionReadyToAdd(out string? error) { - error = null; - return true; + var innerError = ""; + if (string.IsNullOrEmpty(DestinationId)) innerError += "DestinationId is required\r\n"; + + if (ServiceType.IsValid()) { + innerError += "ServiceType is invalid\r\n"; + } + + if (string.IsNullOrWhiteSpace(innerError)) { + error = null; + return true; + } + + error = innerError.Trim(); + return false; } public bool IsSubmissionReadyToSend() { @@ -26,7 +40,9 @@ public record Submission { /// <summary> /// Fachdaten /// </summary> - public string? Data { get; set; } + public Data? Data { get; set; } + + public Metadata Metadata { get; set; } public static explicit operator Submission(SubmissionForPickupDto dto) { return new() { diff --git a/FitConnect/Sender.cs b/FitConnect/Sender.cs index c265c97d8305fb05a4c47de4ae8d6a0745cf52be..a074f513981a931067bfba5aa0750beec1cec5cb 100644 --- a/FitConnect/Sender.cs +++ b/FitConnect/Sender.cs @@ -7,9 +7,10 @@ using Microsoft.Extensions.Logging; namespace FitConnect; public partial class Sender : FunctionalBaseClass { + public Sender(FitConnectEndpoints endpoints, - X509Certificate2? certificate = null, ILogger? logger = null) : base(logger, endpoints, - certificate) { + X509Certificate2 certificate, ILogger? logger = null) : base(endpoints, + certificate, logger) { } @@ -144,4 +145,11 @@ public partial class Sender : FunctionalBaseClass { public async Task<bool> SubmitAsync(string encryptedData, string encryptedMetadata) { throw new NotImplementedException(); } + + protected SubmitSubmissionDto CreateSubmitSubmissionDto(Submission submission) { + return new() { + EncryptedData = submission.Data?.Encrypt(this.Certificate.PublicKey), + EncryptedMetadata = submission.Metadata.Encrypt(Certificate.PublicKey), + }; + } } diff --git a/FitConnect/Subscriber.cs b/FitConnect/Subscriber.cs index a9a53deba643bc6760240f52e781082a707658d1..846a1bc27a08e9613fda19dfbad40112cc68cc5a 100644 --- a/FitConnect/Subscriber.cs +++ b/FitConnect/Subscriber.cs @@ -10,8 +10,8 @@ namespace FitConnect; public class Subscriber : FunctionalBaseClass { public Subscriber(FitConnectEndpoints endpoints, - X509Certificate2? certificate = null, ILogger? logger = null) : base(logger, endpoints, - certificate) { + X509Certificate2 certificate, ILogger? logger = null) : base(endpoints, + certificate, logger) { } @@ -110,4 +110,4 @@ public class Subscriber : FunctionalBaseClass { int limit = 100) { throw new NotImplementedException(); } -} \ No newline at end of file +} diff --git a/Models/Models.csproj b/Models/Models.csproj index 42326e5c3ec81e82a29317e741d328d2d68ef0b9..245b8c8d902f44288fb973cbc21b2653b5342dcf 100644 --- a/Models/Models.csproj +++ b/Models/Models.csproj @@ -7,4 +7,8 @@ <RootNamespace>FitConnect.Models</RootNamespace> </PropertyGroup> + <ItemGroup> + <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> + </ItemGroup> + </Project> diff --git a/Services/FitConnectApiService.cs b/Services/FitConnectApiService.cs index 26cc5d9922b62af28918743bd8b415de101a52f3..4453ddc98e7c869ed371e5859133f2aa726bfaba 100644 --- a/Services/FitConnectApiService.cs +++ b/Services/FitConnectApiService.cs @@ -7,7 +7,7 @@ public class FitConnectApiService { protected readonly DestinationService DestinationService; public readonly OAuthService OAuthService; public readonly RouteService RouteService; - protected readonly SubmissionService SubmissionService; + public readonly SubmissionService SubmissionService; public FitConnectApiService(FitConnectEndpoints endpoints, ILogger? logger = null) { CasesService = new CasesService(endpoints.SubmissionUrl); diff --git a/Services/Models/Api/Metadata.cs b/Services/Models/Api/Metadata.cs new file mode 100644 index 0000000000000000000000000000000000000000..adb00423acc5f7729507d4e42e6db8773aa5bd57 --- /dev/null +++ b/Services/Models/Api/Metadata.cs @@ -0,0 +1,1122 @@ +// <auto-generated /> +// +// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do: +// +// using FitConnect; +// +// var metadata = Metadata.FromJson(jsonString); + +namespace FitConnect.Models.Api.Metadata +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class Metadata + { + /// <summary> + /// Eine Struktur, um zusätzliche Informationen zu hinterlegen + /// </summary> + [JsonProperty("additionalReferenceInfo", NullValueHandling = NullValueHandling.Ignore)] + public AdditionalReferenceInfo AdditionalReferenceInfo { get; set; } + + /// <summary> + /// Eine Liste aller Identifikationsnachweise der Einreichung. + /// </summary> + [JsonProperty("authenticationInformation", NullValueHandling = NullValueHandling.Ignore)] + public List<AuthenticationInformation> AuthenticationInformation { get; set; } + + /// <summary> + /// Beschreibt die Struktur der zusätzlichen Inhalte der Einreichung, wie Anlagen oder + /// Fachdaten. + /// </summary> + [JsonProperty("contentStructure")] + public ContentStructure ContentStructure { get; set; } + + /// <summary> + /// Dieses Objekt enthält die Informationen vom Bezahldienst. + /// </summary> + [JsonProperty("paymentInformation", NullValueHandling = NullValueHandling.Ignore)] + public PaymentInformation PaymentInformation { get; set; } + + /// <summary> + /// Beschreibung der Art der Verwaltungsleistung. Eine Verwaltungsleistung sollte immer mit + /// einer LeiKa-Id beschrieben werden. Ist für die gegebene Verwaltungsleistung keine + /// LeiKa-Id vorhanden, kann die Verwaltungsleistung übergangsweise über die Angabe einer + /// anderen eindeutigen Schema-URN beschrieben werden. + /// </summary> + [JsonProperty("publicServiceType", NullValueHandling = NullValueHandling.Ignore)] + public Verwaltungsleistung PublicServiceType { get; set; } + + [JsonProperty("replyChannel", NullValueHandling = NullValueHandling.Ignore)] + public ReplyChannel ReplyChannel { get; set; } + } + + /// <summary> + /// Eine Struktur, um zusätzliche Informationen zu hinterlegen + /// </summary> + public partial class AdditionalReferenceInfo + { + /// <summary> + /// Das Datum der Antragstellung. Das Datum muss nicht zwingend identisch mit dem Datum der + /// Einreichung des Antrags über FIT-Connect sein. + /// </summary> + [JsonProperty("applicationDate", NullValueHandling = NullValueHandling.Ignore)] + public DateTimeOffset? ApplicationDate { get; set; } + + /// <summary> + /// Eine Referenz zum Vorgang im sendenden System, um bei Problemen und Rückfragen + /// außerhalb von FIT-Connect den Vorgang im dortigen System schneller zu identifizieren. + /// </summary> + [JsonProperty("senderReference", NullValueHandling = NullValueHandling.Ignore)] + public string SenderReference { get; set; } + } + + /// <summary> + /// Eine Struktur, die einen Identifikationsnachweis beschreibt. + /// </summary> + public partial class AuthenticationInformation + { + /// <summary> + /// Der Nachweis wird als Base64Url-kodierte Zeichenkette angegeben. + /// </summary> + [JsonProperty("content")] + public string Content { get; set; } + + /// <summary> + /// Definiert die Art des Identifikationsnachweises. + /// </summary> + [JsonProperty("type")] + public AuthenticationInformationType Type { get; set; } + + /// <summary> + /// semver kompatible Versionsangabe des genutzten Nachweistyps. + /// </summary> + [JsonProperty("version")] + public string Version { get; set; } + } + + /// <summary> + /// Beschreibt die Struktur der zusätzlichen Inhalte der Einreichung, wie Anlagen oder + /// Fachdaten. + /// </summary> + public partial class ContentStructure + { + [JsonProperty("attachments")] + public List<Attachment> Attachments { get; set; } + + /// <summary> + /// Definiert das Schema und die Signatur(-art), die für die Fachdaten verwendet werden. + /// </summary> + [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] + public Data Data { get; set; } + } + + /// <summary> + /// Eine in der Einreichung enthaltene Anlage. + /// </summary> + public partial class Attachment + { + /// <summary> + /// Innerhalb einer Einreichung eindeutige Id der Anlage im Format einer UUIDv4. + /// </summary> + [JsonProperty("attachmentId")] + public Guid AttachmentId { get; set; } + + /// <summary> + /// Optionale Beschreibung der Anlage + /// </summary> + [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] + public string Description { get; set; } + + /// <summary> + /// Ursprünglicher Dateiname bei Erzeugung oder Upload + /// </summary> + [JsonProperty("filename", NullValueHandling = NullValueHandling.Ignore)] + public string Filename { get; set; } + + /// <summary> + /// Der Hashwert der unverschlüsselten Anlage. Die Angabe des Hashwertes dient der + /// Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Anlage durch + /// Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst). + /// </summary> + [JsonProperty("hash")] + public AttachmentHash Hash { get; set; } + + /// <summary> + /// Internet Media Type gemäß RFC 2045, z. B. application/pdf. + /// </summary> + [JsonProperty("mimeType")] + public string MimeType { get; set; } + + /// <summary> + /// Zweck/Art der Anlage + /// - form: Automatisch generierte PDF-Repräsentation des vollständigen Antragsformulars + /// - attachment: Anlage, die von einem Bürger hochgeladen wurde + /// - report: Vom Onlinedienst, nachträglich erzeugte Unterlage + /// </summary> + [JsonProperty("purpose")] + public Purpose Purpose { get; set; } + + [JsonProperty("signature", NullValueHandling = NullValueHandling.Ignore)] + public AttachmentSignature Signature { get; set; } + } + + /// <summary> + /// Der Hashwert der unverschlüsselten Anlage. Die Angabe des Hashwertes dient der + /// Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Anlage durch + /// Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst). + /// </summary> + public partial class AttachmentHash + { + /// <summary> + /// Der Hex-kodierte Hashwert gemäß des angegebenen Algorithmus. + /// </summary> + [JsonProperty("content")] + public string Content { get; set; } + + /// <summary> + /// Der verwendete Hash-Algorithmus. Derzeit ist nur `sha512` erlaubt. + /// </summary> + [JsonProperty("type")] + public HashType Type { get; set; } + } + + /// <summary> + /// Beschreibt das Signaturformt und Profile + /// </summary> + public partial class AttachmentSignature + { + /// <summary> + /// Hier wird die Signatur im Falle einer Detached-Signatur als Base64- oder + /// Base64Url-kodierte Zeichenkette hinterlegt. Eine Base64Url-Kodierung kommt nur bei + /// Einsatz von JSON Web Signatures (JWS / JAdES) zum Einsatz. + /// </summary> + [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] + public string Content { get; set; } + + /// <summary> + /// Beschreibt, ob die Signatur als seperate (detached) Signatur (`true`) oder als Teil des + /// Fachdatensatzes bzw. der Anlage (`false`) übertragen wird. Wenn der Wert `true` ist, + /// dann wird die Signatur Base64- oder Base64Url-kodiert im Feld `content` übertragen. + /// </summary> + [JsonProperty("detachedSignature")] + public bool DetachedSignature { get; set; } + + /// <summary> + /// Referenziert ein eindeutiges Profil einer AdES (advanced electronic signature/seal) + /// gemäß eIDAS-Verordnung über eine URI gemäß [ETSI TS 119 + /// 192](https://www.etsi.org/deliver/etsi_ts/119100_119199/119192/01.01.01_60/ts_119192v010101p.pdf). + /// + /// Für die Details zur Verwendung und Validierung von Profilen siehe auch + /// https://ec.europa.eu/cefdigital/DSS/webapp-demo/doc/dss-documentation.html#_signatures_profile_simplification + /// </summary> + [JsonProperty("eidasAdesProfile", NullValueHandling = NullValueHandling.Ignore)] + public EidasAdesProfile? EidasAdesProfile { get; set; } + + /// <summary> + /// Beschreibt, welches Signaturformat die genutzte Signatur / das genutzte Siegel nutzt. + /// Aktuell wird die Hinterlegung folgender Signaturformate unterstützt: CMS = Cryptographic + /// Message Syntax, Asic = Associated Signature Containers, PDF = PDF Signatur, XML = + /// XML-Signature, JSON = JSON Web Signature. + /// </summary> + [JsonProperty("signatureFormat")] + public SignatureFormat SignatureFormat { get; set; } + } + + /// <summary> + /// Definiert das Schema und die Signatur(-art), die für die Fachdaten verwendet werden. + /// </summary> + public partial class Data + { + /// <summary> + /// Der Hashwert der unverschlüsselten Fachdaten. Die Angabe des Hashwertes dient der + /// Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Fachdaten + /// durch Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst). + /// </summary> + [JsonProperty("hash")] + public DataHash Hash { get; set; } + + /// <summary> + /// Beschreibt das Signaturformt und Profile + /// </summary> + [JsonProperty("signature", NullValueHandling = NullValueHandling.Ignore)] + public DataSignature Signature { get; set; } + + /// <summary> + /// Referenz auf ein Schema, das die Struktur der Fachdaten einer Einreichung beschreibt. + /// </summary> + [JsonProperty("submissionSchema")] + public Fachdatenschema SubmissionSchema { get; set; } + } + + /// <summary> + /// Der Hashwert der unverschlüsselten Fachdaten. Die Angabe des Hashwertes dient der + /// Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Fachdaten + /// durch Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst). + /// </summary> + public partial class DataHash + { + /// <summary> + /// Der Hex-kodierte Hashwert gemäß des angegebenen Algorithmus. + /// </summary> + [JsonProperty("content")] + public string Content { get; set; } + + /// <summary> + /// Der verwendete Hash-Algorithmus. Derzeit ist nur `sha512` erlaubt. + /// </summary> + [JsonProperty("type")] + public HashType Type { get; set; } + } + + /// <summary> + /// Beschreibt das Signaturformt und Profile + /// </summary> + public partial class DataSignature + { + /// <summary> + /// Hier wird die Signatur im Falle einer Detached-Signatur als Base64- oder + /// Base64Url-kodierte Zeichenkette hinterlegt. Eine Base64Url-Kodierung kommt nur bei + /// Einsatz von JSON Web Signatures (JWS / JAdES) zum Einsatz. + /// </summary> + [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] + public string Content { get; set; } + + /// <summary> + /// Beschreibt, ob die Signatur als seperate (detached) Signatur (`true`) oder als Teil des + /// Fachdatensatzes bzw. der Anlage (`false`) übertragen wird. Wenn der Wert `true` ist, + /// dann wird die Signatur Base64- oder Base64Url-kodiert im Feld `content` übertragen. + /// </summary> + [JsonProperty("detachedSignature")] + public bool DetachedSignature { get; set; } + + /// <summary> + /// Referenziert ein eindeutiges Profil einer AdES (advanced electronic signature/seal) + /// gemäß eIDAS-Verordnung über eine URI gemäß [ETSI TS 119 + /// 192](https://www.etsi.org/deliver/etsi_ts/119100_119199/119192/01.01.01_60/ts_119192v010101p.pdf). + /// + /// Für die Details zur Verwendung und Validierung von Profilen siehe auch + /// https://ec.europa.eu/cefdigital/DSS/webapp-demo/doc/dss-documentation.html#_signatures_profile_simplification + /// </summary> + [JsonProperty("eidasAdesProfile", NullValueHandling = NullValueHandling.Ignore)] + public EidasAdesProfile? EidasAdesProfile { get; set; } + + /// <summary> + /// Beschreibt, welches Signaturformat die genutzte Signatur / das genutzte Siegel nutzt. + /// Aktuell wird die Hinterlegung folgender Signaturformate unterstützt: CMS = Cryptographic + /// Message Syntax, Asic = Associated Signature Containers, PDF = PDF Signatur, XML = + /// XML-Signature, JSON = JSON Web Signature. + /// </summary> + [JsonProperty("signatureFormat")] + public SignatureFormat SignatureFormat { get; set; } + } + + /// <summary> + /// Referenz auf ein Schema, das die Struktur der Fachdaten einer Einreichung beschreibt. + /// </summary> + public partial class Fachdatenschema + { + /// <summary> + /// Mimetype (z.B. application/json oder application/xml) des referenzierten Schemas (z.B. + /// XSD- oder JSON-Schema). + /// </summary> + [JsonProperty("mimeType")] + public MimeType MimeType { get; set; } + + /// <summary> + /// URI des Fachschemas. Wird hier eine URL verwendet, sollte das Schema unter der + /// angegebenen URL abrufbar sein. Eine Verfügbarkeit des Schemas unter der angegebenen URL + /// darf jedoch nicht vorausgesetzt werden. + /// </summary> + [JsonProperty("schemaUri")] + public Uri SchemaUri { get; set; } + } + + /// <summary> + /// Dieses Objekt enthält die Informationen vom Bezahldienst. + /// </summary> + public partial class PaymentInformation + { + /// <summary> + /// Bruttobetrag + /// </summary> + [JsonProperty("grossAmount", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(MinMaxValueCheckConverter))] + public double? GrossAmount { get; set; } + + /// <summary> + /// Die vom Benutzer ausgewählte Zahlart. Das Feld ist nur bei einer erfolgreichen Zahlung + /// vorhanden / befüllt. + /// </summary> + [JsonProperty("paymentMethod")] + public PaymentMethod PaymentMethod { get; set; } + + /// <summary> + /// Weitere Erläuterung zur gewählten Zahlart. + /// </summary> + [JsonProperty("paymentMethodDetail", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(PurpleMinMaxLengthCheckConverter))] + public string PaymentMethodDetail { get; set; } + + /// <summary> + /// - INITIAL - der Einreichung hat einen Payment-Request ausgelöst und eine + /// Payment-Transaction wurde angelegt. Der Nutzer hat aber im Bezahldienst noch keine + /// Wirkung erzeugt. + /// - BOOKED - der Nutzer hat die Bezahlung im Bezahldienst autorisiert. + /// - FAILED - der Vorgang wurde vom Bezahldienst aufgrund der Nutzereingaben abgebrochen. + /// - CANCELED - der Nutzer hat die Bezahlung im Bezahldienst abgebrochen. + /// </summary> + [JsonProperty("status")] + public Status Status { get; set; } + + /// <summary> + /// Eine vom Bezahldienst vergebene Transaktions-Id. + /// </summary> + [JsonProperty("transactionId")] + [JsonConverter(typeof(PurpleMinMaxLengthCheckConverter))] + public string TransactionId { get; set; } + + /// <summary> + /// Bezahlreferenz bzw. Verwendungszweck, wie z. B. ein Kassenzeichen. + /// </summary> + [JsonProperty("transactionReference")] + public string TransactionReference { get; set; } + + /// <summary> + /// Zeitstempel der erfolgreichen Durchführung der Bezahlung. + /// </summary> + [JsonProperty("transactionTimestamp", NullValueHandling = NullValueHandling.Ignore)] + public DateTimeOffset? TransactionTimestamp { get; set; } + + /// <summary> + /// Die Rest-URL der Payment Transaction für die Statusabfrage. + /// </summary> + [JsonProperty("transactionUrl", NullValueHandling = NullValueHandling.Ignore)] + public Uri TransactionUrl { get; set; } + } + + /// <summary> + /// Beschreibung der Art der Verwaltungsleistung. Eine Verwaltungsleistung sollte immer mit + /// einer LeiKa-Id beschrieben werden. Ist für die gegebene Verwaltungsleistung keine + /// LeiKa-Id vorhanden, kann die Verwaltungsleistung übergangsweise über die Angabe einer + /// anderen eindeutigen Schema-URN beschrieben werden. + /// </summary> + public partial class Verwaltungsleistung + { + /// <summary> + /// (Kurz-)Beschreibung der Verwaltungsleistung + /// </summary> + [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] + public string Description { get; set; } + + /// <summary> + /// URN einer Leistung. Im Falle einer Leistung aus dem Leistungskatalog sollte hier + /// `urn:de:fim:leika:leistung:` vorangestellt werden. + /// </summary> + [JsonProperty("identifier")] + [JsonConverter(typeof(FluffyMinMaxLengthCheckConverter))] + public string Identifier { get; set; } + + /// <summary> + /// Name/Bezeichnung der Verwaltungsleistung + /// </summary> + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } + } + + public partial class ReplyChannel + { + /// <summary> + /// Akkreditierte Anbieter siehe + /// https://www.bsi.bund.de/DE/Themen/Oeffentliche-Verwaltung/Moderner-Staat/De-Mail/Akkreditierte-DMDA/akkreditierte-dmda_node.html + /// </summary> + [JsonProperty("deMail", NullValueHandling = NullValueHandling.Ignore)] + public DeMail DeMail { get; set; } + + /// <summary> + /// Siehe https://www.elster.de/elsterweb/infoseite/elstertransfer_hilfe_schnittstellen + /// </summary> + [JsonProperty("elster", NullValueHandling = NullValueHandling.Ignore)] + public Elster Elster { get; set; } + + [JsonProperty("eMail", NullValueHandling = NullValueHandling.Ignore)] + public EMail EMail { get; set; } + + /// <summary> + /// Postfachadresse in einem interoperablen Servicekonto (FINK.PFISK) + /// </summary> + [JsonProperty("fink", NullValueHandling = NullValueHandling.Ignore)] + public Fink Fink { get; set; } + } + + /// <summary> + /// Akkreditierte Anbieter siehe + /// https://www.bsi.bund.de/DE/Themen/Oeffentliche-Verwaltung/Moderner-Staat/De-Mail/Akkreditierte-DMDA/akkreditierte-dmda_node.html + /// </summary> + public partial class DeMail + { + [JsonProperty("address")] + public string Address { get; set; } + } + + public partial class EMail + { + [JsonProperty("address")] + public string Address { get; set; } + + /// <summary> + /// Hilfe zur Erstellung gibt es in der Dokumentation unter + /// https://docs.fitko.de/fit-connect/details/pgp-export + /// </summary> + [JsonProperty("pgpPublicKey", NullValueHandling = NullValueHandling.Ignore)] + public string PgpPublicKey { get; set; } + } + + /// <summary> + /// Siehe https://www.elster.de/elsterweb/infoseite/elstertransfer_hilfe_schnittstellen + /// </summary> + public partial class Elster + { + [JsonProperty("accountId")] + public string AccountId { get; set; } + + [JsonProperty("geschaeftszeichen", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(TentacledMinMaxLengthCheckConverter))] + public string Geschaeftszeichen { get; set; } + + [JsonProperty("lieferTicket", NullValueHandling = NullValueHandling.Ignore)] + public string LieferTicket { get; set; } + } + + /// <summary> + /// Postfachadresse in einem interoperablen Servicekonto (FINK.PFISK) + /// </summary> + public partial class Fink + { + /// <summary> + /// FINK Postfachadresse + /// </summary> + [JsonProperty("finkPostfachRef")] + [JsonConverter(typeof(StickyMinMaxLengthCheckConverter))] + public string FinkPostfachRef { get; set; } + + /// <summary> + /// URL des Servicekontos, in dem das Ziel-Postfach liegt + /// </summary> + [JsonProperty("host", NullValueHandling = NullValueHandling.Ignore)] + public Uri Host { get; set; } + } + + /// <summary> + /// Definiert die Art des Identifikationsnachweises. + /// </summary> + public enum AuthenticationInformationType { IdentificationReport }; + + /// <summary> + /// Der verwendete Hash-Algorithmus. Derzeit ist nur `sha512` erlaubt. + /// </summary> + public enum HashType { Sha512 }; + + /// <summary> + /// Zweck/Art der Anlage + /// - form: Automatisch generierte PDF-Repräsentation des vollständigen Antragsformulars + /// - attachment: Anlage, die von einem Bürger hochgeladen wurde + /// - report: Vom Onlinedienst, nachträglich erzeugte Unterlage + /// </summary> + public enum Purpose { Attachment, Form, Report }; + + /// <summary> + /// Referenziert ein eindeutiges Profil einer AdES (advanced electronic signature/seal) + /// gemäß eIDAS-Verordnung über eine URI gemäß [ETSI TS 119 + /// 192](https://www.etsi.org/deliver/etsi_ts/119100_119199/119192/01.01.01_60/ts_119192v010101p.pdf). + /// + /// Für die Details zur Verwendung und Validierung von Profilen siehe auch + /// https://ec.europa.eu/cefdigital/DSS/webapp-demo/doc/dss-documentation.html#_signatures_profile_simplification + /// </summary> + public enum EidasAdesProfile { HttpUriEtsiOrgAdes191X2LevelBaselineBB, HttpUriEtsiOrgAdes191X2LevelBaselineBLt, HttpUriEtsiOrgAdes191X2LevelBaselineBLta, HttpUriEtsiOrgAdes191X2LevelBaselineBT }; + + /// <summary> + /// Beschreibt, welches Signaturformat die genutzte Signatur / das genutzte Siegel nutzt. + /// Aktuell wird die Hinterlegung folgender Signaturformate unterstützt: CMS = Cryptographic + /// Message Syntax, Asic = Associated Signature Containers, PDF = PDF Signatur, XML = + /// XML-Signature, JSON = JSON Web Signature. + /// </summary> + public enum SignatureFormat { Asic, Cms, Json, Pdf, Xml }; + + /// <summary> + /// Mimetype (z.B. application/json oder application/xml) des referenzierten Schemas (z.B. + /// XSD- oder JSON-Schema). + /// </summary> + public enum MimeType { ApplicationJson, ApplicationXml }; + + /// <summary> + /// Die vom Benutzer ausgewählte Zahlart. Das Feld ist nur bei einer erfolgreichen Zahlung + /// vorhanden / befüllt. + /// </summary> + public enum PaymentMethod { Creditcard, Giropay, Invoice, Other, Paydirect, Paypal }; + + /// <summary> + /// - INITIAL - der Einreichung hat einen Payment-Request ausgelöst und eine + /// Payment-Transaction wurde angelegt. Der Nutzer hat aber im Bezahldienst noch keine + /// Wirkung erzeugt. + /// - BOOKED - der Nutzer hat die Bezahlung im Bezahldienst autorisiert. + /// - FAILED - der Vorgang wurde vom Bezahldienst aufgrund der Nutzereingaben abgebrochen. + /// - CANCELED - der Nutzer hat die Bezahlung im Bezahldienst abgebrochen. + /// </summary> + public enum Status { Booked, Canceled, Failed, Initial }; + + public partial class Metadata + { + public static Metadata FromJson(string json) => JsonConvert.DeserializeObject<Metadata>(json, FitConnect.Models.Api.Metadata.Converter.Settings); + } + + public static class Serialize + { + public static string ToJson(this Metadata self) => JsonConvert.SerializeObject(self, FitConnect.Models.Api.Metadata.Converter.Settings); + } + + internal static class Converter + { + public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings + { + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + DateParseHandling = DateParseHandling.None, + Converters = + { + AuthenticationInformationTypeConverter.Singleton, + HashTypeConverter.Singleton, + PurposeConverter.Singleton, + EidasAdesProfileConverter.Singleton, + SignatureFormatConverter.Singleton, + MimeTypeConverter.Singleton, + PaymentMethodConverter.Singleton, + StatusConverter.Singleton, + new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } + }, + }; + } + + internal class AuthenticationInformationTypeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(AuthenticationInformationType) || t == typeof(AuthenticationInformationType?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + if (value == "identificationReport") + { + return AuthenticationInformationType.IdentificationReport; + } + throw new Exception("Cannot unmarshal type AuthenticationInformationType"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (AuthenticationInformationType)untypedValue; + if (value == AuthenticationInformationType.IdentificationReport) + { + serializer.Serialize(writer, "identificationReport"); + return; + } + throw new Exception("Cannot marshal type AuthenticationInformationType"); + } + + public static readonly AuthenticationInformationTypeConverter Singleton = new AuthenticationInformationTypeConverter(); + } + + internal class HashTypeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(HashType) || t == typeof(HashType?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + if (value == "sha512") + { + return HashType.Sha512; + } + throw new Exception("Cannot unmarshal type HashType"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (HashType)untypedValue; + if (value == HashType.Sha512) + { + serializer.Serialize(writer, "sha512"); + return; + } + throw new Exception("Cannot marshal type HashType"); + } + + public static readonly HashTypeConverter Singleton = new HashTypeConverter(); + } + + internal class PurposeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(Purpose) || t == typeof(Purpose?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "attachment": + return Purpose.Attachment; + case "form": + return Purpose.Form; + case "report": + return Purpose.Report; + } + throw new Exception("Cannot unmarshal type Purpose"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (Purpose)untypedValue; + switch (value) + { + case Purpose.Attachment: + serializer.Serialize(writer, "attachment"); + return; + case Purpose.Form: + serializer.Serialize(writer, "form"); + return; + case Purpose.Report: + serializer.Serialize(writer, "report"); + return; + } + throw new Exception("Cannot marshal type Purpose"); + } + + public static readonly PurposeConverter Singleton = new PurposeConverter(); + } + + internal class EidasAdesProfileConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(EidasAdesProfile) || t == typeof(EidasAdesProfile?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "http://uri.etsi.org/ades/191x2/level/baseline/B-B#": + return EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBB; + case "http://uri.etsi.org/ades/191x2/level/baseline/B-LT#": + return EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBLt; + case "http://uri.etsi.org/ades/191x2/level/baseline/B-LTA#": + return EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBLta; + case "http://uri.etsi.org/ades/191x2/level/baseline/B-T#": + return EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBT; + } + throw new Exception("Cannot unmarshal type EidasAdesProfile"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (EidasAdesProfile)untypedValue; + switch (value) + { + case EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBB: + serializer.Serialize(writer, "http://uri.etsi.org/ades/191x2/level/baseline/B-B#"); + return; + case EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBLt: + serializer.Serialize(writer, "http://uri.etsi.org/ades/191x2/level/baseline/B-LT#"); + return; + case EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBLta: + serializer.Serialize(writer, "http://uri.etsi.org/ades/191x2/level/baseline/B-LTA#"); + return; + case EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBT: + serializer.Serialize(writer, "http://uri.etsi.org/ades/191x2/level/baseline/B-T#"); + return; + } + throw new Exception("Cannot marshal type EidasAdesProfile"); + } + + public static readonly EidasAdesProfileConverter Singleton = new EidasAdesProfileConverter(); + } + + internal class SignatureFormatConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(SignatureFormat) || t == typeof(SignatureFormat?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "asic": + return SignatureFormat.Asic; + case "cms": + return SignatureFormat.Cms; + case "json": + return SignatureFormat.Json; + case "pdf": + return SignatureFormat.Pdf; + case "xml": + return SignatureFormat.Xml; + } + throw new Exception("Cannot unmarshal type SignatureFormat"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (SignatureFormat)untypedValue; + switch (value) + { + case SignatureFormat.Asic: + serializer.Serialize(writer, "asic"); + return; + case SignatureFormat.Cms: + serializer.Serialize(writer, "cms"); + return; + case SignatureFormat.Json: + serializer.Serialize(writer, "json"); + return; + case SignatureFormat.Pdf: + serializer.Serialize(writer, "pdf"); + return; + case SignatureFormat.Xml: + serializer.Serialize(writer, "xml"); + return; + } + throw new Exception("Cannot marshal type SignatureFormat"); + } + + public static readonly SignatureFormatConverter Singleton = new SignatureFormatConverter(); + } + + internal class MimeTypeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(MimeType) || t == typeof(MimeType?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "application/json": + return MimeType.ApplicationJson; + case "application/xml": + return MimeType.ApplicationXml; + } + throw new Exception("Cannot unmarshal type MimeType"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (MimeType)untypedValue; + switch (value) + { + case MimeType.ApplicationJson: + serializer.Serialize(writer, "application/json"); + return; + case MimeType.ApplicationXml: + serializer.Serialize(writer, "application/xml"); + return; + } + throw new Exception("Cannot marshal type MimeType"); + } + + public static readonly MimeTypeConverter Singleton = new MimeTypeConverter(); + } + + internal class MinMaxValueCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(double) || t == typeof(double?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<double>(reader); + if (value >= 0.01) + { + return value; + } + throw new Exception("Cannot unmarshal type double"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (double)untypedValue; + if (value >= 0.01) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type double"); + } + + public static readonly MinMaxValueCheckConverter Singleton = new MinMaxValueCheckConverter(); + } + + internal class PaymentMethodConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(PaymentMethod) || t == typeof(PaymentMethod?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "CREDITCARD": + return PaymentMethod.Creditcard; + case "GIROPAY": + return PaymentMethod.Giropay; + case "INVOICE": + return PaymentMethod.Invoice; + case "OTHER": + return PaymentMethod.Other; + case "PAYDIRECT": + return PaymentMethod.Paydirect; + case "PAYPAL": + return PaymentMethod.Paypal; + } + throw new Exception("Cannot unmarshal type PaymentMethod"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (PaymentMethod)untypedValue; + switch (value) + { + case PaymentMethod.Creditcard: + serializer.Serialize(writer, "CREDITCARD"); + return; + case PaymentMethod.Giropay: + serializer.Serialize(writer, "GIROPAY"); + return; + case PaymentMethod.Invoice: + serializer.Serialize(writer, "INVOICE"); + return; + case PaymentMethod.Other: + serializer.Serialize(writer, "OTHER"); + return; + case PaymentMethod.Paydirect: + serializer.Serialize(writer, "PAYDIRECT"); + return; + case PaymentMethod.Paypal: + serializer.Serialize(writer, "PAYPAL"); + return; + } + throw new Exception("Cannot marshal type PaymentMethod"); + } + + public static readonly PaymentMethodConverter Singleton = new PaymentMethodConverter(); + } + + internal class PurpleMinMaxLengthCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(string); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + var value = serializer.Deserialize<string>(reader); + if (value.Length >= 1 && value.Length <= 36) + { + return value; + } + throw new Exception("Cannot unmarshal type string"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + var value = (string)untypedValue; + if (value.Length >= 1 && value.Length <= 36) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type string"); + } + + public static readonly PurpleMinMaxLengthCheckConverter Singleton = new PurpleMinMaxLengthCheckConverter(); + } + + internal class StatusConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(Status) || t == typeof(Status?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "BOOKED": + return Status.Booked; + case "CANCELED": + return Status.Canceled; + case "FAILED": + return Status.Failed; + case "INITIAL": + return Status.Initial; + } + throw new Exception("Cannot unmarshal type Status"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (Status)untypedValue; + switch (value) + { + case Status.Booked: + serializer.Serialize(writer, "BOOKED"); + return; + case Status.Canceled: + serializer.Serialize(writer, "CANCELED"); + return; + case Status.Failed: + serializer.Serialize(writer, "FAILED"); + return; + case Status.Initial: + serializer.Serialize(writer, "INITIAL"); + return; + } + throw new Exception("Cannot marshal type Status"); + } + + public static readonly StatusConverter Singleton = new StatusConverter(); + } + + internal class FluffyMinMaxLengthCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(string); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + var value = serializer.Deserialize<string>(reader); + if (value.Length >= 7 && value.Length <= 255) + { + return value; + } + throw new Exception("Cannot unmarshal type string"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + var value = (string)untypedValue; + if (value.Length >= 7 && value.Length <= 255) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type string"); + } + + public static readonly FluffyMinMaxLengthCheckConverter Singleton = new FluffyMinMaxLengthCheckConverter(); + } + + internal class TentacledMinMaxLengthCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(string); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + var value = serializer.Deserialize<string>(reader); + if (value.Length <= 10) + { + return value; + } + throw new Exception("Cannot unmarshal type string"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + var value = (string)untypedValue; + if (value.Length <= 10) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type string"); + } + + public static readonly TentacledMinMaxLengthCheckConverter Singleton = new TentacledMinMaxLengthCheckConverter(); + } + + internal class StickyMinMaxLengthCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(string); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + var value = serializer.Deserialize<string>(reader); + if (value.Length <= 150) + { + return value; + } + throw new Exception("Cannot unmarshal type string"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + var value = (string)untypedValue; + if (value.Length <= 150) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type string"); + } + + public static readonly StickyMinMaxLengthCheckConverter Singleton = new StickyMinMaxLengthCheckConverter(); + } +} diff --git a/Services/Models/Api/SecurityEventToken.cs b/Services/Models/Api/SecurityEventToken.cs new file mode 100644 index 0000000000000000000000000000000000000000..fccc4762f5ab79409b498649117f7fc057fd6979 --- /dev/null +++ b/Services/Models/Api/SecurityEventToken.cs @@ -0,0 +1,1122 @@ +// <auto-generated /> +// +// To parse this JSON data, add NuGet 'Newtonsoft.Json' then do: +// +// using FitConnect; +// +// var metadata = Metadata.FromJson(jsonString); + +namespace FitConnect.Models.Api.Set +{ + using System; + using System.Collections.Generic; + + using System.Globalization; + using Newtonsoft.Json; + using Newtonsoft.Json.Converters; + + public partial class Metadata + { + /// <summary> + /// Eine Struktur, um zusätzliche Informationen zu hinterlegen + /// </summary> + [JsonProperty("additionalReferenceInfo", NullValueHandling = NullValueHandling.Ignore)] + public AdditionalReferenceInfo AdditionalReferenceInfo { get; set; } + + /// <summary> + /// Eine Liste aller Identifikationsnachweise der Einreichung. + /// </summary> + [JsonProperty("authenticationInformation", NullValueHandling = NullValueHandling.Ignore)] + public List<AuthenticationInformation> AuthenticationInformation { get; set; } + + /// <summary> + /// Beschreibt die Struktur der zusätzlichen Inhalte der Einreichung, wie Anlagen oder + /// Fachdaten. + /// </summary> + [JsonProperty("contentStructure")] + public ContentStructure ContentStructure { get; set; } + + /// <summary> + /// Dieses Objekt enthält die Informationen vom Bezahldienst. + /// </summary> + [JsonProperty("paymentInformation", NullValueHandling = NullValueHandling.Ignore)] + public PaymentInformation PaymentInformation { get; set; } + + /// <summary> + /// Beschreibung der Art der Verwaltungsleistung. Eine Verwaltungsleistung sollte immer mit + /// einer LeiKa-Id beschrieben werden. Ist für die gegebene Verwaltungsleistung keine + /// LeiKa-Id vorhanden, kann die Verwaltungsleistung übergangsweise über die Angabe einer + /// anderen eindeutigen Schema-URN beschrieben werden. + /// </summary> + [JsonProperty("publicServiceType", NullValueHandling = NullValueHandling.Ignore)] + public Verwaltungsleistung PublicServiceType { get; set; } + + [JsonProperty("replyChannel", NullValueHandling = NullValueHandling.Ignore)] + public ReplyChannel ReplyChannel { get; set; } + } + + /// <summary> + /// Eine Struktur, um zusätzliche Informationen zu hinterlegen + /// </summary> + public partial class AdditionalReferenceInfo + { + /// <summary> + /// Das Datum der Antragstellung. Das Datum muss nicht zwingend identisch mit dem Datum der + /// Einreichung des Antrags über FIT-Connect sein. + /// </summary> + [JsonProperty("applicationDate", NullValueHandling = NullValueHandling.Ignore)] + public DateTimeOffset? ApplicationDate { get; set; } + + /// <summary> + /// Eine Referenz zum Vorgang im sendenden System, um bei Problemen und Rückfragen + /// außerhalb von FIT-Connect den Vorgang im dortigen System schneller zu identifizieren. + /// </summary> + [JsonProperty("senderReference", NullValueHandling = NullValueHandling.Ignore)] + public string SenderReference { get; set; } + } + + /// <summary> + /// Eine Struktur, die einen Identifikationsnachweis beschreibt. + /// </summary> + public partial class AuthenticationInformation + { + /// <summary> + /// Der Nachweis wird als Base64Url-kodierte Zeichenkette angegeben. + /// </summary> + [JsonProperty("content")] + public string Content { get; set; } + + /// <summary> + /// Definiert die Art des Identifikationsnachweises. + /// </summary> + [JsonProperty("type")] + public AuthenticationInformationType Type { get; set; } + + /// <summary> + /// semver kompatible Versionsangabe des genutzten Nachweistyps. + /// </summary> + [JsonProperty("version")] + public string Version { get; set; } + } + + /// <summary> + /// Beschreibt die Struktur der zusätzlichen Inhalte der Einreichung, wie Anlagen oder + /// Fachdaten. + /// </summary> + public partial class ContentStructure + { + [JsonProperty("attachments")] + public List<Attachment> Attachments { get; set; } + + /// <summary> + /// Definiert das Schema und die Signatur(-art), die für die Fachdaten verwendet werden. + /// </summary> + [JsonProperty("data", NullValueHandling = NullValueHandling.Ignore)] + public Data Data { get; set; } + } + + /// <summary> + /// Eine in der Einreichung enthaltene Anlage. + /// </summary> + public partial class Attachment + { + /// <summary> + /// Innerhalb einer Einreichung eindeutige Id der Anlage im Format einer UUIDv4. + /// </summary> + [JsonProperty("attachmentId")] + public Guid AttachmentId { get; set; } + + /// <summary> + /// Optionale Beschreibung der Anlage + /// </summary> + [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] + public string Description { get; set; } + + /// <summary> + /// Ursprünglicher Dateiname bei Erzeugung oder Upload + /// </summary> + [JsonProperty("filename", NullValueHandling = NullValueHandling.Ignore)] + public string Filename { get; set; } + + /// <summary> + /// Der Hashwert der unverschlüsselten Anlage. Die Angabe des Hashwertes dient der + /// Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Anlage durch + /// Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst). + /// </summary> + [JsonProperty("hash")] + public AttachmentHash Hash { get; set; } + + /// <summary> + /// Internet Media Type gemäß RFC 2045, z. B. application/pdf. + /// </summary> + [JsonProperty("mimeType")] + public string MimeType { get; set; } + + /// <summary> + /// Zweck/Art der Anlage + /// - form: Automatisch generierte PDF-Repräsentation des vollständigen Antragsformulars + /// - attachment: Anlage, die von einem Bürger hochgeladen wurde + /// - report: Vom Onlinedienst, nachträglich erzeugte Unterlage + /// </summary> + [JsonProperty("purpose")] + public Purpose Purpose { get; set; } + + [JsonProperty("signature", NullValueHandling = NullValueHandling.Ignore)] + public AttachmentSignature Signature { get; set; } + } + + /// <summary> + /// Der Hashwert der unverschlüsselten Anlage. Die Angabe des Hashwertes dient der + /// Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Anlage durch + /// Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst). + /// </summary> + public partial class AttachmentHash + { + /// <summary> + /// Der Hex-kodierte Hashwert gemäß des angegebenen Algorithmus. + /// </summary> + [JsonProperty("content")] + public string Content { get; set; } + + /// <summary> + /// Der verwendete Hash-Algorithmus. Derzeit ist nur `sha512` erlaubt. + /// </summary> + [JsonProperty("type")] + public HashType Type { get; set; } + } + + /// <summary> + /// Beschreibt das Signaturformt und Profile + /// </summary> + public partial class AttachmentSignature + { + /// <summary> + /// Hier wird die Signatur im Falle einer Detached-Signatur als Base64- oder + /// Base64Url-kodierte Zeichenkette hinterlegt. Eine Base64Url-Kodierung kommt nur bei + /// Einsatz von JSON Web Signatures (JWS / JAdES) zum Einsatz. + /// </summary> + [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] + public string Content { get; set; } + + /// <summary> + /// Beschreibt, ob die Signatur als seperate (detached) Signatur (`true`) oder als Teil des + /// Fachdatensatzes bzw. der Anlage (`false`) übertragen wird. Wenn der Wert `true` ist, + /// dann wird die Signatur Base64- oder Base64Url-kodiert im Feld `content` übertragen. + /// </summary> + [JsonProperty("detachedSignature")] + public bool DetachedSignature { get; set; } + + /// <summary> + /// Referenziert ein eindeutiges Profil einer AdES (advanced electronic signature/seal) + /// gemäß eIDAS-Verordnung über eine URI gemäß [ETSI TS 119 + /// 192](https://www.etsi.org/deliver/etsi_ts/119100_119199/119192/01.01.01_60/ts_119192v010101p.pdf). + /// + /// Für die Details zur Verwendung und Validierung von Profilen siehe auch + /// https://ec.europa.eu/cefdigital/DSS/webapp-demo/doc/dss-documentation.html#_signatures_profile_simplification + /// </summary> + [JsonProperty("eidasAdesProfile", NullValueHandling = NullValueHandling.Ignore)] + public EidasAdesProfile? EidasAdesProfile { get; set; } + + /// <summary> + /// Beschreibt, welches Signaturformat die genutzte Signatur / das genutzte Siegel nutzt. + /// Aktuell wird die Hinterlegung folgender Signaturformate unterstützt: CMS = Cryptographic + /// Message Syntax, Asic = Associated Signature Containers, PDF = PDF Signatur, XML = + /// XML-Signature, JSON = JSON Web Signature. + /// </summary> + [JsonProperty("signatureFormat")] + public SignatureFormat SignatureFormat { get; set; } + } + + /// <summary> + /// Definiert das Schema und die Signatur(-art), die für die Fachdaten verwendet werden. + /// </summary> + public partial class Data + { + /// <summary> + /// Der Hashwert der unverschlüsselten Fachdaten. Die Angabe des Hashwertes dient der + /// Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Fachdaten + /// durch Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst). + /// </summary> + [JsonProperty("hash")] + public DataHash Hash { get; set; } + + /// <summary> + /// Beschreibt das Signaturformt und Profile + /// </summary> + [JsonProperty("signature", NullValueHandling = NullValueHandling.Ignore)] + public DataSignature Signature { get; set; } + + /// <summary> + /// Referenz auf ein Schema, das die Struktur der Fachdaten einer Einreichung beschreibt. + /// </summary> + [JsonProperty("submissionSchema")] + public Fachdatenschema SubmissionSchema { get; set; } + } + + /// <summary> + /// Der Hashwert der unverschlüsselten Fachdaten. Die Angabe des Hashwertes dient der + /// Integritätssicherung des Gesamtantrags und schützt vor einem Austausch der Fachdaten + /// durch Systeme zwischen Sender und Subscriber (z.B. dem Zustelldienst). + /// </summary> + public partial class DataHash + { + /// <summary> + /// Der Hex-kodierte Hashwert gemäß des angegebenen Algorithmus. + /// </summary> + [JsonProperty("content")] + public string Content { get; set; } + + /// <summary> + /// Der verwendete Hash-Algorithmus. Derzeit ist nur `sha512` erlaubt. + /// </summary> + [JsonProperty("type")] + public HashType Type { get; set; } + } + + /// <summary> + /// Beschreibt das Signaturformt und Profile + /// </summary> + public partial class DataSignature + { + /// <summary> + /// Hier wird die Signatur im Falle einer Detached-Signatur als Base64- oder + /// Base64Url-kodierte Zeichenkette hinterlegt. Eine Base64Url-Kodierung kommt nur bei + /// Einsatz von JSON Web Signatures (JWS / JAdES) zum Einsatz. + /// </summary> + [JsonProperty("content", NullValueHandling = NullValueHandling.Ignore)] + public string Content { get; set; } + + /// <summary> + /// Beschreibt, ob die Signatur als seperate (detached) Signatur (`true`) oder als Teil des + /// Fachdatensatzes bzw. der Anlage (`false`) übertragen wird. Wenn der Wert `true` ist, + /// dann wird die Signatur Base64- oder Base64Url-kodiert im Feld `content` übertragen. + /// </summary> + [JsonProperty("detachedSignature")] + public bool DetachedSignature { get; set; } + + /// <summary> + /// Referenziert ein eindeutiges Profil einer AdES (advanced electronic signature/seal) + /// gemäß eIDAS-Verordnung über eine URI gemäß [ETSI TS 119 + /// 192](https://www.etsi.org/deliver/etsi_ts/119100_119199/119192/01.01.01_60/ts_119192v010101p.pdf). + /// + /// Für die Details zur Verwendung und Validierung von Profilen siehe auch + /// https://ec.europa.eu/cefdigital/DSS/webapp-demo/doc/dss-documentation.html#_signatures_profile_simplification + /// </summary> + [JsonProperty("eidasAdesProfile", NullValueHandling = NullValueHandling.Ignore)] + public EidasAdesProfile? EidasAdesProfile { get; set; } + + /// <summary> + /// Beschreibt, welches Signaturformat die genutzte Signatur / das genutzte Siegel nutzt. + /// Aktuell wird die Hinterlegung folgender Signaturformate unterstützt: CMS = Cryptographic + /// Message Syntax, Asic = Associated Signature Containers, PDF = PDF Signatur, XML = + /// XML-Signature, JSON = JSON Web Signature. + /// </summary> + [JsonProperty("signatureFormat")] + public SignatureFormat SignatureFormat { get; set; } + } + + /// <summary> + /// Referenz auf ein Schema, das die Struktur der Fachdaten einer Einreichung beschreibt. + /// </summary> + public partial class Fachdatenschema + { + /// <summary> + /// Mimetype (z.B. application/json oder application/xml) des referenzierten Schemas (z.B. + /// XSD- oder JSON-Schema). + /// </summary> + [JsonProperty("mimeType")] + public MimeType MimeType { get; set; } + + /// <summary> + /// URI des Fachschemas. Wird hier eine URL verwendet, sollte das Schema unter der + /// angegebenen URL abrufbar sein. Eine Verfügbarkeit des Schemas unter der angegebenen URL + /// darf jedoch nicht vorausgesetzt werden. + /// </summary> + [JsonProperty("schemaUri")] + public Uri SchemaUri { get; set; } + } + + /// <summary> + /// Dieses Objekt enthält die Informationen vom Bezahldienst. + /// </summary> + public partial class PaymentInformation + { + /// <summary> + /// Bruttobetrag + /// </summary> + [JsonProperty("grossAmount", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(MinMaxValueCheckConverter))] + public double? GrossAmount { get; set; } + + /// <summary> + /// Die vom Benutzer ausgewählte Zahlart. Das Feld ist nur bei einer erfolgreichen Zahlung + /// vorhanden / befüllt. + /// </summary> + [JsonProperty("paymentMethod")] + public PaymentMethod PaymentMethod { get; set; } + + /// <summary> + /// Weitere Erläuterung zur gewählten Zahlart. + /// </summary> + [JsonProperty("paymentMethodDetail", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(PurpleMinMaxLengthCheckConverter))] + public string PaymentMethodDetail { get; set; } + + /// <summary> + /// - INITIAL - der Einreichung hat einen Payment-Request ausgelöst und eine + /// Payment-Transaction wurde angelegt. Der Nutzer hat aber im Bezahldienst noch keine + /// Wirkung erzeugt. + /// - BOOKED - der Nutzer hat die Bezahlung im Bezahldienst autorisiert. + /// - FAILED - der Vorgang wurde vom Bezahldienst aufgrund der Nutzereingaben abgebrochen. + /// - CANCELED - der Nutzer hat die Bezahlung im Bezahldienst abgebrochen. + /// </summary> + [JsonProperty("status")] + public Status Status { get; set; } + + /// <summary> + /// Eine vom Bezahldienst vergebene Transaktions-Id. + /// </summary> + [JsonProperty("transactionId")] + [JsonConverter(typeof(PurpleMinMaxLengthCheckConverter))] + public string TransactionId { get; set; } + + /// <summary> + /// Bezahlreferenz bzw. Verwendungszweck, wie z. B. ein Kassenzeichen. + /// </summary> + [JsonProperty("transactionReference")] + public string TransactionReference { get; set; } + + /// <summary> + /// Zeitstempel der erfolgreichen Durchführung der Bezahlung. + /// </summary> + [JsonProperty("transactionTimestamp", NullValueHandling = NullValueHandling.Ignore)] + public DateTimeOffset? TransactionTimestamp { get; set; } + + /// <summary> + /// Die Rest-URL der Payment Transaction für die Statusabfrage. + /// </summary> + [JsonProperty("transactionUrl", NullValueHandling = NullValueHandling.Ignore)] + public Uri TransactionUrl { get; set; } + } + + /// <summary> + /// Beschreibung der Art der Verwaltungsleistung. Eine Verwaltungsleistung sollte immer mit + /// einer LeiKa-Id beschrieben werden. Ist für die gegebene Verwaltungsleistung keine + /// LeiKa-Id vorhanden, kann die Verwaltungsleistung übergangsweise über die Angabe einer + /// anderen eindeutigen Schema-URN beschrieben werden. + /// </summary> + public partial class Verwaltungsleistung + { + /// <summary> + /// (Kurz-)Beschreibung der Verwaltungsleistung + /// </summary> + [JsonProperty("description", NullValueHandling = NullValueHandling.Ignore)] + public string Description { get; set; } + + /// <summary> + /// URN einer Leistung. Im Falle einer Leistung aus dem Leistungskatalog sollte hier + /// `urn:de:fim:leika:leistung:` vorangestellt werden. + /// </summary> + [JsonProperty("identifier")] + [JsonConverter(typeof(FluffyMinMaxLengthCheckConverter))] + public string Identifier { get; set; } + + /// <summary> + /// Name/Bezeichnung der Verwaltungsleistung + /// </summary> + [JsonProperty("name", NullValueHandling = NullValueHandling.Ignore)] + public string Name { get; set; } + } + + public partial class ReplyChannel + { + /// <summary> + /// Akkreditierte Anbieter siehe + /// https://www.bsi.bund.de/DE/Themen/Oeffentliche-Verwaltung/Moderner-Staat/De-Mail/Akkreditierte-DMDA/akkreditierte-dmda_node.html + /// </summary> + [JsonProperty("deMail", NullValueHandling = NullValueHandling.Ignore)] + public DeMail DeMail { get; set; } + + /// <summary> + /// Siehe https://www.elster.de/elsterweb/infoseite/elstertransfer_hilfe_schnittstellen + /// </summary> + [JsonProperty("elster", NullValueHandling = NullValueHandling.Ignore)] + public Elster Elster { get; set; } + + [JsonProperty("eMail", NullValueHandling = NullValueHandling.Ignore)] + public EMail EMail { get; set; } + + /// <summary> + /// Postfachadresse in einem interoperablen Servicekonto (FINK.PFISK) + /// </summary> + [JsonProperty("fink", NullValueHandling = NullValueHandling.Ignore)] + public Fink Fink { get; set; } + } + + /// <summary> + /// Akkreditierte Anbieter siehe + /// https://www.bsi.bund.de/DE/Themen/Oeffentliche-Verwaltung/Moderner-Staat/De-Mail/Akkreditierte-DMDA/akkreditierte-dmda_node.html + /// </summary> + public partial class DeMail + { + [JsonProperty("address")] + public string Address { get; set; } + } + + public partial class EMail + { + [JsonProperty("address")] + public string Address { get; set; } + + /// <summary> + /// Hilfe zur Erstellung gibt es in der Dokumentation unter + /// https://docs.fitko.de/fit-connect/details/pgp-export + /// </summary> + [JsonProperty("pgpPublicKey", NullValueHandling = NullValueHandling.Ignore)] + public string PgpPublicKey { get; set; } + } + + /// <summary> + /// Siehe https://www.elster.de/elsterweb/infoseite/elstertransfer_hilfe_schnittstellen + /// </summary> + public partial class Elster + { + [JsonProperty("accountId")] + public string AccountId { get; set; } + + [JsonProperty("geschaeftszeichen", NullValueHandling = NullValueHandling.Ignore)] + [JsonConverter(typeof(TentacledMinMaxLengthCheckConverter))] + public string Geschaeftszeichen { get; set; } + + [JsonProperty("lieferTicket", NullValueHandling = NullValueHandling.Ignore)] + public string LieferTicket { get; set; } + } + + /// <summary> + /// Postfachadresse in einem interoperablen Servicekonto (FINK.PFISK) + /// </summary> + public partial class Fink + { + /// <summary> + /// FINK Postfachadresse + /// </summary> + [JsonProperty("finkPostfachRef")] + [JsonConverter(typeof(StickyMinMaxLengthCheckConverter))] + public string FinkPostfachRef { get; set; } + + /// <summary> + /// URL des Servicekontos, in dem das Ziel-Postfach liegt + /// </summary> + [JsonProperty("host", NullValueHandling = NullValueHandling.Ignore)] + public Uri Host { get; set; } + } + + /// <summary> + /// Definiert die Art des Identifikationsnachweises. + /// </summary> + public enum AuthenticationInformationType { IdentificationReport }; + + /// <summary> + /// Der verwendete Hash-Algorithmus. Derzeit ist nur `sha512` erlaubt. + /// </summary> + public enum HashType { Sha512 }; + + /// <summary> + /// Zweck/Art der Anlage + /// - form: Automatisch generierte PDF-Repräsentation des vollständigen Antragsformulars + /// - attachment: Anlage, die von einem Bürger hochgeladen wurde + /// - report: Vom Onlinedienst, nachträglich erzeugte Unterlage + /// </summary> + public enum Purpose { Attachment, Form, Report }; + + /// <summary> + /// Referenziert ein eindeutiges Profil einer AdES (advanced electronic signature/seal) + /// gemäß eIDAS-Verordnung über eine URI gemäß [ETSI TS 119 + /// 192](https://www.etsi.org/deliver/etsi_ts/119100_119199/119192/01.01.01_60/ts_119192v010101p.pdf). + /// + /// Für die Details zur Verwendung und Validierung von Profilen siehe auch + /// https://ec.europa.eu/cefdigital/DSS/webapp-demo/doc/dss-documentation.html#_signatures_profile_simplification + /// </summary> + public enum EidasAdesProfile { HttpUriEtsiOrgAdes191X2LevelBaselineBB, HttpUriEtsiOrgAdes191X2LevelBaselineBLt, HttpUriEtsiOrgAdes191X2LevelBaselineBLta, HttpUriEtsiOrgAdes191X2LevelBaselineBT }; + + /// <summary> + /// Beschreibt, welches Signaturformat die genutzte Signatur / das genutzte Siegel nutzt. + /// Aktuell wird die Hinterlegung folgender Signaturformate unterstützt: CMS = Cryptographic + /// Message Syntax, Asic = Associated Signature Containers, PDF = PDF Signatur, XML = + /// XML-Signature, JSON = JSON Web Signature. + /// </summary> + public enum SignatureFormat { Asic, Cms, Json, Pdf, Xml }; + + /// <summary> + /// Mimetype (z.B. application/json oder application/xml) des referenzierten Schemas (z.B. + /// XSD- oder JSON-Schema). + /// </summary> + public enum MimeType { ApplicationJson, ApplicationXml }; + + /// <summary> + /// Die vom Benutzer ausgewählte Zahlart. Das Feld ist nur bei einer erfolgreichen Zahlung + /// vorhanden / befüllt. + /// </summary> + public enum PaymentMethod { Creditcard, Giropay, Invoice, Other, Paydirect, Paypal }; + + /// <summary> + /// - INITIAL - der Einreichung hat einen Payment-Request ausgelöst und eine + /// Payment-Transaction wurde angelegt. Der Nutzer hat aber im Bezahldienst noch keine + /// Wirkung erzeugt. + /// - BOOKED - der Nutzer hat die Bezahlung im Bezahldienst autorisiert. + /// - FAILED - der Vorgang wurde vom Bezahldienst aufgrund der Nutzereingaben abgebrochen. + /// - CANCELED - der Nutzer hat die Bezahlung im Bezahldienst abgebrochen. + /// </summary> + public enum Status { Booked, Canceled, Failed, Initial }; + + public partial class Metadata + { + public static Metadata FromJson(string json) => JsonConvert.DeserializeObject<Metadata>(json, FitConnect.Models.Api.Set.Converter.Settings); + } + + public static class Serialize + { + public static string ToJson(this Metadata self) => JsonConvert.SerializeObject(self, FitConnect.Models.Api.Set.Converter.Settings); + } + + internal static class Converter + { + public static readonly JsonSerializerSettings Settings = new JsonSerializerSettings + { + MetadataPropertyHandling = MetadataPropertyHandling.Ignore, + DateParseHandling = DateParseHandling.None, + Converters = + { + AuthenticationInformationTypeConverter.Singleton, + HashTypeConverter.Singleton, + PurposeConverter.Singleton, + EidasAdesProfileConverter.Singleton, + SignatureFormatConverter.Singleton, + MimeTypeConverter.Singleton, + PaymentMethodConverter.Singleton, + StatusConverter.Singleton, + new IsoDateTimeConverter { DateTimeStyles = DateTimeStyles.AssumeUniversal } + }, + }; + } + + internal class AuthenticationInformationTypeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(AuthenticationInformationType) || t == typeof(AuthenticationInformationType?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + if (value == "identificationReport") + { + return AuthenticationInformationType.IdentificationReport; + } + throw new Exception("Cannot unmarshal type AuthenticationInformationType"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (AuthenticationInformationType)untypedValue; + if (value == AuthenticationInformationType.IdentificationReport) + { + serializer.Serialize(writer, "identificationReport"); + return; + } + throw new Exception("Cannot marshal type AuthenticationInformationType"); + } + + public static readonly AuthenticationInformationTypeConverter Singleton = new AuthenticationInformationTypeConverter(); + } + + internal class HashTypeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(HashType) || t == typeof(HashType?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + if (value == "sha512") + { + return HashType.Sha512; + } + throw new Exception("Cannot unmarshal type HashType"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (HashType)untypedValue; + if (value == HashType.Sha512) + { + serializer.Serialize(writer, "sha512"); + return; + } + throw new Exception("Cannot marshal type HashType"); + } + + public static readonly HashTypeConverter Singleton = new HashTypeConverter(); + } + + internal class PurposeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(Purpose) || t == typeof(Purpose?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "attachment": + return Purpose.Attachment; + case "form": + return Purpose.Form; + case "report": + return Purpose.Report; + } + throw new Exception("Cannot unmarshal type Purpose"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (Purpose)untypedValue; + switch (value) + { + case Purpose.Attachment: + serializer.Serialize(writer, "attachment"); + return; + case Purpose.Form: + serializer.Serialize(writer, "form"); + return; + case Purpose.Report: + serializer.Serialize(writer, "report"); + return; + } + throw new Exception("Cannot marshal type Purpose"); + } + + public static readonly PurposeConverter Singleton = new PurposeConverter(); + } + + internal class EidasAdesProfileConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(EidasAdesProfile) || t == typeof(EidasAdesProfile?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "http://uri.etsi.org/ades/191x2/level/baseline/B-B#": + return EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBB; + case "http://uri.etsi.org/ades/191x2/level/baseline/B-LT#": + return EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBLt; + case "http://uri.etsi.org/ades/191x2/level/baseline/B-LTA#": + return EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBLta; + case "http://uri.etsi.org/ades/191x2/level/baseline/B-T#": + return EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBT; + } + throw new Exception("Cannot unmarshal type EidasAdesProfile"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (EidasAdesProfile)untypedValue; + switch (value) + { + case EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBB: + serializer.Serialize(writer, "http://uri.etsi.org/ades/191x2/level/baseline/B-B#"); + return; + case EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBLt: + serializer.Serialize(writer, "http://uri.etsi.org/ades/191x2/level/baseline/B-LT#"); + return; + case EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBLta: + serializer.Serialize(writer, "http://uri.etsi.org/ades/191x2/level/baseline/B-LTA#"); + return; + case EidasAdesProfile.HttpUriEtsiOrgAdes191X2LevelBaselineBT: + serializer.Serialize(writer, "http://uri.etsi.org/ades/191x2/level/baseline/B-T#"); + return; + } + throw new Exception("Cannot marshal type EidasAdesProfile"); + } + + public static readonly EidasAdesProfileConverter Singleton = new EidasAdesProfileConverter(); + } + + internal class SignatureFormatConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(SignatureFormat) || t == typeof(SignatureFormat?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "asic": + return SignatureFormat.Asic; + case "cms": + return SignatureFormat.Cms; + case "json": + return SignatureFormat.Json; + case "pdf": + return SignatureFormat.Pdf; + case "xml": + return SignatureFormat.Xml; + } + throw new Exception("Cannot unmarshal type SignatureFormat"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (SignatureFormat)untypedValue; + switch (value) + { + case SignatureFormat.Asic: + serializer.Serialize(writer, "asic"); + return; + case SignatureFormat.Cms: + serializer.Serialize(writer, "cms"); + return; + case SignatureFormat.Json: + serializer.Serialize(writer, "json"); + return; + case SignatureFormat.Pdf: + serializer.Serialize(writer, "pdf"); + return; + case SignatureFormat.Xml: + serializer.Serialize(writer, "xml"); + return; + } + throw new Exception("Cannot marshal type SignatureFormat"); + } + + public static readonly SignatureFormatConverter Singleton = new SignatureFormatConverter(); + } + + internal class MimeTypeConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(MimeType) || t == typeof(MimeType?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "application/json": + return MimeType.ApplicationJson; + case "application/xml": + return MimeType.ApplicationXml; + } + throw new Exception("Cannot unmarshal type MimeType"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (MimeType)untypedValue; + switch (value) + { + case MimeType.ApplicationJson: + serializer.Serialize(writer, "application/json"); + return; + case MimeType.ApplicationXml: + serializer.Serialize(writer, "application/xml"); + return; + } + throw new Exception("Cannot marshal type MimeType"); + } + + public static readonly MimeTypeConverter Singleton = new MimeTypeConverter(); + } + + internal class MinMaxValueCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(double) || t == typeof(double?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<double>(reader); + if (value >= 0.01) + { + return value; + } + throw new Exception("Cannot unmarshal type double"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (double)untypedValue; + if (value >= 0.01) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type double"); + } + + public static readonly MinMaxValueCheckConverter Singleton = new MinMaxValueCheckConverter(); + } + + internal class PaymentMethodConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(PaymentMethod) || t == typeof(PaymentMethod?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "CREDITCARD": + return PaymentMethod.Creditcard; + case "GIROPAY": + return PaymentMethod.Giropay; + case "INVOICE": + return PaymentMethod.Invoice; + case "OTHER": + return PaymentMethod.Other; + case "PAYDIRECT": + return PaymentMethod.Paydirect; + case "PAYPAL": + return PaymentMethod.Paypal; + } + throw new Exception("Cannot unmarshal type PaymentMethod"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (PaymentMethod)untypedValue; + switch (value) + { + case PaymentMethod.Creditcard: + serializer.Serialize(writer, "CREDITCARD"); + return; + case PaymentMethod.Giropay: + serializer.Serialize(writer, "GIROPAY"); + return; + case PaymentMethod.Invoice: + serializer.Serialize(writer, "INVOICE"); + return; + case PaymentMethod.Other: + serializer.Serialize(writer, "OTHER"); + return; + case PaymentMethod.Paydirect: + serializer.Serialize(writer, "PAYDIRECT"); + return; + case PaymentMethod.Paypal: + serializer.Serialize(writer, "PAYPAL"); + return; + } + throw new Exception("Cannot marshal type PaymentMethod"); + } + + public static readonly PaymentMethodConverter Singleton = new PaymentMethodConverter(); + } + + internal class PurpleMinMaxLengthCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(string); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + var value = serializer.Deserialize<string>(reader); + if (value.Length >= 1 && value.Length <= 36) + { + return value; + } + throw new Exception("Cannot unmarshal type string"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + var value = (string)untypedValue; + if (value.Length >= 1 && value.Length <= 36) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type string"); + } + + public static readonly PurpleMinMaxLengthCheckConverter Singleton = new PurpleMinMaxLengthCheckConverter(); + } + + internal class StatusConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(Status) || t == typeof(Status?); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + if (reader.TokenType == JsonToken.Null) return null; + var value = serializer.Deserialize<string>(reader); + switch (value) + { + case "BOOKED": + return Status.Booked; + case "CANCELED": + return Status.Canceled; + case "FAILED": + return Status.Failed; + case "INITIAL": + return Status.Initial; + } + throw new Exception("Cannot unmarshal type Status"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + if (untypedValue == null) + { + serializer.Serialize(writer, null); + return; + } + var value = (Status)untypedValue; + switch (value) + { + case Status.Booked: + serializer.Serialize(writer, "BOOKED"); + return; + case Status.Canceled: + serializer.Serialize(writer, "CANCELED"); + return; + case Status.Failed: + serializer.Serialize(writer, "FAILED"); + return; + case Status.Initial: + serializer.Serialize(writer, "INITIAL"); + return; + } + throw new Exception("Cannot marshal type Status"); + } + + public static readonly StatusConverter Singleton = new StatusConverter(); + } + + internal class FluffyMinMaxLengthCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(string); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + var value = serializer.Deserialize<string>(reader); + if (value.Length >= 7 && value.Length <= 255) + { + return value; + } + throw new Exception("Cannot unmarshal type string"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + var value = (string)untypedValue; + if (value.Length >= 7 && value.Length <= 255) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type string"); + } + + public static readonly FluffyMinMaxLengthCheckConverter Singleton = new FluffyMinMaxLengthCheckConverter(); + } + + internal class TentacledMinMaxLengthCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(string); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + var value = serializer.Deserialize<string>(reader); + if (value.Length <= 10) + { + return value; + } + throw new Exception("Cannot unmarshal type string"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + var value = (string)untypedValue; + if (value.Length <= 10) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type string"); + } + + public static readonly TentacledMinMaxLengthCheckConverter Singleton = new TentacledMinMaxLengthCheckConverter(); + } + + internal class StickyMinMaxLengthCheckConverter : JsonConverter + { + public override bool CanConvert(Type t) => t == typeof(string); + + public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) + { + var value = serializer.Deserialize<string>(reader); + if (value.Length <= 150) + { + return value; + } + throw new Exception("Cannot unmarshal type string"); + } + + public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) + { + var value = (string)untypedValue; + if (value.Length <= 150) + { + serializer.Serialize(writer, value); + return; + } + throw new Exception("Cannot marshal type string"); + } + + public static readonly StickyMinMaxLengthCheckConverter Singleton = new StickyMinMaxLengthCheckConverter(); + } +} diff --git a/Services/Models/Submission/SubmitSubmissionDto.cs b/Services/Models/Submission/SubmitSubmissionDto.cs index ec47ff8a5fe4adc0f8c9e46ffc8fc5728d407c65..db1e7d96e679ee03789fd908098db26d64c538e6 100644 --- a/Services/Models/Submission/SubmitSubmissionDto.cs +++ b/Services/Models/Submission/SubmitSubmissionDto.cs @@ -4,7 +4,7 @@ namespace FitConnect.Services.Models; public class SubmitSubmissionDto { [JsonPropertyName("encryptedData")] - public string EncryptedData; + public string? EncryptedData; [JsonPropertyName("encryptedMetadata")] public string EncryptedMetadata; diff --git a/Services/Services.csproj b/Services/Services.csproj index 9532ca430385e7c6b1f9774a9064d4bcfebbb013..a7e0e4428d4d28e78ac79e675b28c5102e5ba1ad 100644 --- a/Services/Services.csproj +++ b/Services/Services.csproj @@ -8,12 +8,12 @@ </PropertyGroup> <ItemGroup> - <ProjectReference Include="..\Models\Models.csproj"/> - <ProjectReference Include="..\RestService\RestService.csproj"/> + <ProjectReference Include="..\Models\Models.csproj" /> + <ProjectReference Include="..\RestService\RestService.csproj" /> </ItemGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1"/> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> </ItemGroup> </Project> diff --git a/readme.md b/readme.md index b1f41534b6db438a9c870a42502b0807adbb13b4..ab8a5455732c3c092b77b09577f99fb339e35953 100644 --- a/readme.md +++ b/readme.md @@ -133,6 +133,21 @@ You need a secret file for e2e test like: } ``` +# API Abstraction + +```mermaid + sequenceDiagram + Client->>FitConnectServer: SendSubmission + FitConnectServer->>Client: Success +``` + +## The sender process +```mermaid + sequenceDiagram + Sender->>FitConnectServer: GetSubmissions + FitConnectServer->>Sender: Success +``` + [glossary](https://docs.fitko.de/fit-connect/docs/glossary/) ### Tickets