diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7847b419884a776bd42109fc8ed55d2a43264ecb..85f71821f3adab33d386b7a3e427ed2fda500a70 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,11 @@ stages: - test - build +reuse: + extends: .reuse + variables: + DOCKER_REGISTRY_READ: $DOCKER_PULL_REGISTRY + build: stage: build image: mcr.microsoft.com/dotnet/sdk:6.0 @@ -18,4 +23,6 @@ test: image: mcr.microsoft.com/dotnet/sdk:6.0 script: - dotnet test E2ETest + - dotnet test IntegrationTests + - dotnet test BasicUnitTest diff --git a/.reuse/dep5 b/.reuse/dep5 index f530c5364ffd7b1f6bcc973b1df087f33fc1b8eb..a7b7b6314e26b38080dbfdf62a6216627843d563 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -9,5 +9,6 @@ License: EUPL-1.2 Files: .gitlab-ci.yml .gitignore .idea/* renovate.json checkstyle.xml config.yml + nuget_api.template.txt Copyright: 2022 FIT-Connect contributors License: CC0-1.0 \ No newline at end of file diff --git a/BasicUnitTest/SecurityEventTokenTests.cs b/BasicUnitTest/SecurityEventTokenTests.cs index 54bec9717b7d03ced31f7283256cec1ebb6a2d95..800215d6a0e4557711264543a191985e2feffe3f 100644 --- a/BasicUnitTest/SecurityEventTokenTests.cs +++ b/BasicUnitTest/SecurityEventTokenTests.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.IO; using Autofac; +using FitConnect; using FitConnect.Encryption; using FitConnect.Models; using FitConnect.Models.v1.Api; @@ -13,7 +16,6 @@ namespace BasicUnitTest; [TestFixture] public class SecurityEventTokenTests { - private const string rejectSubmission = SecurityEventToken.RejectSubmissionSchema; @@ -30,7 +32,6 @@ public class SecurityEventTokenTests { } - [Test] public void CreateJwt_AcceptSubmission() { var token = _encryption.CreateAcceptSecurityEventToken(new SubmissionForPickupDto { @@ -40,7 +41,7 @@ public class SecurityEventTokenTests { Console.WriteLine(token); var decoded = new SecurityEventToken(token); decoded.Event?.Type.Should() - .Be("https://schema.fitko.de/fit-connect/events/accept-submission"); + .Be(acceptSubmission); decoded.EventType.Should().Be(EventType.Accept); } @@ -54,7 +55,7 @@ public class SecurityEventTokenTests { Console.WriteLine(token); var decoded = new SecurityEventToken(token); decoded.Event?.Type.Should() - .Be("https://schema.fitko.de/fit-connect/events/reject-submission"); + .Be(rejectSubmission); decoded.EventType.Should().Be(EventType.Reject); } @@ -68,7 +69,7 @@ public class SecurityEventTokenTests { Console.WriteLine(token); var decoded = new SecurityEventToken(token); decoded.Event?.Type.Should() - .Be("https://schema.fitko.de/fit-connect/events/reject-submission"); + .Be(rejectSubmission); decoded.EventType.Should().Be(EventType.Reject); } @@ -83,7 +84,7 @@ public class SecurityEventTokenTests { Console.WriteLine(token); var decoded = new SecurityEventToken(token); decoded.Event?.Type.Should() - .Be("https://schema.fitko.de/fit-connect/events/reject-submission"); + .Be(rejectSubmission); decoded.EventType.Should().Be(EventType.Reject); } @@ -98,7 +99,7 @@ public class SecurityEventTokenTests { var decoded = new SecurityEventToken(token); decoded.Event?.Type.Should() - .Be("https://schema.fitko.de/fit-connect/events/reject-submission"); + .Be(rejectSubmission); decoded.EventType.Should().Be(EventType.Reject); } @@ -112,7 +113,7 @@ public class SecurityEventTokenTests { Console.WriteLine(token); var decoded = new SecurityEventToken(token); decoded.Event?.Type.Should() - .Be("https://schema.fitko.de/fit-connect/events/reject-submission"); + .Be(rejectSubmission); decoded.EventType.Should().Be(EventType.Reject); } @@ -126,22 +127,25 @@ public class SecurityEventTokenTests { Console.WriteLine(token); var decoded = new SecurityEventToken(token); decoded.Event?.Type.Should() - .Be("https://schema.fitko.de/fit-connect/events/reject-submission"); + .Be(rejectSubmission); decoded.EventType.Should().Be(EventType.Reject); } - // [Test] - // public void CreateJwt_Reject_WithCustomProblem() { - // var token = _encryption.CreateSecurityEventToken(Guid.NewGuid().ToString(), - // Guid.NewGuid().ToString(), - // Guid.NewGuid().ToString(), - // "https://schema.fitko.de/fit-connect/events/reject-submission", - // new[] { new Problems { Description = "A real big issue" } } - // ); - // Console.WriteLine(token); - // var decoded = new SecurityEventToken(token); - // decoded.Event?.Type.Should() - // .Be("https://schema.fitko.de/fit-connect/events/reject-submission"); - // decoded.EventType.Should().Be(EventType.Reject); - // } + [Test] + public void CreateJwt_WithEncryptionKey_ShouldWork() { + _encryption.CreateSecurityEventToken(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), + Guid.NewGuid().ToString(), "DummyEvent", null); + } + + [Test] + public void CreateJwt_WithEncryptionKey_ShouldThrowAnException() { + Assert.Throws<InvalidOperationException>(() => + new FitEncryption(_encryption.PrivateKeyDecryption, + _encryption.PublicKeyEncryption, // using wrong key + _encryption.PublicKeyEncryption, + _encryption.PublicKeySignatureVerification, null) + .CreateSecurityEventToken(Guid.NewGuid().ToString(), Guid.NewGuid().ToString(), + Guid.NewGuid().ToString(), "DummyEvent", null) + ); + } } diff --git a/BasicUnitTest/SenderTests.cs b/BasicUnitTest/SenderTests.cs index e824755cf22491380dab5cd161d3f1e15f6e3436..87ba281e807a512331518dc61c1ece74cdcbd89d 100644 --- a/BasicUnitTest/SenderTests.cs +++ b/BasicUnitTest/SenderTests.cs @@ -85,7 +85,7 @@ public class SenderTests { } [Test] - public void VerifyMetaData_MissingLeikaKey_ThorwsAnError() { + public void VerifyMetaData_MissingLeikaKey_ThrowsAnError() { // Arrange var submission = new Submission(); diff --git a/Documentation/documentation.de-DE.md b/Documentation/documentation.de-DE.md index 6933d0a820563490681d10d26b9b9c1eb8039644..d6e5cb5d0dc561dc6bba94c2a290977932ca0a8a 100644 --- a/Documentation/documentation.de-DE.md +++ b/Documentation/documentation.de-DE.md @@ -1,3 +1,6 @@ +<br /> +<div align="center"><img src="https://www.fitko.de/fileadmin/_processed_/b/9/csm_FIT-Connect_3e8e926015.jpg" alt="Logo" width="50%" height="50%"> </div> + # FIT-Connect .NET SDK User Guide ## Einleitung @@ -8,6 +11,7 @@ Das FIT-Connect .NET SDK bietet eine einfache Möglichkeit, sowohl einen Antrags ### OSX +#### Apple M1 Chip __Anmerkung:__ Bei einem Mac mit einem M1 chip, muss brew in der _x86-64_-Version installiert werden. @@ -17,13 +21,15 @@ _Die Version x86_64 kann parallel zur arm64 Version installiert werden._ arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" ``` +#### Apple Intel Chip + Auf OSX wird das SDK nur dann unterstützt, wenn OpenSSL auf dem System installiert ist. ```sh brew install openssl@1.1 ``` -Die Environment-Variable ```DYLD_LIBRARY_PATH``` muss auf den Pfad zu OpenSSL verweisen. +Die Environment-Variable `DYLD_LIBRARY_PATH` muss auf den Pfad zu OpenSSL verweisen. _Beispiele:_ diff --git a/E2ETest/RejectSubmissionTest.cs b/E2ETest/RejectSubmissionTest.cs index cff129269469f8dc3bc34f4a9be3cfed0583f49d..1864dffc0e93641b43b3226db4e869968841f212 100644 --- a/E2ETest/RejectSubmissionTest.cs +++ b/E2ETest/RejectSubmissionTest.cs @@ -89,7 +89,7 @@ public class RejectSubmissionTest : EndToEndTestBase { public void Sender_GetSubmissionState_AfterRejecting() { // Act var status = Sender.GetStatusForSubmission(_caseId, Settings.DestinationId); - + // Assert status.Count.Should().BeGreaterThan(0); status.ForEach( diff --git a/FitConnect/Encryption/CertificateHelper.cs b/FitConnect/Encryption/CertificateHelper.cs index 691a54eff175c575a9ebbb5546f22f6bc1901114..92a6abee65fdc52db579ca7ef3060722d8464459 100644 --- a/FitConnect/Encryption/CertificateHelper.cs +++ b/FitConnect/Encryption/CertificateHelper.cs @@ -1,3 +1,4 @@ +using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; using System.Text.Unicode; @@ -29,21 +30,16 @@ public class CertificateHelper { _logger?.LogDebug("Issuers: {Issuer}", certificate.Issuer); if (rootCertificate != null) { - certificateChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; certificateChain.ChainPolicy.CustomTrustStore.AddRange(rootCertificate); - certificateChain.ChainPolicy.ExtraStore.AddRange(rootCertificate); - certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.Online; - certificateChain.ChainPolicy.RevocationFlag = X509RevocationFlag.ExcludeRoot; - certificateChain.ChainPolicy.DisableCertificateDownloads = false; - // certificateChain.ChainPolicy.VerificationFlags = X509VerificationFlags.IgnoreEndRevocationUnknown; - certificateChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; + certificateChain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust; + certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.Offline; + certificateChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EndCertificateOnly; _logger?.LogDebug("Using custom root certificate"); } else { certificateChain.ChainPolicy.RevocationMode = X509RevocationMode.Online; certificateChain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain; certificateChain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag; - certificateChain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 0, 30); } @@ -55,9 +51,11 @@ public class CertificateHelper { var statusAggregation = certificateChain.ChainStatus.Aggregate("", (r, s) => r + "\n\t - " + s.Status + ": " + s.StatusInformation); - if (certificateChain.ChainStatus.Length > 0) + if (certificateChain.ChainStatus.Length > 0) { _logger?.Log(logLevel, "Certificate status: {ObjStatusInformation}", statusAggregation); + // _logger?.Log(logLevel, "Chain status: {ChainStatus}", certificateChain.ToDisplayString()); + } return result; } @@ -95,4 +93,58 @@ public static class X509Certificate2Extensions { File.WriteAllText(fileName + ".pem", content); File.WriteAllText(fileName + ".json", JsonConvert.SerializeObject(certificate)); } + + public static string ToDisplayString(this X509Chain chain) { + var b = new StringBuilder(); + + b.AppendLine($"[{nameof(chain.ChainPolicy.RevocationFlag)}]"); + b.AppendLine($" {chain.ChainPolicy.RevocationFlag}"); + b.AppendLine(); + b.AppendLine($"[{nameof(chain.ChainPolicy.RevocationMode)}]"); + b.AppendLine($" {chain.ChainPolicy.RevocationMode}"); + b.AppendLine(); + b.AppendLine($"[{nameof(chain.ChainPolicy.VerificationFlags)}]"); + b.AppendLine($" {chain.ChainPolicy.VerificationFlags}"); + b.AppendLine(); + b.AppendLine($"[{nameof(chain.ChainPolicy.VerificationTime)}]"); + b.AppendLine($" {chain.ChainPolicy.VerificationTime}"); + b.AppendLine(); + b.AppendLine($"[Application Policy]"); + foreach (var policy in chain.ChainPolicy.ApplicationPolicy) { + b.AppendLine($" {policy.ToDisplayString()}"); + } + + b.AppendLine(); + b.AppendLine($"[Certificate Policy]"); + foreach (var policy in chain.ChainPolicy.CertificatePolicy) { + b.AppendLine($" {policy.ToDisplayString()}"); + } + + b.AppendLine(); + b.AppendLine($"[Elements]"); + foreach (var (element, index) in chain.ChainElements.Cast<X509ChainElement>() + .Select((element, index) => (element, index))) { + b.AppendLine(); + b.AppendLine($"[Element {index + 1}]"); + b.AppendLine(); + b.Append(element.Certificate.ToString()); + b.AppendLine(); + b.AppendLine($"[Status]"); + foreach (var status in element.ChainElementStatus) { + b.AppendLine($" {status.ToDisplayString()}"); + } + } + + return b.ToString(); + } + + public static string ToDisplayString(this Oid oid) { + return oid.FriendlyName == oid.Value + ? $"{oid.Value}" + : $"{oid.FriendlyName}: {oid.Value}"; + } + + public static string ToDisplayString(this X509ChainStatus status) { + return $"{status.Status}: {status.StatusInformation}"; + } } diff --git a/FitConnect/Encryption/FitEncryption.cs b/FitConnect/Encryption/FitEncryption.cs index 9a611b597f5087c6e67278f680b19704f87e7cc5..ff3ad1e384d03550cf703492b09eadc9dff9e4ec 100644 --- a/FitConnect/Encryption/FitEncryption.cs +++ b/FitConnect/Encryption/FitEncryption.cs @@ -141,7 +141,7 @@ public class FitEncryption { } - private string CreateSecurityEventToken(string submissionId, + internal string CreateSecurityEventToken(string submissionId, string caseId, string destinationId, string eventName, object? content) { @@ -150,11 +150,7 @@ public class FitEncryption { var subject = "submission:" + submissionId; _jwtHeader = - new JwtHeader(new SigningCredentials(signingKey, "PS512")) { - //{ "typ", "secevent+jwt" }, - // { "kid", signingKey.KeyId }, - // { "alg", "PS512" } - }; + new JwtHeader(new SigningCredentials(signingKey, "PS512")); _jwtHeader["typ"] = "secevent+jwt"; @@ -240,8 +236,8 @@ public class FitEncryption { logger?.LogTrace("Verifying JWT {Token}", signature); - // if (key.KeySize != 4096) - // throw new Exception("Key size must be 4096"); + if (key.KeySize != 4096) + throw new Exception("Key size must be 4096"); var result = tokenHandler.ValidateToken(signature, new TokenValidationParameters { ValidAlgorithms = new[] { "PS512" }, @@ -260,9 +256,6 @@ public class FitEncryption { RequireExpirationTime = false, IgnoreTrailingSlashWhenValidatingAudience = true, TryAllIssuerSigningKeys = true - - // set clockskew to zero so tokens expire exactly at token expiration time (instead of 5 minutes later) - // ClockSkew = TimeSpan.Zero }); var error = result.Exception?.Message; diff --git a/FitConnect/FitConnect.csproj b/FitConnect/FitConnect.csproj index c4077619411f1911413c68b3bb5023ffd09bc28a..14e74adc213758e659cdd315beecc32ca86fc7f6 100644 --- a/FitConnect/FitConnect.csproj +++ b/FitConnect/FitConnect.csproj @@ -6,8 +6,18 @@ <Nullable>enable</Nullable> <AssemblyVersion></AssemblyVersion> <FileVersion></FileVersion> - <PackageVersion>0.1.0-beta.1</PackageVersion> - <Title>FitConnect SDK</Title> + <PackageVersion>0.0.1</PackageVersion> + <Title>FIT-Connect .NET SDK</Title> + <Description>Library for sending and receiving submissions via FIT-Connect</Description> + <Copyright>2022 FIT-Connect contributors</Copyright> + <PackageProjectUrl>https://docs.fitko.de/fit-connect/</PackageProjectUrl> + <Authors>Föderale IT-Kooperation (FITKO)</Authors> + <PackageLicense>https://git.fitko.de/fit-connect/sdk-dotnet/-/blob/main/LICENSE.txt</PackageLicense> + <RepositoryUrl>https://git.fitko.de/fit-connect/sdk-dotnet</RepositoryUrl> + <RepositoryType>GIT</RepositoryType> + <PackageTags>egovernment, ozg, fitconnect</PackageTags> + <PackageReleaseNotes>initial version of the SDK</PackageReleaseNotes> + <PackageIcon>icon.png</PackageIcon> </PropertyGroup> <ItemGroup> @@ -25,6 +35,7 @@ </ItemGroup> <ItemGroup> + <None Include="icon.png" Pack="true" Visible="false" PackagePath="" /> <None Remove="metadata.schema.json" /> <EmbeddedResource Include="metadata.schema.json" /> </ItemGroup> diff --git a/FitConnect/FitConnectClient.cs b/FitConnect/FitConnectClient.cs index 19089979576abe25e29fb04b368216e49d167689..91f2fe1a8c06dd41ff70f5dc857985aa57c0577c 100644 --- a/FitConnect/FitConnectClient.cs +++ b/FitConnect/FitConnectClient.cs @@ -7,6 +7,8 @@ using FitConnect.Services.Interfaces; using FitConnect.Services.Models.v1.Submission; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.Tokens; +using Newtonsoft.Json.Schema; +using ValidationError = NJsonSchema.Validation.ValidationError; namespace FitConnect; @@ -18,6 +20,15 @@ public abstract class FitConnectClient { protected readonly bool VerifiedKeysAreMandatory; + /// <summary> + /// Validates <paramref name="dataString"/> against <paramref name="schemaString"/> and returns the validation errors if any appear + /// </summary> + public static async Task<ICollection<ValidationError>> ValidateSchemaAsync( + string dataString, string schemaString) { + var schema = await NJsonSchema.JsonSchema.FromJsonAsync(schemaString); + return schema.Validate(dataString); + } + protected FitConnectClient(FitConnectEnvironment environment, string clientId, string clientSecret, ILogger? logger = null, @@ -104,9 +115,9 @@ public abstract class FitConnectClient { public List<SecurityEventToken> GetStatusForSubmission(string caseId, string destinationId, bool skipTest = false) { var events = SubmissionService.GetStatusForSubmissionAsync(caseId, destinationId, skipTest) - .Result; - return events?.Where(s => s != null) - .Select(e => new SecurityEventToken(e!)).ToList() ?? new List<SecurityEventToken>(); + .Result?.Select(e => new SecurityEventToken(e!)).ToList() ?? + new List<SecurityEventToken>(); + return events; } /// <summary> diff --git a/FitConnect/HttpCalls/SenderCalls.http b/FitConnect/HttpCalls/SenderCalls.http index 57695ee4231e76bfee2aa9e4e4680f911f97789b..5da29a439a652900654d2e140b711c64e9df7518 100644 --- a/FitConnect/HttpCalls/SenderCalls.http +++ b/FitConnect/HttpCalls/SenderCalls.http @@ -90,6 +90,12 @@ Content-Type: application/json Authorization: Bearer {{token}} +### Get well known keys in Production +GET https://submission-api-prod.fit-connect.niedersachsen.de/.well-known/jwks.json +Accept: application/json +Content-Type: application/json + + ### GET Destination from DVDV GET https://dvdv-testsystem.governikus.de/dvdv-fitconnect/v1/destinations/{{destinationId}} Accept: application/json diff --git a/FitConnect/Models/SecurityEventToken.cs b/FitConnect/Models/SecurityEventToken.cs index de3b3ec1000f0dd55eafb8e7101134256034c753..ded9ac85a4951dfb2d712cfafece3dba3bfc4f77 100644 --- a/FitConnect/Models/SecurityEventToken.cs +++ b/FitConnect/Models/SecurityEventToken.cs @@ -18,7 +18,6 @@ public enum EventType { } public class SecurityEventToken { - public const string CreateSubmissionSchema = "https://schema.fitko.de/fit-connect/events/create-submission"; @@ -40,14 +39,13 @@ public class SecurityEventToken { public const string DeleteSubmissionSchema = "https://schema.fitko.de/fit-connect/events/delete-submission"; - public SecurityEventToken(string jwtEncodedString) { - Token = new JsonWebToken(jwtEncodedString); + TokenString = jwtEncodedString; EventType = DecodeEventType(Token.Claims); - if (Token.Claims.All(c => c.Type != "iat")) + if (Token.Claims.All(c => c.Type != "iat")) return; - + var iat = Token.Claims.FirstOrDefault(c => c.Type == "iat")!.Value; if (long.TryParse(iat, out var timeEpoch)) EventTime = DateTime.UnixEpoch.AddSeconds(timeEpoch); @@ -59,7 +57,8 @@ public class SecurityEventToken { public Events? Event { get; set; } public object? Payload { get; set; } - public JsonWebToken Token { get; set; } + public JsonWebToken Token => new JsonWebToken(TokenString); + public string TokenString { get; set; } private EventType DecodeEventType(IEnumerable<Claim> claims) { var eventsClaim = claims.FirstOrDefault(c => c.Type == "events"); @@ -87,14 +86,12 @@ public class SecurityEventToken { ForwardSubmissionSchema)) return EventType.Forward; if (eventsClaim.Value.Contains( - RejectSubmissionSchema)) { Problems = GetProblems(events?.Values?.FirstOrDefault()?.ToString() ?? ""); return EventType.Reject; } - if (eventsClaim.Value.Contains(AcceptSubmissionSchema)) return EventType.Accept; diff --git a/FitConnect/Router.cs b/FitConnect/Router.cs index 51d28dce0aa17511d9579d3b545cf1dde2dee3d9..665540a4bdea63c5a8fa4b10dcd28528bfa3b2f4 100644 --- a/FitConnect/Router.cs +++ b/FitConnect/Router.cs @@ -3,12 +3,13 @@ using FitConnect.Encryption; using FitConnect.Models; using FitConnect.Services; using FitConnect.Services.Interfaces; -using IdentityModel; +using Jose; using Microsoft.Extensions.Logging; using Microsoft.IdentityModel.JsonWebTokens; using Microsoft.IdentityModel.Tokens; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using Base64Url = IdentityModel.Base64Url; using Route = FitConnect.Services.Models.v1.Routes.Route; namespace FitConnect; @@ -37,36 +38,47 @@ internal class Router : IRouter { foreach (var route in routes) { _logger?.LogInformation("Testing destination {DestinationId}", route.DestinationId); var verifyJwt = await VerifyDestinationSignature(route); + if (!verifyJwt) { + throw new Exception($"Invalid destination signature"); + } verifyJwt &= await VerifyDestinationParametersSignature(route); - if (!verifyJwt) throw new Exception("Invalid signature"); + if (!verifyJwt) { + throw new Exception($"Invalid destination parameter signature"); + } + + verifyJwt &= VerifySubmissionHost(route); + + if (!verifyJwt) { + throw new Exception( + $"SubmissionHost does not match DestinationParameters SubmissionUrl"); + } } return routes; } + private bool VerifySubmissionHost(Route route) { + var signature = new JsonWebToken(route.DestinationSignature); + var payload = + JsonConvert.DeserializeObject<dynamic>( + Base64UrlEncoder.Decode(signature.EncodedPayload)); - /// <summary> - /// Finding Areas - /// </summary> - /// <param name="filter"></param> - /// <param name="totalCount"></param> - /// <param name="offset"></param> - /// <param name="limit"></param> - /// <returns></returns> - public IEnumerable<Area> GetAreas(string filter, out int totalCount, int offset = 0, - int limit = 100) { - var dto = _routeService.GetAreas(filter, offset, limit).Result; - totalCount = dto?.TotalCount ?? 0; - return dto?.Areas ?? new List<Area>(); + string? submissionHost = payload?.submissionHost; + if (submissionHost == null) return false; + + var destinationUri = new Uri(route.DestinationParameters.SubmissionUrl); + + return destinationUri.Host == submissionHost; } + private async Task<bool> VerifyDestinationParametersSignature(Route route) { + // Get Key from SubmissionAPI var submissionKey = await GetSubmissionServiceValidationJwk(route.DestinationParameters.SubmissionUrl); - // Get Key from SubmissionAPI var parameterJson = JsonConvert.SerializeObject(route.DestinationParameters, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, @@ -111,11 +123,27 @@ internal class Router : IRouter { var result = await client.GetAsync("/.well-known/jwks.json"); return await result.Content.ReadAsStringAsync(); } + + + /// <summary> + /// Finding Areas + /// </summary> + /// <param name="filter"></param> + /// <param name="totalCount"></param> + /// <param name="offset"></param> + /// <param name="limit"></param> + /// <returns></returns> + public IEnumerable<Area> GetAreas(string filter, out int totalCount, int offset = 0, + int limit = 100) { + var dto = _routeService.GetAreas(filter, offset, limit).Result; + totalCount = dto?.TotalCount ?? 0; + return dto?.Areas ?? new List<Area>(); + } } public class OrderedContractResolver : DefaultContractResolver { - protected override IList<JsonProperty> CreateProperties(Type type, - MemberSerialization memberSerialization) { + protected override System.Collections.Generic.IList<JsonProperty> CreateProperties( + System.Type type, MemberSerialization memberSerialization) { NamingStrategy = new CamelCaseNamingStrategy(); return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName) .ToList(); diff --git a/FitConnect/Sender.cs b/FitConnect/Sender.cs index 01a67d0ee1a5e56e12db55b99d082ed70de21088..994660b3a02eba0fab49bf226bc06640d277663a 100644 --- a/FitConnect/Sender.cs +++ b/FitConnect/Sender.cs @@ -158,7 +158,7 @@ public partial class Sender : FitConnectClient, ISender, ISenderWithDestination, created.SubmissionId, Submission.CaseId); var encryptedAttachments = Encrypt(PublicKey!, Submission.Attachments); - UploadAttachmentsAsync(Submission.Id!, encryptedAttachments); + UploadAttachments(Submission.Id!, encryptedAttachments); return this; } @@ -256,7 +256,7 @@ public partial class Sender : FitConnectClient, ISender, ISenderWithDestination, /// </summary> /// <param name="submissionId">Submissions ID</param> /// <param name="encryptedAttachments">Encrypted attachments with id and content</param> - private bool UploadAttachmentsAsync(string submissionId, + private bool UploadAttachments(string submissionId, Dictionary<string, string> encryptedAttachments) { try { foreach (var (id, content) in encryptedAttachments) { diff --git a/FitConnect/Services/RouteService.cs b/FitConnect/Services/RouteService.cs index e64ae440b201d9ab8212e04243c2f7d73557b059..5a17a872274d0efa5238d7da0c4473fa7f429017 100644 --- a/FitConnect/Services/RouteService.cs +++ b/FitConnect/Services/RouteService.cs @@ -22,8 +22,7 @@ internal class RouteService : RestCallService, IRouteService { public async Task<List<Route>> GetDestinationIdAsync(string leikaKey, string? ags, string? ars, string? areaId) { - if (ars == null && ags == null && areaId == null) - throw new ArgumentException("Either ars, ags or areaId must be specified."); + ValidateParametersForGetDestinationId(ags, ars, areaId); var result = await RestCall<RoutesListDto>($"/routes?leikaKey={leikaKey}" + (ags != null ? $"&ags={ags}" : "") + @@ -34,6 +33,19 @@ internal class RouteService : RestCallService, IRouteService { return result?.Routes?.ToList() ?? new List<Route>(); } + private void ValidateParametersForGetDestinationId(string? ags, string? ars, string? areaId) { + if (ars == null && ags == null && areaId == null) + throw new ArgumentException("Either ars, ags or areaId must be specified."); + + var paramCount = 0; + if (ags != null) paramCount++; + if (ars != null) paramCount++; + if (areaId != null) paramCount++; + + if (paramCount != 1) + throw new ArgumentException("Only one of ars, ags or areaId must be specified."); + } + public async Task<AreaList?> GetAreas(string filter, int offset = 0, int limit = 100) { var result = await RestCall<AreaList>( $"/areas?areaSearchexpression={filter}&offset={offset}&limit={limit}", diff --git a/FitConnect/Services/SubmissionService.cs b/FitConnect/Services/SubmissionService.cs index b937358b8a2811609152e5224eddfe87ce9aca70..f29a496adf5a323b1622c24b5d9c20195198b2a7 100644 --- a/FitConnect/Services/SubmissionService.cs +++ b/FitConnect/Services/SubmissionService.cs @@ -254,8 +254,21 @@ internal class SubmissionService : RestCallService, ISubmissionService { : keySet.Keys.Append(_signatureValidationKey)).ToList(); - return (await GetKeyIdsFromEvent(events, destinationId, keys.Select(k => k.Kid).ToList())) - .Union(keys); + var result = + (await GetKeyIdsFromEvent(events, destinationId, keys.Select(k => k.Kid).ToList())) + .Union(keys).ToList(); + + var valid = result.Aggregate(true, + (a, key) => (new CertificateHelper(_logger).ValidateCertificate(key))); + if (!valid) { + _logger?.LogError("(SET Events verification) Invalid certificate"); + throw new ArgumentException("(SET Events verification) Invalid certificate"); + } + else { + _logger?.LogInformation("(SET Events verification) Certificate is valid"); + } + + return result; } private async Task<IEnumerable<JsonWebKey>> GetKeyIdsFromEvent(EventLogDto events, diff --git a/FitConnect/Subscriber.cs b/FitConnect/Subscriber.cs index eb2454d06bb0c4d5c9adad3d15e6b3a5e3d99e4a..745355ca5f04abc46e985e67a6ac33487d2f3ab8 100644 --- a/FitConnect/Subscriber.cs +++ b/FitConnect/Subscriber.cs @@ -174,6 +174,9 @@ public class Subscriber : FitConnectClient, } + /// <summary> + /// Accept or reject submission + /// </summary> private void CompleteSubmission(Submission submission, FinishSubmissionStatus status, Problems[]? problems = null) { if (submission.Id == null || submission.CaseId == null || @@ -224,7 +227,7 @@ public class Subscriber : FitConnectClient, var result = VerifyCallback(callbackSecret, timestamp, content); if (result != authentication) - throw new ArgumentException("Request is not authentic"); + throw new ArgumentException("Verified request does not match authentication"); return true; } } diff --git a/FitConnect/icon.png b/FitConnect/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1ea9ec540cf2d5c901463380c9db4a7704714d79 Binary files /dev/null and b/FitConnect/icon.png differ diff --git a/IntegrationTests/CallbackTest.cs b/IntegrationTests/CallbackTest.cs index 52a30c78eae27d8bebaab0a8b3afd50b25d93e67..86bd62d855ae0c8a6a964708a517f895c275beee 100644 --- a/IntegrationTests/CallbackTest.cs +++ b/IntegrationTests/CallbackTest.cs @@ -13,22 +13,12 @@ namespace IntegrationTests; [TestFixture] public class CallbackTest { + private HttpRequest _request = null!; + private string _callbackSecret = ""; + + [SetUp] public void Setup() { - // POST /callbacks/fit-connect - // callback-authentication: 798cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df - // callback-timestamp: 1672527599 - // - // {"type":"https://schema.fitko.de/fit-connect/submission-api/callbacks/new-submissions","submissionIds":["f39ab143-d91a-474a-b69f-b00f1a1873c2"]} - - // HttpRequest request = null!; - // request.Headers - // request.Headers.Add("callback-authentication", - // "798cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df"); - // request.Headers.Add("callback-timestamp", DateTime.Now.ToEpochTime().ToString()); - // request.Method = "POST"; - // request.ContentType = "application/json"; - var memoryStream = new MemoryStream(); var streamWriter = new StreamWriter(memoryStream); @@ -37,6 +27,7 @@ public class CallbackTest { streamWriter.Flush(); memoryStream.Position = 0; + // Request = new DefaultHttpRequest(new DefaultHttpContext()) { // Body = new StreamBody(memoryStream) // }; @@ -56,13 +47,11 @@ public class CallbackTest { mock.Setup(w => w.Method).Returns("POST"); mock.Setup(w => w.Body).Returns(memoryStream); - Request = mock.Object; + _request = mock.Object; _callbackSecret = Container.Create().Resolve<MockSettings>().CallbackSecret; } - private HttpRequest Request = null!; - private string _callbackSecret = ""; [Test] public void ValidRequest_WithSingeValues() { @@ -82,33 +71,33 @@ public class CallbackTest { [Test] public void ValidRequest() { // Assert - FitConnect.Subscriber.VerifyCallback(_callbackSecret, Request).Should().Be(true); + FitConnect.Subscriber.VerifyCallback(_callbackSecret, _request).Should().Be(true); } [Test] public void RequestAge_Fails() { // Arrange - Request.Headers["callback-timestamp"] = "1641066653"; + _request.Headers["callback-timestamp"] = "1641066653"; // Atc // Assert Assert.Throws<ArgumentException>(() => { - FitConnect.Subscriber.VerifyCallback(_callbackSecret, Request); - })! - .Message.Should().Be("Request is too old"); + FitConnect.Subscriber.VerifyCallback(_callbackSecret, _request); + }) + ?.Message.Should().Be("Request is too old"); } [Test] public void RequestAuthentication_Fails() { // Arrange - Request.Headers["callback-authentication"] = + _request.Headers["callback-authentication"] = "898cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df"; // Atc // Assert Assert.Throws<ArgumentException>(() => { - FitConnect.Subscriber.VerifyCallback(_callbackSecret, Request); - })! - .Message.Should().Be("Request is not authentic"); + FitConnect.Subscriber.VerifyCallback(_callbackSecret, _request); + }) + ?.Message.Should().Be("Verified request does not match authentication"); } } diff --git a/IntegrationTests/CertificateValidation.cs b/IntegrationTests/CertificateValidation.cs index 5a59b0a3b2e2be8678b9768efe701dcccdfb9858..c0ca8ac6dc85117299741acddb3d5bdfc6e22d1b 100644 --- a/IntegrationTests/CertificateValidation.cs +++ b/IntegrationTests/CertificateValidation.cs @@ -130,38 +130,29 @@ public class CertificateValidation { .Should().BeTrue(); } + [Ignore("Not the scope of this branch - reactivate later")] [Test] - [Ignore("Not implemented")] public void CheckPemFiles() { - var files = System.IO.Directory.GetFiles("./certificates"); + var files = Directory.GetFiles("./certificates"); var success = 0; var failed = 0; var failedCerts = new List<string>(); - foreach (var fileName in files.Where(f => !f.EndsWith("root.pem"))) { + foreach (var fileName in files) { _logger.LogInformation("Checking file: {FileName}", fileName); + var certificateContents = Directory.GetFiles("./certificates/roots"); + var rootCertificates = certificateContents + .Select(file => new X509Certificate2(file)).ToArray(); + certificateContents.Length.Should().Be(rootCertificates.Length); - if (fileName.EndsWith(".pem")) { - var certificate = X509Certificate2.CreateFromPem(File.ReadAllText(fileName)); - var valid = _certificateHelper.ValidateCertificate(certificate, out var states, - null); - if (valid) { - success++; - } - else { - failed++; - failedCerts.Add(fileName); - } - } if (fileName.EndsWith(".json")) { var shouldFail = !fileName.Contains("/valid"); var jwk = new JsonWebKey(File.ReadAllText(fileName)); var valid = _certificateHelper.ValidateCertificate(jwk, - shouldFail ? LogLevel.Warning : LogLevel.Critical, - Directory.GetFiles("./certificates/roots") - .Select(file => new X509Certificate2(file)).ToArray()); + shouldFail ? LogLevel.Warning : LogLevel.Critical, rootCertificates + ); if (shouldFail) valid = !valid; @@ -174,6 +165,18 @@ public class CertificateValidation { failedCerts.Add(fileName); } } + else { + var certificate = new X509Certificate2(fileName); + var valid = _certificateHelper.ValidateCertificate(certificate, out var states, + null); + if (valid) { + success++; + } + else { + failed++; + failedCerts.Add(fileName); + } + } } _logger.LogWarning("Failed certificates: {Certs}", diff --git a/IntegrationTests/IntegrationTests.csproj b/IntegrationTests/IntegrationTests.csproj index 9b8b7191f7b8e445d2b4267c954870582561c3d6..8dd3721c0ca364e3d36d5bcde31b0c97317accdd 100644 --- a/IntegrationTests/IntegrationTests.csproj +++ b/IntegrationTests/IntegrationTests.csproj @@ -36,13 +36,13 @@ <None Update="certificates\www-amazon-de-zertifikatskette.pem"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> - <None Update="certificates\validEncJW_KeyUse.json"> + <None Update="certificates\invalidEncJW_KeyUse.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> - <None Update="certificates\validEncJWK.json"> + <None Update="certificates\invalidEncJWK.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> - <None Update="certificates\validSigJWK.json"> + <None Update="certificates\invalidSigJWK.json"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> <None Update="certificates\revokedEncJWK.json"> @@ -135,6 +135,51 @@ <None Update="certificates\roots\ca.26305.der"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </None> + <None Update="certificates\invalid_publicKey_encryption.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\invalid_publicKey_signature_verification.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\valid_productionKey.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1212.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1230.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1249.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1269.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1286.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1305.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1322.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1340.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\roots\ca.1357.der"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\validEncJWK_KeyUse.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\ValidEncJWK.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> + <None Update="certificates\validSigJWK.json"> + <CopyToOutputDirectory>Always</CopyToOutputDirectory> + </None> </ItemGroup> </Project> diff --git a/IntegrationTests/ProxyTest.cs b/IntegrationTests/ProxyTest.cs index 275d7cd9f0eb377d712922c8f815b9c9578a88a6..7d7fee7f90337f1e562d626506ad85de5de6cee1 100644 --- a/IntegrationTests/ProxyTest.cs +++ b/IntegrationTests/ProxyTest.cs @@ -13,6 +13,8 @@ using NUnit.Framework; namespace IntegrationTests; +// Change to https://hub.docker.com/r/mitmproxy/mitmproxy/ + [Ignore("Not testable in docker container, take to long to run every time")] [TestFixture] public class ProxyTest { @@ -25,8 +27,8 @@ public class ProxyTest { File.WriteAllText("proxy/access.log", ""); _container = new TestcontainersBuilder<TestcontainersContainer>() - .WithImage("ubuntu/squid") - .WithPortBinding("3128", "3128") + .WithImage("mitmproxy/mitmproxy") + .WithPortBinding("3128", "8081") .WithBindMount(path, @"/var/log/squid") .Build(); _container.StartAsync().Wait(); @@ -39,7 +41,7 @@ public class ProxyTest { new FitConnect.Sender(FitConnectEnvironment.Testing, _id, _secret) - .WithProxy("localhost", 3128); + .WithProxy("localhost", 3128, null, null); #pragma warning disable SYSLIB0014 _webClient = new WebClient { diff --git a/IntegrationTests/Routing/RoutingTests.cs b/IntegrationTests/Routing/RoutingTests.cs index bd8faef4eb1ee8daa9e6d21ca862a627518afe7b..c87cbd5d8770a864828b85262e4ce4efbc965b5f 100644 --- a/IntegrationTests/Routing/RoutingTests.cs +++ b/IntegrationTests/Routing/RoutingTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Text; using FitConnect; @@ -93,19 +93,35 @@ public class RoutingTests { } [Test] - [Order(80)] - public void BaseSignatureTest() { - /* - Data from Python - {"keys": [{"alg": "PS512", "e": "AQAB", "key_ops": ["verify"], "kid": "32858147-f090-43a9-b2fd-d26ae5b41c03", "kty": "RSA", "n": "3UltHnOKF38UbpfQ-sG6_p-_SiWTzDohXE1PT6I5xpnQncczwyAF8j-PZv8TxxtvuVxYVJdt610B3bMCwPaD1Kw2ht1mS_Iu2d_SqTxhCDF4S0yrCovo6fKVQXG-IPsbs59-1lefUqjrUbkxRRxH335p22Lmg9Wf0XBJqMWsMBNnul8lKjLP7krk9TKQKR35js42oIliEakByWpq6kzPPlgV4mkFlivnseQaLxItFMh6rs5lLS1kHdrYfwCWS97wf5TO2ubygo_617qKAeN3e6mYYb6k_30WYwnH-vc1_gMf8JuBOZ7sO-OpW09XrQLBkUXTHbIcmLExpm3yOizc-UI9NrLgwomPyg0Ml1n3bKpIWq6dIpyO2LJ6euu8CzPDs2NNv12Z_FUuJMPQWvt-nv3g6AgOHPJZQJ8TjI27yPgjFwtef91OT1jQ_IzgCVp7EFNn-rZtgxhV2PLmhXick8jLszodEgcki5Ooj5oBbze0yV7zPZ3cdXle_NwDKlxkwbAs3WCyTY7mOVA0avsW9cDDwplQnBMDbwKSGOqjDHVI2EY2SN4ur0lYp-gm5IxtTMm2d4CjAXh4XVHBvNNLxBJb9byDA7qM1QgJa1DaKdV5yoTL2VNTWUBYbK4ag7K6avACM7KjL9YvzUIYNnegu5qFtUVXfjBlE_5wNbYzBTk"}]} - {"encryptionKid":"1e95f036-ccff-425c-a0de-3d89d5cc59fa","metadataVersions":["1.0.0"],"publicKeys":{"keys":[{"alg":"RSA-OAEP-256","e":"AQAB","key_ops":["wrapKey"],"kid":"1e95f036-ccff-425c-a0de-3d89d5cc59fa","kty":"RSA","n":"2ch1Ir3_Lyb9_HxW9RqIodxi9fXhix6APKwqiSfi-JlRqVa1FoAFsW1nW0IbQjkW6sNkWUFWuA9AfVoKT9nnIcnLSjSQ84SI-if6qTbornyKvBjXg8BSecSUUPYyT0-4NmxrXMGHPYbJV7fQq6jPXzkWC5P5jqQ7ObraQp752BcE_JVQUmFk1ydhhbnroHpGUkA-8jG_kiVL-lAz7uUmZCh6i3ZJD5HN1JOE5LMyzUQOgOFUUPiviBywQAbPQuDLydZ2diO5wqm4mwBadAAzC27OllSkNXSgnd9MVajXmtBVpz2ksMaSCAbfB4rK9q5jXd5YMwu1ZlA-ZuWKYm_p1GjbZdx4xk9w23Zkgnrr3SvWnW98686fd03MG1ACAGatq5FcAvGp8BXCKwz5FpyYtOONx-tECYHcHhx_SafOe9siLYObLmBSsLF3TAjigZjpGOuEjBtKyv5OwJj-6YfIYYjlofuqv6GHUGDvv8iQsy6U4eHCoRpKJzmX6L22MUQgisYvQdGY2jbdEni3g_MpciMIbnZFLENVrqHXYcgHN-SbXl_GVR5b3F0ompES55xA7fuYlt4lp5j0IUo0OWM2_tYYHtASZicVAwnbzLQZEA0u-wXZr0ByMWE07Od_KaLUomlBPi1Ac_FU3KOx0APKJUm7D3__aiLZll3Sh9EnIvE","x5c":["MIIE6jCCAtKgAwIBAgIGAXo1pG0GMA0GCSqGSIb3DQEBCwUAMDYxNDAyBgNVBAMMK2FaV0ptMG1XRmFxRGFOTkJOaDNabVluVmw3eG52SVMxVEJ5SHhaVnNUNm8wHhcNMjEwNjIyMjEzMzI2WhcNMjIwNDE4MjEzMzI2WjA2MTQwMgYDVQQDDCthWldKbTBtV0ZhcURhTk5CTmgzWm1ZblZsN3hudklTMVRCeUh4WlZzVDZvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2ch1Ir3/Lyb9/HxW9RqIodxi9fXhix6APKwqiSfi+JlRqVa1FoAFsW1nW0IbQjkW6sNkWUFWuA9AfVoKT9nnIcnLSjSQ84SI+if6qTbornyKvBjXg8BSecSUUPYyT0+4NmxrXMGHPYbJV7fQq6jPXzkWC5P5jqQ7ObraQp752BcE/JVQUmFk1ydhhbnroHpGUkA+8jG/kiVL+lAz7uUmZCh6i3ZJD5HN1JOE5LMyzUQOgOFUUPiviBywQAbPQuDLydZ2diO5wqm4mwBadAAzC27OllSkNXSgnd9MVajXmtBVpz2ksMaSCAbfB4rK9q5jXd5YMwu1ZlA+ZuWKYm/p1GjbZdx4xk9w23Zkgnrr3SvWnW98686fd03MG1ACAGatq5FcAvGp8BXCKwz5FpyYtOONx+tECYHcHhx/SafOe9siLYObLmBSsLF3TAjigZjpGOuEjBtKyv5OwJj+6YfIYYjlofuqv6GHUGDvv8iQsy6U4eHCoRpKJzmX6L22MUQgisYvQdGY2jbdEni3g/MpciMIbnZFLENVrqHXYcgHN+SbXl/GVR5b3F0ompES55xA7fuYlt4lp5j0IUo0OWM2/tYYHtASZicVAwnbzLQZEA0u+wXZr0ByMWE07Od/KaLUomlBPi1Ac/FU3KOx0APKJUm7D3//aiLZll3Sh9EnIvECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAFi24JFEmYL2sOqaaQOMHCVm+WQ9QyN/rkvjHLG8th5TIF8NJ4uwxl+n6GBO2uOZOBixHbU5pK9slyt9Xryw7CbL/hhRGSs3GSFR2hkxoBHdzfFnAwegnd1H5XHQwSgh43jGOn9AxsyISpkvFo7+DObh9Cv8NsjpL57No4UJ62XggfYJW43u4+I/fHcDwnvIN0dvnYpQkCbTmrjLa5IkGim9BynfW1k6VuxLya1SsyjBHWw2YNQ4xBJI4OzXhL6OmBSrohF1RbIKOpjtqGfkXZpxufLNV2CnL36r/41c1nop6cmCIMDtnFEQdAmGe8m/8wvVpLnks59C02/WotlK3iORHCYB6G0pHMKFB4zOVANYtLFgqTgN4HNciV3FN0TvI19qzjkAdcB+m+L+LdseIzcQ/BToGyPvWkJ1mvJZIp0ejnlMWIl3VlNpMKeZ7lJbPpZvABO00lK+FynhITtb6N29toE+7JgHAlWmxw6PFFY1x+3xTHBTOU0oUR/TyKsEU0+bNSb/0S+ZyodmnIFbgYWarjK5pUwfTRyPyeVEukg1Gf30c/7f/5KZ/dpLFUNBb/YTNIzYEhGNUyLJ1mrSz33gr4MtvI4uSu0Jpr1NrwdMGvFhr5QOCULuoC9KlokusUpi0GTH0gK3K/TUi6qvU+Wztfa7mqah17BVVFT1wATs="]}]},"replyChannels":{"email":{"usePgp":true}},"status":"active","submissionSchemas":[{"mimeType":"application/json","schemaUri":"https://schema.fitko.de/fim/s06000178_0.4.schema.json"}],"submissionUrl":"https://submission-api-testing.fit-connect.fitko.dev"} - eyJraWQiOiIzMjg1ODE0Ny1mMDkwLTQzYTktYjJmZC1kMjZhZTViNDFjMDMiLCJjdHkiOiJhcHBsaWNhdGlvblwvam9zZSIsInR5cCI6IkpPU0UiLCJhbGciOiJQUzUxMiJ9..gOj_e9VS3pAJAtwkGa_SXSOeu-uX-40oD69kR9lwrfYV1_71rHLRcD8fDpD8sFXAXUvg8WNdBjXu5AgKR7jfOsAZezc5IUsh-VbPsRVG7ArZuwD1Q2RRJRDBbGEqsTT2wjxvGEnRDnPEXdrfPE6X3ron0ePk6KkVwBj4sY_g0be1Zq7GAL7C7cA_aVFPfkD3w-4-0F-Jrha9RG6w7j4r4ZvGrHierxHtm78JpSRG5rNxxa_Zhdrr7d_HiYdwVg0npfvRYHcI5ZxUP2bCS24NKn_oKNhPxyTWFwxIFrd0RQo6WQKxqmRfsJQ3Vt1SvgpgSHguogFuhfSlvt3Gu66b8XLvjwIoAF74Q7ry3h8EsHseGyYLWy076dggaRAh-HBYM8dZjSisEpCw5eYsemHBN6iVAxinkAFsTB0UYLwYsoKo7Lybsfb9ohz-IzHwufwOKJXTmHtxCrXlvCFliohOT9i6aHzBCRYT09l-ZcgnbfEMY4Jy8KiMZleMgJTIgGg3xMI80vdOZZAtQsosgT39JnKQ6GzPAfNxLnV3AB7VDlT-5xwaV28DexmoHTcFZoU8rYqB4SFfSsg6VHC6UHjLJFaGPtCS4de_S8sHTVKnxmfX8xBKU3U54dTSztn1UdQ4bDD8iTOKXX24uEUqWe6IV1D19lF8X8TyyOXm_fb0oPM - eyJraWQiOiIzMjg1ODE0Ny1mMDkwLTQzYTktYjJmZC1kMjZhZTViNDFjMDMiLCJjdHkiOiJhcHBsaWNhdGlvblwvam9zZSIsInR5cCI6IkpPU0UiLCJhbGciOiJQUzUxMiJ9.eyJlbmNyeXB0aW9uS2lkIjoiMWU5NWYwMzYtY2NmZi00MjVjLWEwZGUtM2Q4OWQ1Y2M1OWZhIiwibWV0YWRhdGFWZXJzaW9ucyI6WyIxLjAuMCJdLCJwdWJsaWNLZXlzIjp7ImtleXMiOlt7ImFsZyI6IlJTQS1PQUVQLTI1NiIsImUiOiJBUUFCIiwia2V5X29wcyI6WyJ3cmFwS2V5Il0sImtpZCI6IjFlOTVmMDM2LWNjZmYtNDI1Yy1hMGRlLTNkODlkNWNjNTlmYSIsImt0eSI6IlJTQSIsIm4iOiIyY2gxSXIzX0x5YjlfSHhXOVJxSW9keGk5ZlhoaXg2QVBLd3FpU2ZpLUpsUnFWYTFGb0FGc1cxblcwSWJRamtXNnNOa1dVRld1QTlBZlZvS1Q5bm5JY25MU2pTUTg0U0ktaWY2cVRib3JueUt2QmpYZzhCU2VjU1VVUFl5VDAtNE5teHJYTUdIUFliSlY3ZlFxNmpQWHprV0M1UDVqcVE3T2JyYVFwNzUyQmNFX0pWUVVtRmsxeWRoaGJucm9IcEdVa0EtOGpHX2tpVkwtbEF6N3VVbVpDaDZpM1pKRDVITjFKT0U1TE15elVRT2dPRlVVUGl2aUJ5d1FBYlBRdURMeWRaMmRpTzV3cW00bXdCYWRBQXpDMjdPbGxTa05YU2duZDlNVmFqWG10QlZwejJrc01hU0NBYmZCNHJLOXE1alhkNVlNd3UxWmxBLVp1V0tZbV9wMUdqYlpkeDR4azl3MjNaa2ducnIzU3ZXblc5ODY4NmZkMDNNRzFBQ0FHYXRxNUZjQXZHcDhCWENLd3o1RnB5WXRPT054LXRFQ1lIY0hoeF9TYWZPZTlzaUxZT2JMbUJTc0xGM1RBamlnWmpwR091RWpCdEt5djVPd0pqLTZZZklZWWpsb2Z1cXY2R0hVR0R2djhpUXN5NlU0ZUhDb1JwS0p6bVg2TDIyTVVRZ2lzWXZRZEdZMmpiZEVuaTNnX01wY2lNSWJuWkZMRU5WcnFIWFljZ0hOLVNiWGxfR1ZSNWIzRjBvbXBFUzU1eEE3ZnVZbHQ0bHA1ajBJVW8wT1dNMl90WVlIdEFTWmljVkF3bmJ6TFFaRUEwdS13WFpyMEJ5TVdFMDdPZF9LYUxVb21sQlBpMUFjX0ZVM0tPeDBBUEtKVW03RDNfX2FpTFpsbDNTaDlFbkl2RSIsIng1YyI6WyJNSUlFNmpDQ0F0S2dBd0lCQWdJR0FYbzFwRzBHTUEwR0NTcUdTSWIzRFFFQkN3VUFNRFl4TkRBeUJnTlZCQU1NSzJGYVYwcHRNRzFYUm1GeFJHRk9Ua0pPYUROYWJWbHVWbXczZUc1MlNWTXhWRUo1U0hoYVZuTlVObTh3SGhjTk1qRXdOakl5TWpFek16STJXaGNOTWpJd05ERTRNakV6TXpJMldqQTJNVFF3TWdZRFZRUUREQ3RoV2xkS2JUQnRWMFpoY1VSaFRrNUNUbWd6V20xWmJsWnNOM2h1ZGtsVE1WUkNlVWg0V2xaelZEWnZNSUlDSWpBTkJna3Foa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQTJjaDFJcjMvTHliOS9IeFc5UnFJb2R4aTlmWGhpeDZBUEt3cWlTZmkrSmxScVZhMUZvQUZzVzFuVzBJYlFqa1c2c05rV1VGV3VBOUFmVm9LVDlubkljbkxTalNRODRTSStpZjZxVGJvcm55S3ZCalhnOEJTZWNTVVVQWXlUMCs0Tm14clhNR0hQWWJKVjdmUXE2alBYemtXQzVQNWpxUTdPYnJhUXA3NTJCY0UvSlZRVW1GazF5ZGhoYm5yb0hwR1VrQSs4akcva2lWTCtsQXo3dVVtWkNoNmkzWkpENUhOMUpPRTVMTXl6VVFPZ09GVVVQaXZpQnl3UUFiUFF1REx5ZFoyZGlPNXdxbTRtd0JhZEFBekMyN09sbFNrTlhTZ25kOU1WYWpYbXRCVnB6MmtzTWFTQ0FiZkI0cks5cTVqWGQ1WU13dTFabEErWnVXS1ltL3AxR2piWmR4NHhrOXcyM1prZ25ycjNTdlduVzk4Njg2ZmQwM01HMUFDQUdhdHE1RmNBdkdwOEJYQ0t3ejVGcHlZdE9PTngrdEVDWUhjSGh4L1NhZk9lOXNpTFlPYkxtQlNzTEYzVEFqaWdaanBHT3VFakJ0S3l2NU93SmorNllmSVlZamxvZnVxdjZHSFVHRHZ2OGlRc3k2VTRlSENvUnBLSnptWDZMMjJNVVFnaXNZdlFkR1kyamJkRW5pM2cvTXBjaU1JYm5aRkxFTlZycUhYWWNnSE4rU2JYbC9HVlI1YjNGMG9tcEVTNTV4QTdmdVlsdDRscDVqMElVbzBPV00yL3RZWUh0QVNaaWNWQXduYnpMUVpFQTB1K3dYWnIwQnlNV0UwN09kL0thTFVvbWxCUGkxQWMvRlUzS094MEFQS0pVbTdEMy8vYWlMWmxsM1NoOUVuSXZFQ0F3RUFBVEFOQmdrcWhraUc5dzBCQVFzRkFBT0NBZ0VBRmkyNEpGRW1ZTDJzT3FhYVFPTUhDVm0rV1E5UXlOL3JrdmpITEc4dGg1VElGOE5KNHV3eGwrbjZHQk8ydU9aT0JpeEhiVTVwSzlzbHl0OVhyeXc3Q2JML2hoUkdTczNHU0ZSMmhreG9CSGR6ZkZuQXdlZ25kMUg1WEhRd1NnaDQzakdPbjlBeHN5SVNwa3ZGbzcrRE9iaDlDdjhOc2pwTDU3Tm80VUo2MlhnZ2ZZSlc0M3U0K0kvZkhjRHdudklOMGR2bllwUWtDYlRtcmpMYTVJa0dpbTlCeW5mVzFrNlZ1eEx5YTFTc3lqQkhXdzJZTlE0eEJKSTRPelhoTDZPbUJTcm9oRjFSYklLT3BqdHFHZmtYWnB4dWZMTlYyQ25MMzZyLzQxYzFub3A2Y21DSU1EdG5GRVFkQW1HZThtLzh3dlZwTG5rczU5QzAyL1dvdGxLM2lPUkhDWUI2RzBwSE1LRkI0ek9WQU5ZdExGZ3FUZ040SE5jaVYzRk4wVHZJMTlxemprQWRjQittK0wrTGRzZUl6Y1EvQlRvR3lQdldrSjFtdkpaSXAwZWpubE1XSWwzVmxOcE1LZVo3bEpiUHBadkFCTzAwbEsrRnluaElUdGI2TjI5dG9FKzdKZ0hBbFdteHc2UEZGWTF4KzN4VEhCVE9VMG9VUi9UeUtzRVUwK2JOU2IvMFMrWnlvZG1uSUZiZ1lXYXJqSzVwVXdmVFJ5UHllVkV1a2cxR2YzMGMvN2YvNUtaL2RwTEZVTkJiL1lUTkl6WUVoR05VeUxKMW1yU3ozM2dyNE10dkk0dVN1MEpwcjFOcndkTUd2RmhyNVFPQ1VMdW9DOUtsb2t1c1VwaTBHVEgwZ0szSy9UVWk2cXZVK1d6dGZhN21xYWgxN0JWVkZUMXdBVHM9Il19XX0sInJlcGx5Q2hhbm5lbHMiOnsiZW1haWwiOnsidXNlUGdwIjp0cnVlfX0sInN0YXR1cyI6ImFjdGl2ZSIsInN1Ym1pc3Npb25TY2hlbWFzIjpbeyJtaW1lVHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJzY2hlbWFVcmkiOiJodHRwczovL3NjaGVtYS5maXRrby5kZS9maW0vczA2MDAwMTc4XzAuNC5zY2hlbWEuanNvbiJ9XSwic3VibWlzc2lvblVybCI6Imh0dHBzOi8vc3VibWlzc2lvbi1hcGktdGVzdGluZy5maXQtY29ubmVjdC5maXRrby5kZXYifQ.gOj_e9VS3pAJAtwkGa_SXSOeu-uX-40oD69kR9lwrfYV1_71rHLRcD8fDpD8sFXAXUvg8WNdBjXu5AgKR7jfOsAZezc5IUsh-VbPsRVG7ArZuwD1Q2RRJRDBbGEqsTT2wjxvGEnRDnPEXdrfPE6X3ron0ePk6KkVwBj4sY_g0be1Zq7GAL7C7cA_aVFPfkD3w-4-0F-Jrha9RG6w7j4r4ZvGrHierxHtm78JpSRG5rNxxa_Zhdrr7d_HiYdwVg0npfvRYHcI5ZxUP2bCS24NKn_oKNhPxyTWFwxIFrd0RQo6WQKxqmRfsJQ3Vt1SvgpgSHguogFuhfSlvt3Gu66b8XLvjwIoAF74Q7ry3h8EsHseGyYLWy076dggaRAh-HBYM8dZjSisEpCw5eYsemHBN6iVAxinkAFsTB0UYLwYsoKo7Lybsfb9ohz-IzHwufwOKJXTmHtxCrXlvCFliohOT9i6aHzBCRYT09l-ZcgnbfEMY4Jy8KiMZleMgJTIgGg3xMI80vdOZZAtQsosgT39JnKQ6GzPAfNxLnV3AB7VDlT-5xwaV28DexmoHTcFZoU8rYqB4SFfSsg6VHC6UHjLJFaGPtCS4de_S8sHTVKnxmfX8xBKU3U54dTSztn1UdQ4bDD8iTOKXX24uEUqWe6IV1D19lF8X8TyyOXm_fb0oPM - {"encryptionKid": "1e95f036-ccff-425c-a0de-3d89d5cc59fa", "metadataVersions": ["1.0.0"], "publicKeys": {"keys": [{"alg": "RSA-OAEP-256", "e": "AQAB", "key_ops": ["wrapKey"], "kid": "1e95f036-ccff-425c-a0de-3d89d5cc59fa", "kty": "RSA", "n": "2ch1Ir3_Lyb9_HxW9RqIodxi9fXhix6APKwqiSfi-JlRqVa1FoAFsW1nW0IbQjkW6sNkWUFWuA9AfVoKT9nnIcnLSjSQ84SI-if6qTbornyKvBjXg8BSecSUUPYyT0-4NmxrXMGHPYbJV7fQq6jPXzkWC5P5jqQ7ObraQp752BcE_JVQUmFk1ydhhbnroHpGUkA-8jG_kiVL-lAz7uUmZCh6i3ZJD5HN1JOE5LMyzUQOgOFUUPiviBywQAbPQuDLydZ2diO5wqm4mwBadAAzC27OllSkNXSgnd9MVajXmtBVpz2ksMaSCAbfB4rK9q5jXd5YMwu1ZlA-ZuWKYm_p1GjbZdx4xk9w23Zkgnrr3SvWnW98686fd03MG1ACAGatq5FcAvGp8BXCKwz5FpyYtOONx-tECYHcHhx_SafOe9siLYObLmBSsLF3TAjigZjpGOuEjBtKyv5OwJj-6YfIYYjlofuqv6GHUGDvv8iQsy6U4eHCoRpKJzmX6L22MUQgisYvQdGY2jbdEni3g_MpciMIbnZFLENVrqHXYcgHN-SbXl_GVR5b3F0ompES55xA7fuYlt4lp5j0IUo0OWM2_tYYHtASZicVAwnbzLQZEA0u-wXZr0ByMWE07Od_KaLUomlBPi1Ac_FU3KOx0APKJUm7D3__aiLZll3Sh9EnIvE", "x5c": ["MIIE6jCCAtKgAwIBAgIGAXo1pG0GMA0GCSqGSIb3DQEBCwUAMDYxNDAyBgNVBAMMK2FaV0ptMG1XRmFxRGFOTkJOaDNabVluVmw3eG52SVMxVEJ5SHhaVnNUNm8wHhcNMjEwNjIyMjEzMzI2WhcNMjIwNDE4MjEzMzI2WjA2MTQwMgYDVQQDDCthWldKbTBtV0ZhcURhTk5CTmgzWm1ZblZsN3hudklTMVRCeUh4WlZzVDZvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2ch1Ir3/Lyb9/HxW9RqIodxi9fXhix6APKwqiSfi+JlRqVa1FoAFsW1nW0IbQjkW6sNkWUFWuA9AfVoKT9nnIcnLSjSQ84SI+if6qTbornyKvBjXg8BSecSUUPYyT0+4NmxrXMGHPYbJV7fQq6jPXzkWC5P5jqQ7ObraQp752BcE/JVQUmFk1ydhhbnroHpGUkA+8jG/kiVL+lAz7uUmZCh6i3ZJD5HN1JOE5LMyzUQOgOFUUPiviBywQAbPQuDLydZ2diO5wqm4mwBadAAzC27OllSkNXSgnd9MVajXmtBVpz2ksMaSCAbfB4rK9q5jXd5YMwu1ZlA+ZuWKYm/p1GjbZdx4xk9w23Zkgnrr3SvWnW98686fd03MG1ACAGatq5FcAvGp8BXCKwz5FpyYtOONx+tECYHcHhx/SafOe9siLYObLmBSsLF3TAjigZjpGOuEjBtKyv5OwJj+6YfIYYjlofuqv6GHUGDvv8iQsy6U4eHCoRpKJzmX6L22MUQgisYvQdGY2jbdEni3g/MpciMIbnZFLENVrqHXYcgHN+SbXl/GVR5b3F0ompES55xA7fuYlt4lp5j0IUo0OWM2/tYYHtASZicVAwnbzLQZEA0u+wXZr0ByMWE07Od/KaLUomlBPi1Ac/FU3KOx0APKJUm7D3//aiLZll3Sh9EnIvECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAFi24JFEmYL2sOqaaQOMHCVm+WQ9QyN/rkvjHLG8th5TIF8NJ4uwxl+n6GBO2uOZOBixHbU5pK9slyt9Xryw7CbL/hhRGSs3GSFR2hkxoBHdzfFnAwegnd1H5XHQwSgh43jGOn9AxsyISpkvFo7+DObh9Cv8NsjpL57No4UJ62XggfYJW43u4+I/fHcDwnvIN0dvnYpQkCbTmrjLa5IkGim9BynfW1k6VuxLya1SsyjBHWw2YNQ4xBJI4OzXhL6OmBSrohF1RbIKOpjtqGfkXZpxufLNV2CnL36r/41c1nop6cmCIMDtnFEQdAmGe8m/8wvVpLnks59C02/WotlK3iORHCYB6G0pHMKFB4zOVANYtLFgqTgN4HNciV3FN0TvI19qzjkAdcB+m+L+LdseIzcQ/BToGyPvWkJ1mvJZIp0ejnlMWIl3VlNpMKeZ7lJbPpZvABO00lK+FynhITtb6N29toE+7JgHAlWmxw6PFFY1x+3xTHBTOU0oUR/TyKsEU0+bNSb/0S+ZyodmnIFbgYWarjK5pUwfTRyPyeVEukg1Gf30c/7f/5KZ/dpLFUNBb/YTNIzYEhGNUyLJ1mrSz33gr4MtvI4uSu0Jpr1NrwdMGvFhr5QOCULuoC9KlokusUpi0GTH0gK3K/TUi6qvU+Wztfa7mqah17BVVFT1wATs="]}]}, "replyChannels": {"email": {"usePgp": True}}, "status": "active", "submissionSchemas": [{"mimeType": "application/json", "schemaUri": "https://schema.fitko.de/fim/s06000178_0.4.schema.json"}], "submissionUrl": "https://submission-api-testing.fit-connect.fitko.dev"} - {"encryptionKid": "1e95f036-ccff-425c-a0de-3d89d5cc59fa", "metadataVersions": ["1.0.0"], "publicKeys": {"keys": [{"key_ops":["wrapKey"],"x5c":["MIIE6jCCAtKgAwIBAgIGAXo1pG0GMA0GCSqGSIb3DQEBCwUAMDYxNDAyBgNVBAMMK2FaV0ptMG1XRmFxRGFOTkJOaDNabVluVmw3eG52SVMxVEJ5SHhaVnNUNm8wHhcNMjEwNjIyMjEzMzI2WhcNMjIwNDE4MjEzMzI2WjA2MTQwMgYDVQQDDCthWldKbTBtV0ZhcURhTk5CTmgzWm1ZblZsN3hudklTMVRCeUh4WlZzVDZvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA2ch1Ir3/Lyb9/HxW9RqIodxi9fXhix6APKwqiSfi+JlRqVa1FoAFsW1nW0IbQjkW6sNkWUFWuA9AfVoKT9nnIcnLSjSQ84SI+if6qTbornyKvBjXg8BSecSUUPYyT0+4NmxrXMGHPYbJV7fQq6jPXzkWC5P5jqQ7ObraQp752BcE/JVQUmFk1ydhhbnroHpGUkA+8jG/kiVL+lAz7uUmZCh6i3ZJD5HN1JOE5LMyzUQOgOFUUPiviBywQAbPQuDLydZ2diO5wqm4mwBadAAzC27OllSkNXSgnd9MVajXmtBVpz2ksMaSCAbfB4rK9q5jXd5YMwu1ZlA+ZuWKYm/p1GjbZdx4xk9w23Zkgnrr3SvWnW98686fd03MG1ACAGatq5FcAvGp8BXCKwz5FpyYtOONx+tECYHcHhx/SafOe9siLYObLmBSsLF3TAjigZjpGOuEjBtKyv5OwJj+6YfIYYjlofuqv6GHUGDvv8iQsy6U4eHCoRpKJzmX6L22MUQgisYvQdGY2jbdEni3g/MpciMIbnZFLENVrqHXYcgHN+SbXl/GVR5b3F0ompES55xA7fuYlt4lp5j0IUo0OWM2/tYYHtASZicVAwnbzLQZEA0u+wXZr0ByMWE07Od/KaLUomlBPi1Ac/FU3KOx0APKJUm7D3//aiLZll3Sh9EnIvECAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAFi24JFEmYL2sOqaaQOMHCVm+WQ9QyN/rkvjHLG8th5TIF8NJ4uwxl+n6GBO2uOZOBixHbU5pK9slyt9Xryw7CbL/hhRGSs3GSFR2hkxoBHdzfFnAwegnd1H5XHQwSgh43jGOn9AxsyISpkvFo7+DObh9Cv8NsjpL57No4UJ62XggfYJW43u4+I/fHcDwnvIN0dvnYpQkCbTmrjLa5IkGim9BynfW1k6VuxLya1SsyjBHWw2YNQ4xBJI4OzXhL6OmBSrohF1RbIKOpjtqGfkXZpxufLNV2CnL36r/41c1nop6cmCIMDtnFEQdAmGe8m/8wvVpLnks59C02/WotlK3iORHCYB6G0pHMKFB4zOVANYtLFgqTgN4HNciV3FN0TvI19qzjkAdcB+m+L+LdseIzcQ/BToGyPvWkJ1mvJZIp0ejnlMWIl3VlNpMKeZ7lJbPpZvABO00lK+FynhITtb6N29toE+7JgHAlWmxw6PFFY1x+3xTHBTOU0oUR/TyKsEU0+bNSb/0S+ZyodmnIFbgYWarjK5pUwfTRyPyeVEukg1Gf30c/7f/5KZ/dpLFUNBb/YTNIzYEhGNUyLJ1mrSz33gr4MtvI4uSu0Jpr1NrwdMGvFhr5QOCULuoC9KlokusUpi0GTH0gK3K/TUi6qvU+Wztfa7mqah17BVVFT1wATs="],"kid":"1e95f036-ccff-425c-a0de-3d89d5cc59fa","kty":"RSA","alg":"RSA-OAEP-256","n":"2ch1Ir3_Lyb9_HxW9RqIodxi9fXhix6APKwqiSfi-JlRqVa1FoAFsW1nW0IbQjkW6sNkWUFWuA9AfVoKT9nnIcnLSjSQ84SI-if6qTbornyKvBjXg8BSecSUUPYyT0-4NmxrXMGHPYbJV7fQq6jPXzkWC5P5jqQ7ObraQp752BcE_JVQUmFk1ydhhbnroHpGUkA-8jG_kiVL-lAz7uUmZCh6i3ZJD5HN1JOE5LMyzUQOgOFUUPiviBywQAbPQuDLydZ2diO5wqm4mwBadAAzC27OllSkNXSgnd9MVajXmtBVpz2ksMaSCAbfB4rK9q5jXd5YMwu1ZlA-ZuWKYm_p1GjbZdx4xk9w23Zkgnrr3SvWnW98686fd03MG1ACAGatq5FcAvGp8BXCKwz5FpyYtOONx-tECYHcHhx_SafOe9siLYObLmBSsLF3TAjigZjpGOuEjBtKyv5OwJj-6YfIYYjlofuqv6GHUGDvv8iQsy6U4eHCoRpKJzmX6L22MUQgisYvQdGY2jbdEni3g_MpciMIbnZFLENVrqHXYcgHN-SbXl_GVR5b3F0ompES55xA7fuYlt4lp5j0IUo0OWM2_tYYHtASZicVAwnbzLQZEA0u-wXZr0ByMWE07Od_KaLUomlBPi1Ac_FU3KOx0APKJUm7D3__aiLZll3Sh9EnIvE","e":"AQAB"}]},"replyChannels":{"email":{"usePgp":true}},"status":"active","submissionSchemas":[{"mimeType":"application/json","schemaUri":"https://schema.fitko.de/fim/s06000178_0.4.schema.json"}],"submissionUrl":"https://submission-api-testing.fit-connect.fitko.dev"} - */ + public void FindDestination_WithAreaIdAndAgs_ShouldThrowException() { + // Arrange + var leika = "99123456760610"; + var ags = "06435014"; + var areaId = "931"; + + // Act + Action action = () => _router.FindDestinationsAsync(leika, ags, areaId).Wait(); + // Assert + action.Should().Throw<ArgumentException>(); + } + + [Test] + public void FindDestination_WithoutAgsAndAreaId_ShouldThrowException() { + // Arrange + var leika = "99123456760610"; + // Act + Action action = () => _router.FindDestinationsAsync(leika).Wait(); + + // Assert + action.Should().Throw<ArgumentException>(); + } + + [Test] + [Order(80)] + public void BaseSignatureTest() { + var parameter = JsonConvert.DeserializeObject(_body); // Get Key from SubmissionAPI var parameterJson = JsonConvert.SerializeObject(parameter, @@ -157,8 +173,6 @@ public class RoutingTests { [Test] [Order(100)] public void BaseSignatureTest_DownloadKeyAndRoute() { - // GET destination from here: https://dvdv-testsystem.governikus.de/dvdv-fitconnect/v1/destinations/{id - var body = new DvdvService("https://dvdv-testsystem.governikus.de/dvdv-fitconnect", logger: _logger).GetDestinationJson("d40e7b13-da98-4b09-9e16-bbd61ca81510") diff --git a/IntegrationTests/Subscriber/SubscriberTestUnHappyPath.cs b/IntegrationTests/Subscriber/SubscriberTestUnHappyPath.cs index 20c6ba8b62f990d96a0c70dccb6b607cff0a94e7..6da31d6854a799bee618eef447692f6ab538d803 100644 --- a/IntegrationTests/Subscriber/SubscriberTestUnHappyPath.cs +++ b/IntegrationTests/Subscriber/SubscriberTestUnHappyPath.cs @@ -16,6 +16,7 @@ public class SubscriberTestUnHappyPath : SubscriberTestBase { // Act var valid = JsonHelper.VerifyMetadata(wrongData); + // validationErrors.ToList().ForEach(v => Logger?.LogWarning("ERROR: {V}", v.ToString())); // Assert valid.Should().BeFalse(); @@ -29,6 +30,7 @@ public class SubscriberTestUnHappyPath : SubscriberTestBase { // Act && Assert Assert.Throws<JsonReaderException>(() => { var valid = JsonHelper.VerifyMetadata(wrongData, Logger); + // validationErrors.ToList().ForEach(v => Logger.LogWarning("ERROR: {V}", v.ToString())); }); } } diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000000000000000000000000000000000000..ee10b30618210f16ded8f67f4375cd7a9bebfea0 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,9 @@ +# Security Vulnerabilities + +Our team is trying to make sure that our code is secure. Nevertheless, we are very grateful for anyone who finds a security vulnerability and reports it to us. + +Please do not report security vulnerabilities through GitLab directly. Because GitLab issues are public, this could result in directly disclosing the vulnerability. + +Please send any request regarding a potential security vulnerability with all the information that could help to `it-sicherheit <at> fitko.de`. See our [security.txt](https://www.fitko.de/.well-known/security.txt) for details. + +If possible please note the release version or the commit id of the main branch that you have investigated. diff --git a/changelog.md b/changelog.md index 1a588142cbbc9dcf8188bb279c128e462a77aedf..5b1261b9c0fae81a4b72a89e7747254dac4332ca 100644 --- a/changelog.md +++ b/changelog.md @@ -1,3 +1,12 @@ # Changelog +All notable changes to the FIT-Connect .NET SDK will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/), +and this project adheres to [Semantic Versioning](https://semver.org/). + ## [Unreleased] + +## [1.0.0] - TBD +### Added +- initial version of the SDK to send submissions and subscribe to destinations diff --git a/nuget-readme.md b/nuget-readme.md new file mode 100644 index 0000000000000000000000000000000000000000..920c7ebdc6bcbf9ca847dc653f9ffc0bdd5f884c --- /dev/null +++ b/nuget-readme.md @@ -0,0 +1,115 @@ +# FIT-Connect .NET SDK User Guide + +## Einleitung + +Das FIT-Connect .NET SDK bietet eine einfache Möglichkeit, sowohl einen Antragsteller (Sender) als auch einen Antragsempfänger (Subscriber) an FIT-Connect anzubinden. + +## Voraussetzungen + +### OSX + +Auf OSX wird das SDK nur dann unterstützt, wenn OpenSSL auf dem System installiert ist. +Zum Installieren von OpenSSL können Sie Homebrew verwenden: + +```sh +brew install openssl@1.1 +``` + +Die Environment-Variable ```DYLD_LIBRARY_PATH``` muss auf den Pfad zu OpenSSL verweisen. + +_Beispiele:_ + +```sh +export DYLD_LIBRARY_PATH=/usr/local/opt/openssl/lib +export DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib +``` + +### Sender + +Um einen Antrag mit dem SDK versenden zu können, werden eine ClientID und ein ClientSecret benötigt. +Diese können im Self-Service-Portal von FIT-Connect erzeugt werden. +[Hier](https://docs.fitko.de/fit-connect/docs/getting-started/account) ist beschrieben, wie Sie eine ClientID und ein ClientSecret erhalten. + +[Offizelle Dokumentation von FIT-Connect zum Versenden von Einreichungen (Anträgen)](https://docs.fitko.de/fit-connect/docs/sending/overview) + +### Subscriber + +Der Subscriber benötigt eine ClientID, ein ClientSecret und zwei Schlüsselpaare (im Format JSON Web Key) zum Verschlüsseln und Signieren. + +Zum Testen können selbstgenerierte Schlüsselpaare mit dem [hierzu bereitgestellten Tool](https://docs.fitko.de/fit-connect/docs/details/jwk-creation) erzeugt werden. + +In der Produktivumgebung müssen hierzu [Zertifikate der Verwaltungs-PKI zum Einsatz kommen](https://docs.fitko.de/fit-connect/docs/receiving/certificate). Aus einem Zertifikat der Verwaltungs-PKI können Sie die benötigten JSON-Schlüsselpaare erzeugen. [Hier](https://docs.fitko.de/fit-connect/docs/receiving/certificate) ist beschrieben, wie Sie zur Erzeugung der Schlüsselpaare ein Tool der FITKO nutzen. + +[Offizielle Dokumentation von FIT-Connect zum Abrufen von Einreichungen](https://docs.fitko.de/fit-connect/docs/receiving/overview) + +# Beispiele + +## Sender + +### Erstellen einer Einreichung (submission) + +Das folgende Beispiel zeigt, wie Sie das SDK in einem sendenden System nutzen, um eine Submission (eine Einreichung) zu erzeugen und zu senden: + +```csharp +var submission = Client + .GetSender(FitConnectEnvironment.Testing, clientId, clientSecret, logger) + .WithDestination(destinationId) + .WithServiceType("FIT Connect Demo", leikaKey) + .WithAttachments(new Attachment("Test.pdf", "Test Attachment")) + .WithData("{\"message\":\"Hello World\"}") + .Submit(); +``` + +Im Beispiel oben stellt das Argument "FitConnectEnvironment.Testing" die FIT-Connect-Endpunkte zur Verfügung, die aufgerufen werden sollen. +Das Argument 'destinationId" enthält die eindeutige Adresse des empfangenden Systems (Fachverfahrens), das die Antragsdaten über FIT-Connect erhalten soll. +Das Argument "leikaKey" stellt die ID der beantragten Leistung bereit. "Leika" ist die Abkürzung für "Leistungskatalog". + +## Subscriber + +### Erstellen eines Subscribers + +Das folgende Beispiel zeigt, wie Sie das SDK in einem empfangenden System nutzen, um einen Subscriber zu erzeugen, der Einreichungen von sendenden Systemen erhält. + +```csharp +var subscriber = Client.GetSubscriber(FitConnectEnvironment.Testing, clientId, + clientSecret, + privateKeyDecryption, + privateKeySigning, + publicKeyEncryption, + publicKeySignatureVerification, + logger); +``` + +Im Beispiel oben stellt das Argument "privateKeyDecryption" den JSON Web Key (JWK) für den privaten Schlüssel zum Entschlüsseln bereit. +Das Argument 'privateKeySigning" enthält den JWK des privaten Schlüssels zum Signieren. +Das Argument 'publicKeyEncryption" liefert den JWK des öffentlichen Schlüssels zum Verschlüsseln. +Das Argument 'publicKeySignatureVerification" enthält den JWK des öffentlichen Schlüssels zum Überprüfen der Signatur. + + +### Abrufen der verfügbaren Submissions + +Das folgende Beispiel zeigt, wie Sie das SDK in einem empfangenden System nutzen, um beim Server (dem Zustelldienst von FIT-Connect) nachzufragen, welche Einreichungen (Submissions) zur Abholung bereitliegen. + +```csharp +var submissions = subscriber.GetAvailableSubmissions(); +``` + +Das folgende Beispiel zeigt, wie Sie das SDK in einem empfangenden System nutzen, um bereitliegende Einreichungen mit ihren Anhängen vom Server (Zustelldienst) abzurufen. + +### Abrufen der Submissions mit den Anhängen + +```csharp +foreach (var submission in submissions) { + var subscriberWithSubmission = subscriber.RequestSubmission(submission.SubmissionId); + var data = subscriber.GetDataJson(); + var attachments = subscriberWithSubmission + .GetAttachments(); + // Submission accept + subscriberWithSubmission + .AcceptSubmission(); + // or submission reject + subscriberWithSubmission + .RejectSubmission(); + +} +``` diff --git a/nuget_api.template.txt b/nuget_api.template.txt new file mode 100644 index 0000000000000000000000000000000000000000..405b951fab29bf4e5f78b869bce0b3a8106970d5 --- /dev/null +++ b/nuget_api.template.txt @@ -0,0 +1 @@ +<< Just add your nuget api key here and rename the file to nuget_api.txt >> \ No newline at end of file diff --git a/readme.md b/readme.md index 0c95997b0084ef1b1274b21bc9638b6b681b51f4..275f0effde7cb149b0e8e43e4efcca648e3abdd9 100644 --- a/readme.md +++ b/readme.md @@ -1,46 +1,96 @@ -# Fit-Connect .NET SDK +<br /> +<div align="center"><img src="https://www.fitko.de/fileadmin/_processed_/b/9/csm_FIT-Connect_3e8e926015.jpg" alt="Logo" width="50%" height="50%"> </div> -For an implementation example take a look at the [DemoRunner](DemoRunner/Program.cs) + + +# FIT-Connect .NET SDK + +Für ein kurzes Beispiel zu Implementierung kann der [DemoRunner](DemoRunner/Program.cs) herangezogen werden. ## **!! IN DEVELOPMENT NOT FOR PRODUCTION USE !!** -**Fit-Connect .NET SDK** is a .NET library for the Fit-Connect API. +**FIT-Connect .NET SDK** ist eine .NET library for the FIT-Connect API. + +Das SDK ist als [NuGet Package](https://www.nuget.org/packages/FitConnect/) verfügbar. + +## Einleitung + +Das FIT-Connect .NET SDK bietet eine einfache Möglichkeit, sowohl einen Antragsteller (Sender) als auch einen Antragsempfänger (Subscriber) an FIT-Connect anzubinden. + +## Voraussetzungen -Die Verwendung des SDKs ist in der [documentation (ger)](./Documentation/documentation.de-DE.md) -erklärt +### OSX + +Auf OSX wird das SDK nur dann unterstützt, wenn OpenSSL auf dem System installiert ist. +Zum Installieren von OpenSSL können Sie Homebrew verwenden: + +```sh +brew install openssl@1.1 +``` + +Die Environment-Variable ```DYLD_LIBRARY_PATH``` muss auf den Pfad zu OpenSSL verweisen. + +_Beispiele:_ + +```sh +export DYLD_LIBRARY_PATH=/usr/local/opt/openssl/lib +export DYLD_LIBRARY_PATH=/usr/local/opt/openssl@1.1/lib +``` -## For information how to use the SDK and FitConnect visit: + +## Weitere Informationen zu FIT-Connect finden Sie hier: * [SDK-Dokumentation](./documentation/documentation.de-DE.md) -* [FIT-Connect Dokumentation](https://docs.fitko.de/fit-connect/docs) -* [FIT-Connect Dokumentation](https://fit-connect.com/docs) +* [FIT-Connect Dokumentation](https://docs.fitko.de/FIT-Connect/docs) +* [FIT-Connect Dokumentation](https://FIT-Connect.com/docs) -<!-- -## Structure -For the structure look the [Structure documentation](structure.md) ---> # Allgemeines ## Environment -Das FitConnect SDK kann an die folgenden Umgebungen angeschlossen werden: +Das FIT-Connect SDK kann an die folgenden Umgebungen angeschlossen werden: - Testing: ```FitConnectEnvironment.Testing``` - Staging: ```FitConnectEnvironment.Staging``` - Production: ```FitConnectEnvironment.Production``` -[FIT Connect Umgebungen](https://docs.fitko.de/fit-connect/docs/getting-started/first-steps/#environments) +[FIT Connect Umgebungen](https://docs.fitko.de/FIT-Connect/docs/getting-started/first-steps/#environments) Hierfür muss der Client die Environment auswählen oder einen eigenen Environment-Parameter übergeben. ## Credentials ClientId und ClientSecret sind die Grundlage, um einen Token vom OAuth-Server abfragen zu können. -Die beiden Werte sind im [Self-Service Portal der Testumgebung von FIT-Connect](https://portal.auth-testing.fit-connect.fitko.dev/clients) zu erstellen. +Die beiden Werte sind im [Self-Service Portal der Testumgebung von FIT-Connect](https://portal.auth-testing.FIT-Connect.fitko.dev/clients) zu erstellen. # Sender +Um einen Antrag mit dem SDK versenden zu können, werden eine ClientID und ein ClientSecret benötigt. +[Hier](https://docs.fitko.de/fit-connect/docs/getting-started/account) ist beschrieben, wie Sie eine ClientID und ein ClientSecret erhalten. + +[Offizelle Dokumentation von FIT-Connect zum Versenden von Einreichungen (Anträgen)](https://docs.fitko.de/fit-connect/docs/sending/overview) + +## Beispiel + +Das folgende Beispiel zeigt, wie Sie das SDK in einem sendenden System nutzen, um eine Submission (eine Einreichung) zu erzeugen und zu senden: + +```csharp +var submission = Client + .GetSender(FitConnectEnvironment.Testing, clientId, clientSecret, logger) + .WithDestination(destinationId) + .WithServiceType("FIT Connect Demo", leikaKey) + .WithAttachments(new Attachment("Test.pdf", "Test Attachment")) + .WithData("{\"message\":\"Hello World\"}") + .Submit(); +``` + +Im Beispiel oben stellt das Argument "FitConnectEnvironment.Testing" die FIT-Connect-Endpunkte zur Verfügung, die aufgerufen werden sollen. +Das Argument 'destinationId" enthält die eindeutige Adresse des empfangenden Systems (Fachverfahrens), das die Antragsdaten über FIT-Connect erhalten soll. +Das Argument "leikaKey" stellt die ID der beantragten Leistung bereit. "Leika" ist die Abkürzung für "Leistungskatalog". + +## Details + ```mermaid flowchart LR start([GetSender]) @@ -59,32 +109,32 @@ flowchart LR encData-->submit ``` -Das SDK verhindert auf Grund der FluentAPI, dass die Methoden in der falschen Reihe aufgerufen werden können. +Das SDK verhindert durch die FluentAPI, dass die Methoden in der falschen Reihenfolge aufgerufen werden können. -## GetSender(FitConnectEnvironment.Testing, clientId, clientSecret, logger) +### GetSender(FitConnectEnvironment.Testing, clientId, clientSecret, logger) Hier werden die FIT Connect Environment ausgewählt und die Credentials übergeben. Der Parameter ```logger``` ist optional und muss das Interface ```Microsoft.Extensions.Logging.ILogger``` implementieren. -## .WithDestination(destinationId) +### .WithDestination(destinationId) -Die Destination ID des Zustelldienstes muss an dieser Stelle übergeben werden. +Die `destinationId` des Zustellpunktes, an den die Submission geschickt werden soll, muss an dieser Stelle übergeben werden. _Noch nicht vollständig getestet!_<br> Eine Möglichkeit, die Destination ID zu ermitteln, geht über die Methode ``FindDestinationId()`` des Senders. -## .WithServiceType("FIT Connect Demo", leikaKey) +### .WithServiceType("FIT Connect Demo", leikaKey) Der Service Type muss an dieser Stelle übergeben werden. Hierfür wird ein Leistungsschlüssel (LeiKa-Schlüssel) benötigt. Leistungsschlüssel haben diese Form ```urn:de:fim:leika:leistung:99400048079000``` -## .WithAttachments(new Attachment("Test.pdf", "Test Attachment")) +### .WithAttachments(new Attachment("Test.pdf", "Test Attachment")) Die Anhänge zum Antrag werden mit ```.WithAttachments``` übergeben. -Diese Methode erwartet ein Array von Attachment Objekten die als ```params``` übergeben werden können. +Diese Methode erwartet ein Array von Attachment-Objekten, die als ```params``` übergeben werden können. -Das Attachment kann mit den folgenden Parametern erstellt werden: +Ein Attachment kann mit den folgenden Parametern erstellt werden: - Metadaten und byte[] content - Dateiname und Beschreibung @@ -94,30 +144,65 @@ Dazu werden zwei Konstruktoren bereitgestellt: - ```Attachment(Api.Metadata.Attachment metadata, byte[] content)``` - ```Attachment(string fileName, string description,)``` -## .WithData("{\"message\":\"Hello World\"}") +### .WithData("{\"message\":\"Hello World\"}") -Die Fachdaten werden als JSON String übergeben. +Die Fachdaten werden als JSON-String übergeben. -## .Submit() +### .Submit() Das Abschicken der Submission erfolgt mit diesem Aufruf. +# Subscriber + +Das folgende Beispiel zeigt, wie Sie das SDK in einem empfangenden System nutzen, um einen Subscriber zu erzeugen, der Einreichungen von sendenden Systemen erhält. + ## Beispiel ```csharp -var submission = Client - .GetSender(FitConnectEnvironment.Development, clientId, clientSecret, logger) - .WithDestination(destinationId) - .WithServiceType("FIT Connect Demo", leikaKey) - .WithAttachments(new Attachment("Test.pdf", "Test Attachment")) - .WithData("{\"message\":\"Hello World\"}") - .Submit(); +var subscriber = Client.GetSubscriber(FitConnectEnvironment.Testing, clientId, + clientSecret, + privateKeyDecryption, + privateKeySigning, + publicKeyEncryption, + publicKeySignatureVerification, + logger); ``` +Im Beispiel oben stellt das Argument "privateKeyDecryption" den JSON Web Key (JWK) für den privaten Schlüssel zum Entschlüsseln bereit. +Das Argument 'privateKeySigning" enthält den JWK des privaten Schlüssels zum Signieren. +Das Argument 'publicKeyEncryption" liefert den JWK des öffentlichen Schlüssels zum Verschlüsseln. +Das Argument 'publicKeySignatureVerification" enthält den JWK des öffentlichen Schlüssels zum Überprüfen der Signatur. -# Subscriber +### Abrufen der verfügbaren Submissions + +Das folgende Beispiel zeigt, wie Sie das SDK in einem empfangenden System nutzen, um beim Server (dem Zustelldienst von FIT-Connect) nachzufragen, welche Einreichungen (Submissions) zur Abholung bereitliegen. -Der Subscriber braucht zusätzliche Informationen, um die Submissions abrufen zu können. -Hier sind zusätzlich die Schlüssel zum Ver- und Entschlüsseln notwendig. +```csharp +var submissions = subscriber.GetAvailableSubmissions(); +``` + +Das folgende Beispiel zeigt, wie Sie das SDK in einem empfangenden System nutzen, um bereitliegende Einreichungen mit ihren Anhängen vom Server (Zustelldienst) abzurufen. + +### Abrufen der Submissions mit den Anhängen + +```csharp +foreach (var submission in submissions) { + var subscriberWithSubmission = subscriber.RequestSubmission(submission.SubmissionId); + var data = subscriber.GetDataJson(); + var attachments = subscriberWithSubmission + .GetAttachments(); + // Submission accept + subscriberWithSubmission + .AcceptSubmission(); + // or submission reject + subscriberWithSubmission + .RejectSubmission(); + +} +``` + + + +## Details ````mermaid flowchart LR @@ -147,12 +232,12 @@ flowchart LR ## .GetSubscriber(...) -Hier werden die FIT Connect Environment ausgewählt, die Keys und die Credentials übergeben. +Hier werden die FIT-Connect-Environment, die Keys und die Credentials übergeben. Der Parameter ```logger``` ist optional und muss das Interface ```Microsoft.Extensions.Logging.ILogger``` implementieren. ## .GetAvailableSubmissions() -Liefert eine Liste mit den verfügbaren Submissions zurück. +Liefert eine Liste der verfügbaren Submissions zurück. ## .RequestSubmission(submissionId) @@ -161,7 +246,7 @@ Der Rückgabewert der Funktion ist also _Subscriber mit einer Submission_ ## .GetDataJson() -Liefert die Fachdaten als JSON String zurück. +Liefert die Fachdaten als JSON-String zurück. ## .GetAttachments() @@ -170,11 +255,11 @@ Die Attachments können so geprüft werden. ## .AcceptSubmission() -Akzepiert die Submission und löscht diese vom Server. +Akzeptiert die Submission und löscht die Submission, die auf dem Server gespeichert ist. ## .RejectSubmission() -Weißt die Submission zurück. +Weist die Submission zurück. ## .ForwardSubmission() @@ -209,7 +294,7 @@ Weißt die Submission zurück. # Router -Die Client-Implementierung der Router API +Die Client-Implementierung der Routing-API ```csharp Client.GetRouter(FitConnectEnvironment.Development, logger); @@ -222,3 +307,4 @@ Client.GetRouter(FitConnectEnvironment.Development, logger); Source code is licensed under the [EUPL](LICENSES/EUPL-1.2.txt). Rechtlicher Hinweis: Dieses Software Development Kit (SDK) ist dazu bestimmt, die Anbindung einer Software an die FIT-Connect-Infrastruktur zu ermöglichen. Hierfür kann das SDK in die anzubindenden Software integriert werden. Erfolgt die Integration des SDK in unveränderter Form, liegt keine Bearbeitung im Sinne der EUPL bzw. des deutschen Urheberrechts vor. Die Art und Weise der Verlinkung des SDK führt insbesondere nicht zur Schaffung eines abgeleiteten Werkes. Die unveränderte Übernahme des SDK in eine anzubindende Software führt damit nicht dazu, dass die anzubindende Software unter den Bedingungen der EUPL zu lizenzieren ist. Für die Weitergabe des SDK selbst - in unveränderter oder bearbeiteter Form, als Quellcode oder ausführbares Programm - gelten die Lizenzbedingungen der EUPL in unveränderter Weise. + diff --git a/structure.md b/structure.md index 7449047f09344202257861f9c18b288de0a57562..e951b473d22abffd9b71576670f012e83171c272 100644 --- a/structure.md +++ b/structure.md @@ -16,10 +16,10 @@ RouteService } - FitConnectClient ..> SubmissionSender : public - FitConnectClient ..> SubmissionSubscriber : public - SubmissionSender --|> FunctionalBaseClass - SubmissionSubscriber --|> FunctionalBaseClass + FitConnectClient ..> Sender : public + FitConnectClient ..> Subscriber : public + Sender --|> FunctionalBaseClass + Subscriber --|> FunctionalBaseClass FunctionalBaseClass ..> FitConnectApiService : protected FitConnectApiService ..> RouteService : public diff --git a/version.sh b/version.sh index e3da4df0add6637e02224dad4127ccc1d30be6b2..9a083469de3acec9477cc4702f3ba8ba23fdd4e3 100755 --- a/version.sh +++ b/version.sh @@ -50,6 +50,8 @@ cp "FitConnect/bin/Release/FitConnect.$1.nupkg" . exit 0 +dotnet nuget push "FitConnect.$1.nupkg" -k $(cat nuget_api.txt) -s https://api.nuget.org/v3/index.json + #CURRENT=$(git branch | grep \* | cut -d ' ' -f2) #if [ "$CURRENT" != "master" ]; then # echo "Not on master branch"