diff --git a/E2ETests/E2ETests.csproj b/E2ETests/E2ETests.csproj index 5c22f888dff9e67e999ba2f5716fec2c0fedc16f..b78e88522ad81a1269c8f66db483d2d240b8be2b 100644 --- a/E2ETests/E2ETests.csproj +++ b/E2ETests/E2ETests.csproj @@ -8,15 +8,16 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="FluentAssertions" Version="6.7.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> - <PackageReference Include="NUnit" Version="3.13.2" /> - <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="FluentAssertions" Version="6.7.0"/> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0"/> + <PackageReference Include="NUnit" Version="3.13.2"/> + <PackageReference Include="NUnit3TestAdapter" Version="4.0.0"/> + <PackageReference Include="coverlet.collector" Version="3.1.0"/> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\FitConnect\FitConnect.csproj" /> + <ProjectReference Include="..\FitConnect\FitConnect.csproj"/> + <ProjectReference Include="..\Services\Services.csproj"/> </ItemGroup> </Project> diff --git a/E2ETests/OAuthServiceTest.cs b/E2ETests/OAuthServiceTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..bb7bdc5e37e0e3b8c438c96f1f45917558a39afa --- /dev/null +++ b/E2ETests/OAuthServiceTest.cs @@ -0,0 +1,64 @@ +using System; +using System.IO; +using FitConnect; +using FitConnect.Services; +using FluentAssertions; +using Newtonsoft.Json; +using NUnit.Framework; + +namespace E2ETests; + +public class OAuthServiceTest { + private string _clientId; + private string _clientSecret; + private OAuthService _oAuthService = null!; + + [OneTimeSetUp] + public void OneTimeSetup() { + // relative to the project execution directory + const string secretFile = "../../../http-client.private.env.json"; + + if (!File.Exists(secretFile)) { + // If the secret file is not found, create it with the default values + // The file will be pretty in C#11, when """ is introduced + File.WriteAllText(secretFile, @" +{ + ""sender"": { + ""id"": ""00000000-0000-0000-0000-000000000000"", + ""secret"": ""0000000000000000000000000000000000000000000"", + ""scope"": ""send:region:DE"" + } +}"); + throw new Exception("Please fill the secret.json file with your sender credentials"); + } + + var jsonContent = File.ReadAllText(secretFile); + var secret = JsonConvert.DeserializeObject<dynamic>(jsonContent); + _clientId = secret.sender.id; + _clientSecret = secret.sender.secret; + } + + + [SetUp] + public void SetUp() { + var endpoints = FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development); + _oAuthService = new OAuthService(endpoints.TokenUrl); + } + + [Test] + public void GetAccessToken_ExpiresInShouldBe1800_WithoutScope() { + var token = _oAuthService.GetTokenAsync(_clientId, _clientSecret).Result; + token.Should().NotBeNull(); + token!.ExpiresIn.Should().Be(1800); + token.Scope.Should().Be("send:region:DE"); + } + + [Test] + public void GetAccessToken_ScopeShouldMatch_WithScope() { + var token = _oAuthService.GetTokenAsync(_clientId, _clientSecret, "send:region:DE01010") + .Result; + token.Should().NotBeNull(); + token!.ExpiresIn.Should().Be(1800); + token.Scope.Should().Be("send:region:DE01010"); + } +} diff --git a/E2ETests/SenderTest.cs b/E2ETests/SenderTest.cs index d912585e6239ce4d272bdb141b1c409fe3c27f74..21d5c64cf1bbc98150100b1eb53dce7f52f009c9 100644 --- a/E2ETests/SenderTest.cs +++ b/E2ETests/SenderTest.cs @@ -12,6 +12,10 @@ public class SenderTest { private string _clientSecret = ""; private Sender _sender; + /// <summary> + /// Setup creates json file for credentials if not found + /// </summary> + /// <exception cref="Exception"></exception> [OneTimeSetUp] public void OneTimeSetup() { // relative to the project execution directory @@ -48,20 +52,4 @@ public class SenderTest { _clientId.Should().NotBe("00000000-0000-0000-0000-000000000000"); _clientSecret.Should().NotBe("0000000000000000000000000000000000000000000"); } - - [Test] - public void GetAccessToken() { - var token = _sender.GetTokenAsync(_clientId, _clientSecret).Result; - token.Should().NotBeNull(); - token!.ExpiresIn.Should().Be(1800); - token.Scope.Should().Be("send:region:DE"); - } - - [Test] - public void GetAccessTokenWithScope() { - var token = _sender.GetTokenAsync(_clientId, _clientSecret, "send:region:DE01010").Result; - token.Should().NotBeNull(); - token!.ExpiresIn.Should().Be(1800); - token.Scope.Should().Be("send:region:DE01010"); - } } diff --git a/EncryptionTests/Certificates/certificate.cer b/EncryptionTests/Certificates/certificate.cer new file mode 100644 index 0000000000000000000000000000000000000000..73f0387ee1b1ff8bdc9fe430d24b328c0e860b85 --- /dev/null +++ b/EncryptionTests/Certificates/certificate.cer @@ -0,0 +1,24 @@ +-----BEGIN CERTIFICATE----- +MIIEyjCCArKgAwIBAgIIMIw7HOs7698wDQYJKoZIhvcNAQELBQAwJTEWMBQGA1UEAxMNZml0Y29u +bmVjdC5kZTELMAkGA1UEBhMCREUwHhcNMjIwNjA4MTEwNzQ3WhcNMjcwNjA4MTEwNzUyWjAlMRYw +FAYDVQQDEw1maXRjb25uZWN0LmRlMQswCQYDVQQGEwJERTCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAJxxbIieIEY9Vvql4R6ED5HqtGMjoJZ2xiwF5Zv/DuLseAZEUR7QuBvPG8CLcYOE +JArXcAS5smLfG2LiNerMgDnrLGvBl3qrYOEq0kRIqGkSr5JM2V/bk2UgMToOJGQDBMh6IbU707Xp +u1gHe2wuNxbJE4kALZV8zJX9fE2bc87o3URsw4yv3EnCpR3TskPI2uazNxKWVWjgpZ5yB6PQMbG0 +R7e0qGdEeIo5PpaxdkzbJUN4LwnEdZCKbXt3bMqr7wUJ4lXrqCUhO1Y57iR5YoNBTkKgJHV9fbcy +EHs6IZAhxQCCyuczby5UDS4VzXvnqpxxnvI/rv12nc2WPPctvmA3AyHOH7hTugg7UZ75UKFFHqfU +TeY6lWkifUpTpWVZ3Krm0+3Ja/VZzufPSybifTR9v5JMxQdwaqp9E53Cqh1csqciLVazDOjU6Lhn +fjLr4GwSri1FveXDqpq/jJhkTbuDL1fAEs+eDauD3vNxb0Bverpb5/7rOcAKTH0GEzZhSHV23fyP +DpKmpi5h0py42SdbBAZat8C1StMQ7m3UxuDdoMg+zRS5d12d6s0ZFf2xwFCjDVNVN6y7BkSGuA2l +2/I5ojXhK/Wm+DAcKT/osZznyNq4OlHL2ck6LOSSgFCJFrdkpku++gROx2xjUIFQjryUkcPZ6y0l +PLXAHJUEGO97AgMBAAEwDQYJKoZIhvcNAQELBQADggIBADXJ6LP4HdnUiFCgQR8PoY+fR8XCnYx8 +FdVB9v9xdsBhE1/B1c8z6kUInQdq2NfsRjyfgA1wIdPElP+b1bu/TKS/GpWrsPHUXDRv67qT13cB +rw8I6rHTDdnF00pEU4mabI32EqGnEAwu8DGlKnDbnh/dY7IQjYK40/ZutcqzybzvBiNFoYu7rKXg +7CThPwc92fnPfFcDJJMU9YlA5C6MaLSxDj0e3z1rl+ew9pab8gclnbzgxGOqFMNPhNRHx0SvFrCF +dPzva9mYDATHgR66hJAo/hov8qJnz6/7xkQgFPy4jHwBk2ubM2SN8+UEyklm7u3v8X6mpvdE7/gC +qWJYJjcpzz7QEBmvJ9yYIALNo8E9UyZNfYgE/hEyBpEccNk7z6y/yHNThcojwbfmYjztl5Ed4uHY +i1ycf5LYcSAxtzD6V5CuIVzmPNkSgB1m8Wu/+5Sy2/uQGIKNK/X6E9f1GjTDBv5KzxOoongc4yri +VinYgmrpoF6Uh6G423IGT6/+SpyQa5oegpSbKKDcSAQKK/fCJQbck03WTbpgKDhc0KDLB2C07yiI +RNgSDRO+eg8BTFjxE3uPVwY8AP+QxK2+Xn5ozuT7aedeS41MQYeeatd6StNy809DvIMb44ZUA6JU +/n8Ok8eHWVY+aShfomcLyH7+EeL9e2LTSp0geUdmsnNI +-----END CERTIFICATE----- \ No newline at end of file diff --git a/EncryptionTests/Certificates/certificate.pfx b/EncryptionTests/Certificates/certificate.pfx new file mode 100644 index 0000000000000000000000000000000000000000..ec4092f2c68e0c8865fe9333be770dd09fb5c6f9 Binary files /dev/null and b/EncryptionTests/Certificates/certificate.pfx differ diff --git a/SenderTest/SenderTest.csproj b/EncryptionTests/EncryptionTests.csproj similarity index 66% rename from SenderTest/SenderTest.csproj rename to EncryptionTests/EncryptionTests.csproj index def8c00370d4ec3573756904bfa33de12b0ef921..edababf841e8930e50e54f1d0a183f4ce4299bb5 100644 --- a/SenderTest/SenderTest.csproj +++ b/EncryptionTests/EncryptionTests.csproj @@ -5,20 +5,26 @@ <Nullable>enable</Nullable> <IsPackable>false</IsPackable> + + <RootNamespace>SenderTest</RootNamespace> </PropertyGroup> <ItemGroup> - <PackageReference Include="FluentAssertions" Version="6.7.0" /> - <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" /> - <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> - <PackageReference Include="NUnit" Version="3.13.2" /> - <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="FluentAssertions" Version="6.7.0"/> + <PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0"/> + <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0"/> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0"/> + <PackageReference Include="NUnit" Version="3.13.2"/> + <PackageReference Include="NUnit3TestAdapter" Version="4.0.0"/> + <PackageReference Include="coverlet.collector" Version="3.1.0"/> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\FitConnect\FitConnect.csproj"/> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\FitConnect\FitConnect.csproj" /> + <Folder Include="Certificates"/> </ItemGroup> </Project> diff --git a/EncryptionTests/JweTest.cs b/EncryptionTests/JweTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..fbde038538a0600c3f80dbe58dd7941c5ec7c57f --- /dev/null +++ b/EncryptionTests/JweTest.cs @@ -0,0 +1,28 @@ +using FitConnect; +using Microsoft.Extensions.Logging; +using NUnit.Framework; + +namespace SenderTest; + +public class JweTest { + private ILogger<JweTest> _logger; + private Sender _sender; + + + [SetUp] + public void SetUp() { + _logger = LoggerFactory.Create(cfg => cfg.AddConsole()) + .CreateLogger<JweTest>(); + + _sender = new Sender(_logger, + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); + } + + [Test] + public void TestJwe() { + var jwe = _sender.Encryption.GetTestToken(); + Assert.IsNotNull(jwe); + + _logger.LogInformation(jwe); + } +} diff --git a/EncryptionTests/SenderEncryptionWithImportedCertificateTest.cs b/EncryptionTests/SenderEncryptionWithImportedCertificateTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..9b63f0e53381eb68f66a60ba7c42235c01ebb0d0 --- /dev/null +++ b/EncryptionTests/SenderEncryptionWithImportedCertificateTest.cs @@ -0,0 +1,43 @@ +using System.Security.Cryptography.X509Certificates; +using FitConnect; +using Microsoft.Extensions.Logging; +using NUnit.Framework; + +namespace SenderTest; + +public class SenderEncryptionWithImportedCertificateTest { + private ILogger _logger = null!; + private Sender _sender = null!; + + [OneTimeSetUp] + public void OneTimeSetUp() { + // Import certificate + } + + [SetUp] + public void SetUp() { + // Create a new Sender + var fileName = "../../../Certificates/certificate.cer"; + var cert = new X509Certificate2(fileName); + _logger = LoggerFactory.Create(cfg => cfg.AddConsole()) + .CreateLogger<SenderEncryptionWithSelfSignedCertificateTest>(); + + _sender = new Sender(_logger, + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development), cert); + } + + [TearDown] + public void TearDown() { + // Dispose the Sender + } + + [OneTimeTearDown] + public void OneTimeTearDown() { + // Delete certificate + } + + [Test] + public void TestEncryption() { + // Encrypt a message + } +} diff --git a/EncryptionTests/SenderEncryptionWithSelfSignedCertificateTest.cs b/EncryptionTests/SenderEncryptionWithSelfSignedCertificateTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..abe9bd3ed4c81e058365827e6eaacc5720074c85 --- /dev/null +++ b/EncryptionTests/SenderEncryptionWithSelfSignedCertificateTest.cs @@ -0,0 +1,73 @@ +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using FitConnect; +using FitConnect.Security; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using NUnit.Framework; + +namespace SenderTest; + +public class SenderEncryptionWithSelfSignedCertificateTest { + private const string ToEncrypt = "This is a test message"; + private static byte[]? cypher; + private X509Certificate2 _certificate = null!; + private ILogger<SenderEncryptionWithSelfSignedCertificateTest> _logger = null!; + private Sender _sender = null!; + + + /* + * Encryption test must be changed for production to only allow extern signed certificates + * and forbid self-signed certificates. + */ + + [OneTimeSetUp] + public void OneTimeSetup() { + _certificate = RsaEncryption.CreateSelfSignedCertificate("./"); + } + + [OneTimeTearDown] + public void OneTimeTearDown() { + _certificate.Dispose(); + // File.Copy("./certificate.pfx", "../../../Certificates/certificate.pfx", true); + // File.Copy("./certificate.cer", "../../../Certificates/certificate.cer", true); + File.Delete("./certificate.pfx"); + File.Delete("./certificate.cer"); + } + + [SetUp] + public void Setup() { + _logger = LoggerFactory.Create(cfg => cfg.AddConsole()) + .CreateLogger<SenderEncryptionWithSelfSignedCertificateTest>(); + _sender = new Sender(_logger, + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); + + var certificate = new X509Certificate2("./certificate.pfx"); + _sender.Encryption.ImportCertificate(certificate); + } + + + [Test] + [Order(10)] + public void CryptWithOutPublicKeyImport() { + cypher = _sender.Encryption.EncryptData(Encoding.UTF8.GetBytes(ToEncrypt)); + + _logger.LogInformation("Cypher: {}", Convert.ToBase64String(cypher)); + } + + [Test] + [Order(20)] + public void Decrypt_ResultShouldMatchToEncrypt() { + var result = _sender.Encryption.DecryptDataAsync(cypher!); + Encoding.UTF8.GetString(result).Should().Be(ToEncrypt); + } + + + [Test] + public void ExportPrivateKey() { + var privateKey = _sender.Encryption.ExportPrivateKey(); + _logger.LogInformation("Private key: {}", Convert.ToBase64String(privateKey)); + } +} diff --git a/EncryptionTests/SenderEncryptionWithoutCertificateTest.cs b/EncryptionTests/SenderEncryptionWithoutCertificateTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..61207eb94cb5ec94aa791f358c8fe5c01e0cb5c8 --- /dev/null +++ b/EncryptionTests/SenderEncryptionWithoutCertificateTest.cs @@ -0,0 +1,54 @@ +using System; +using System.Text; +using FitConnect; +using FluentAssertions; +using Microsoft.Extensions.Logging; +using NUnit.Framework; + +namespace SenderTest; + +public class SenderEncryptionWithoutCertificateTest { + private const string ToEncrypt = "Hello World"; + private string _cypherText = null!; + private ILogger<SenderEncryptionWithoutCertificateTest> _logger = null!; + private Sender _sender = null!; + + /* + * Encryption test must be changed for production to only allow extern signed certificates + * and forbid self-signed certificates. + */ + + [OneTimeSetUp] + public void OneTimeSetUp() { + _logger = LoggerFactory.Create(cfg => cfg.AddConsole()) + .CreateLogger<SenderEncryptionWithoutCertificateTest>(); + _sender = new Sender(_logger, + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); + } + + + [Test] + [Order(10)] + public void EncryptData_ShouldNotThrowAnyException() { + var cypher = _sender.Encryption.EncryptData(Encoding.UTF8.GetBytes(ToEncrypt)); + + _cypherText = Convert.ToBase64String(cypher); + _logger.LogInformation("Cypher: {}", _cypherText); + } + + + [Test] + [Order(20)] + public void DecryptData_ShouldMatchToEncrypt() { + var cypher = Convert.FromBase64String(_cypherText); + var plain = _sender.Encryption.DecryptDataAsync(cypher); + + Encoding.UTF8.GetString(plain).Should().Be(ToEncrypt); + } + + [Test] + public void ExportPrivateKey_ShouldNotThrowAnyException() { + var privateKey = _sender.Encryption.ExportPrivateKey(); + _logger.LogInformation("Private key: {}", Convert.ToBase64String(privateKey)); + } +} diff --git a/EncryptionTests/SubscriberEncryptionWithImportedCertificateTest.cs b/EncryptionTests/SubscriberEncryptionWithImportedCertificateTest.cs new file mode 100644 index 0000000000000000000000000000000000000000..9adf848c95063f1905b41000a957d2bf2c603199 --- /dev/null +++ b/EncryptionTests/SubscriberEncryptionWithImportedCertificateTest.cs @@ -0,0 +1,43 @@ +using System.Security.Cryptography.X509Certificates; +using FitConnect; +using Microsoft.Extensions.Logging; +using NUnit.Framework; + +namespace SenderTest; + +public class SubscriberEncryptionWithImportedCertificateTest { + private ILogger _logger = null!; + private Subscriber _subscriber = null!; + + [OneTimeSetUp] + public void OneTimeSetUp() { + // Import certificate + } + + [SetUp] + public void SetUp() { + // Create a new Sender + var fileName = "../../../Certificates/certificate.pfx"; + var cert = new X509Certificate2(fileName); + _logger = LoggerFactory.Create(cfg => cfg.AddConsole()) + .CreateLogger<SenderEncryptionWithSelfSignedCertificateTest>(); + + _subscriber = new Subscriber(_logger, + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development), cert); + } + + [TearDown] + public void TearDown() { + // Dispose the Sender + } + + [OneTimeTearDown] + public void OneTimeTearDown() { + // Delete certificate + } + + [Test] + public void TestEncryption() { + // Encrypt a message + } +} diff --git a/FitConnect.sln b/FitConnect.sln index 8c1a251e18c5c52a00665405ae50d9e84d1cb20d..1b039a682ba57d7971d14a95f46c0281516890c6 100644 --- a/FitConnect.sln +++ b/FitConnect.sln @@ -8,7 +8,13 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InitializationTests", "Init EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E2ETests", "E2ETests\E2ETests.csproj", "{27115A99-2AE8-42BC-9495-BE2DCEDDF1E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SenderTest", "SenderTest\SenderTest.csproj", "{68F1EC39-B234-4422-9AA8-E55CFA15025D}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EncryptionTests", "EncryptionTests\EncryptionTests.csproj", "{68F1EC39-B234-4422-9AA8-E55CFA15025D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services", "Services\Services.csproj", "{1BB06AF4-492C-4041-A947-FE65F7A48698}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestService", "RestService\RestService.csproj", "{51743B56-6147-43FF-8513-EE38A3E7B188}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Models", "Models\Models.csproj", "{98C78243-6ABD-492D-ACA8-23B3BF17FBDA}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -32,6 +38,18 @@ Global {68F1EC39-B234-4422-9AA8-E55CFA15025D}.Debug|Any CPU.Build.0 = Debug|Any CPU {68F1EC39-B234-4422-9AA8-E55CFA15025D}.Release|Any CPU.ActiveCfg = Release|Any CPU {68F1EC39-B234-4422-9AA8-E55CFA15025D}.Release|Any CPU.Build.0 = Release|Any CPU + {1BB06AF4-492C-4041-A947-FE65F7A48698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1BB06AF4-492C-4041-A947-FE65F7A48698}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1BB06AF4-492C-4041-A947-FE65F7A48698}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1BB06AF4-492C-4041-A947-FE65F7A48698}.Release|Any CPU.Build.0 = Release|Any CPU + {51743B56-6147-43FF-8513-EE38A3E7B188}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51743B56-6147-43FF-8513-EE38A3E7B188}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51743B56-6147-43FF-8513-EE38A3E7B188}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51743B56-6147-43FF-8513-EE38A3E7B188}.Release|Any CPU.Build.0 = Release|Any CPU + {98C78243-6ABD-492D-ACA8-23B3BF17FBDA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98C78243-6ABD-492D-ACA8-23B3BF17FBDA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98C78243-6ABD-492D-ACA8-23B3BF17FBDA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98C78243-6ABD-492D-ACA8-23B3BF17FBDA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {73CE5625-4C13-458E-B524-0DAA850F4041} = {D382641C-B027-411A-814C-C8C20A9505F3} diff --git a/FitConnect/BaseClasses/FunctionalBaseClass.cs b/FitConnect/BaseClasses/FunctionalBaseClass.cs new file mode 100644 index 0000000000000000000000000000000000000000..18ab99ad865e147cd64488553a13491f6c5feab7 --- /dev/null +++ b/FitConnect/BaseClasses/FunctionalBaseClass.cs @@ -0,0 +1,44 @@ +using System.Security.Cryptography.X509Certificates; +using FitConnect.Models; +using FitConnect.RestService; +using FitConnect.Security; +using FitConnect.Services; +using Microsoft.Extensions.Logging; + +namespace FitConnect.BaseClasses; + +public abstract class FunctionalBaseClass { + public readonly IEncryption Encryption; + protected readonly ILogger? Logger; + protected readonly FitConnectApiService ApiService; + + /// <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> + /// <example> + /// new Sender(logger, FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)) + /// </example> + protected FunctionalBaseClass(ILogger? logger, FitConnectEndpoints? endpoints, + X509Certificate2? certificate) { + Endpoints = endpoints ?? + FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development); + + Logger = logger; + Encryption = new RsaEncryption(logger, certificate); + ApiService = new FitConnectApiService(Endpoints, logger); + } + + public FitConnectEndpoints Endpoints { get; } + + + /// <summary> + /// Receive the SET data + /// </summary> + /// <returns></returns> + public Task<SecurityEventToken> GetSetDataAsync() { + throw new NotImplementedException(); + } +} diff --git a/FitConnect/Client.cs b/FitConnect/Client.cs new file mode 100644 index 0000000000000000000000000000000000000000..0008dc93dde33da376dc4c6fd36da4f7eecb6081 --- /dev/null +++ b/FitConnect/Client.cs @@ -0,0 +1,117 @@ +using System.Security.Cryptography.X509Certificates; +using FitConnect.Models; +using FitConnect.Services; +using FitConnect.Services.Models; +using Microsoft.Extensions.Logging; + +namespace FitConnect; + +/// <summary> +/// The FitConnect API Client +/// </summary> +// ReSharper disable once UnusedType.Global +public class Client { + private readonly string _clientId; + private readonly string _clientSecret; + private readonly Sender _sender; + private readonly Subscriber _subscriber; + + + + public Client(FitConnectEndpoints endpoints, + string clientId, string clientSecret, + X509Certificate2? certificate = null, + ILogger? logger = null) { + _clientId = clientId; + _clientSecret = clientSecret; + _sender = new Sender(logger, endpoints, certificate); + _subscriber = new Subscriber(logger, endpoints, certificate); + //_service = new FitConnectApiService(endpoints, logger); + } + + + /// <summary> + /// Get destination for submission. + /// <para>Leika-Key and one of the area specifier has to be set.</para> + /// </summary> + /// <param name="leiaKey">Mandatory</param> + /// <param name="ags"></param> + /// <param name="ars"></param> + /// <param name="areaId"></param> + /// <returns></returns> + public async Task<string> GetDestinationIdAsync(string leiaKey, string? ags, string? ars, + string? areaId) { + if (string.IsNullOrEmpty(leiaKey)) { + throw new ArgumentException("leiaKey is mandatory"); + } + + if (string.IsNullOrEmpty(ags) && + string.IsNullOrEmpty(ars) && + string.IsNullOrEmpty(areaId)) { + throw new ArgumentException("One of the area specifier has to be set"); + } + + return await _sender.GetDestinationIdAsync(leiaKey, ags, ars, areaId); + } + + /// <summary> + /// Sending a submission to the FitConnect API<br/> + /// <para>The submission is prepared for sending to the FitConnect API.</para> + /// </summary> + /// <param name="submission">The submission to send</param> + /// <returns>success</returns> + // ReSharper disable once MemberCanBePrivate.Global + public async Task<bool> SendSubmissionAsync(Submission submission) { + // Einreichung anlegen und ankündigen + var createSubmissionDto = _sender.CreateSubmissionDto(submission); + + // Anlagen verschlüsseln + var encryptedAttachments = submission.Attachments + .Select(a => _sender.EncryptAttachment(a)).ToList(); + + // Anlagen hochladen + await _sender.UploadAttachmentsAsync(encryptedAttachments); + + // Metadaten befüllen und verschlüsseln + var metaData = await _sender.CreateMetadataAsync("", new byte[] { }); + + // Fachdaten verschlüsseln + var encryptedData = _sender.EncryptDataAsync(submission.Data); + + // Fachdaten & Metadaten hochladen und Einreichung absenden + await _sender.SubmitSubmissionAsync(submission); + + return true; + } + + /// <summary> + /// Gets all submissions from the FitConnect API + /// </summary> + /// <returns>List of the available submissions</returns> + // ReSharper disable once MemberCanBePrivate.Global + public async Task<List<Submission>> GetSubmissionsAsync(string destinationId, int offset = 0, + int limit = 100) { + // var pickUpList = + // await _service.SubmissionService.ListSubmissions(destinationId, offset, limit); + // return pickUpList.Submissions.Select(dto => (Submission)dto).ToList(); + throw new NotImplementedException(); + } + + /// <summary> + /// Sending a submission to the FitConnect API + /// </summary> + /// <param name="submission">The submission to send</param> + /// <returns>success</returns> + public bool SendSubmission(Submission submission) { + return SendSubmissionAsync(submission).Result; + } + + /// <summary> + /// Gets all submissions from the FitConnect API + /// </summary> + /// <returns>List of the available submissions</returns> + public List<Submission> GetSubmissions(string destinationId, int offset = 0, + int limit = 100) { + return GetSubmissionsAsync(destinationId, offset, limit).Result; + } +} diff --git a/FitConnect/EncryptionBaseClass.cs b/FitConnect/EncryptionBaseClass.cs deleted file mode 100644 index 60581a49e6f26ffb84ec973fd94ac35412c52de8..0000000000000000000000000000000000000000 --- a/FitConnect/EncryptionBaseClass.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using Microsoft.Extensions.Logging; - -namespace FitConnect; - -public class EncryptionBaseClass { - private readonly ILogger? _logger; - private readonly RSA _rsa; - private RSA? _publicKey; - private RSA? _privateKey; - - protected EncryptionBaseClass(ILogger? logger) { - _logger = logger; - _rsa = RSA.Create(2048); - } - - - /// <summary> - /// - /// </summary> - /// <param name="cert"></param> - /// <returns></returns> - private bool CheckCertificate(X509Certificate2 cert) => - cert.HasPrivateKey; // && cert.Verify(); - - - public void ImportCertificate(X509Certificate2 cert) { - if (!CheckCertificate(cert)) { - throw new Exception("Invalid certificate"); - } - - _publicKey = cert.GetRSAPublicKey(); - // _publicKey = RSA.Create(2048); - // _publicKey.ImportRSAPublicKey(cert.GetPublicKey(), out int _); - - if ((_publicKey?.KeySize ?? 0) == 0) - throw new Exception("Invalid certificate, no public key"); - - _logger.LogInformation("Public key imported {}", - Convert.ToBase64String(_publicKey.ExportRSAPrivateKey())); - - if (cert.HasPrivateKey) { - _privateKey = cert.GetRSAPrivateKey(); - } - - if (_privateKey != null) - _logger.LogInformation("Certificate with private key imported"); - else - _logger.LogInformation("Certificate has no private key"); - } - - /// <summary> - /// Import a public key from a PEM file - /// </summary> - /// <param name="certificatePath"></param> - /// <param name="password">Password for the certificate</param> - /// <exception cref="ArgumentException"></exception> - /// <exception cref="Exception"></exception> - public void ImportCertificate(string certificatePath, string password) { - _logger.LogInformation("Importing certificate {CertPath}", certificatePath); - var cert = - new X509Certificate2(certificatePath, password, X509KeyStorageFlags.MachineKeySet); - - if (!CheckCertificate(cert)) { - throw new ArgumentException("Certificate is not valid"); - } - - var parameters = cert.PublicKey.GetRSAPublicKey()?.ExportParameters(true); - if (parameters == null) { - throw new Exception("Could not get public key from certificate"); - } - - _rsa.ImportParameters(parameters.Value); - } - - public byte[] DecryptDataAsync(byte[] data) { - return (_privateKey ?? _rsa).Decrypt(data, RSAEncryptionPadding.OaepSHA256); - } - - public byte[] ExportPublicKey() { - return _rsa.ExportRSAPublicKey(); - } - - public byte[] ExportPrivateKey() { - return _rsa.ExportRSAPrivateKey(); - } - - public byte[] EncryptData(byte[] data) { - return (_publicKey ?? _rsa).Encrypt(data, RSAEncryptionPadding.OaepSHA256); - } - - public byte[] EncryptData(byte[] data, byte[] publicKey) { - _logger?.LogInformation( - "Encrypting data with public key: {}", - Convert.ToBase64String(_rsa.ExportRSAPublicKey())); - - _rsa.ImportRSAPublicKey(publicKey, out var read); - return _rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256); - } -} diff --git a/FitConnect/FitConnect.csproj b/FitConnect/FitConnect.csproj index 69367e6234325f21201583ed8e29f60558966ca8..30c89efb000c08f73948a96e07ca5647cf6c4103 100644 --- a/FitConnect/FitConnect.csproj +++ b/FitConnect/FitConnect.csproj @@ -11,18 +11,24 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> - <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> - <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" /> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> + <PackageReference Include="Newtonsoft.Json" Version="13.0.1" /> + <PackageReference Include="Newtonsoft.Json.Schema" Version="3.0.14" /> + <PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.19.0" /> </ItemGroup> <ItemGroup> - <None Remove="metadata.schema.json" /> - <EmbeddedResource Include="metadata.schema.json" /> + <None Remove="metadata.schema.json" /> + <EmbeddedResource Include="metadata.schema.json" /> </ItemGroup> <ItemGroup> - <Folder Include="Interfaces" /> + <Folder Include="Interfaces" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Models\Models.csproj" /> + <ProjectReference Include="..\Services\Services.csproj" /> </ItemGroup> </Project> diff --git a/FitConnect/FunctionalBaseClass.cs b/FitConnect/FunctionalBaseClass.cs deleted file mode 100644 index 844b2fe928b0703b19f3eeb3e45b2b0896667352..0000000000000000000000000000000000000000 --- a/FitConnect/FunctionalBaseClass.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System.Net.Http.Headers; -using System.Net.Http.Json; -using System.Text; -using FitConnect.Models; -using Microsoft.Extensions.Logging; - -namespace FitConnect; - -public class FunctionalBaseClass : EncryptionBaseClass { - protected readonly ILogger? Logger; - public FitConnectEndpoints Endpoints { get; } - - /// <summary> - /// Constructor for the FunctionalBaseClass - /// </summary> - /// <param name="logger">ILogger implementation</param> - /// <param name="endpoints">FitConnect endpoints</param> - protected FunctionalBaseClass(ILogger? logger, FitConnectEndpoints? endpoints) : base(logger) { - Endpoints = endpoints ?? - FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development); - - Logger = logger; - } - - - /// <summary> - /// Requesting an OAuth token from the FitConnect API. - /// - /// You can get the Client ID and Client Secret from the FitConnect Self Service portal - /// under https://portal.auth-testing.fit-connect.fitko.dev - /// </summary> - /// <param name="clientId">Your client Id</param> - /// <param name="clientSecret">Your client Secret</param> - /// <param name="scope">Scope if needed</param> - /// <returns></returns> - public async Task<OAuthAccessToken?> GetTokenAsync(string clientId, string clientSecret, - string? scope = null) { - var client = new HttpClient(); - client.DefaultRequestHeaders.Accept.Add( - MediaTypeWithQualityHeaderValue.Parse("application/json")); - - var requestContent = new Dictionary<string, string> { - { "grant_type", "client_credentials" }, - { "client_id", clientId }, - { "client_secret", clientSecret } - }; - - if (scope != null) - requestContent["scope"] = scope; - - var content = new FormUrlEncodedContent(requestContent); - - var request = new HttpRequestMessage(HttpMethod.Post, Endpoints.TokenUrl) { - Content = content, - Method = HttpMethod.Post - }; - - var response = await client.SendAsync(request); - - return await response.Content.ReadFromJsonAsync<OAuthAccessToken>(); - } - - /// <summary> - /// Receive the SET data - /// </summary> - /// <returns></returns> - public Task<SecurityEventToken> GetSetDataAsync() { - throw new NotImplementedException(); - } - - - private async Task<T?> RestCall<T>(Uri uri, HttpMethod method, string body) { - var client = new HttpClient(); - client.DefaultRequestHeaders.Add("Accept", "application/json"); - client.DefaultRequestHeaders.Add("Content-Type", "application/json"); - - var request = new HttpRequestMessage(); - request.Method = method; - request.Content = new StringContent(body, Encoding.UTF8, "application/json"); - request.RequestUri = uri; - - var response = await client.SendAsync(request); - - if (response.IsSuccessStatusCode) - return (await response.Content.ReadFromJsonAsync<T>()); - - throw new HttpRequestException("Error calling FitConnect API"); - } -} diff --git a/FitConnect/HttpCalls/RoutingApi.http b/FitConnect/HttpCalls/RoutingApi.http new file mode 100644 index 0000000000000000000000000000000000000000..b3cd06e1080086ca3cb22ba98b0a71d043f9bad5 --- /dev/null +++ b/FitConnect/HttpCalls/RoutingApi.http @@ -0,0 +1,9 @@ +### Get areas +GET {{routing_api_url}}/v1/areas?areaSearchexpression=93437 +Accept: application/json + +### Get areas +GET {{routing_api_url}}/v1/routes?leikaKey=99012070006000 +Accept: application/json + + diff --git a/FitConnect/HttpCalls/http-client.env.json b/FitConnect/HttpCalls/http-client.env.json new file mode 100644 index 0000000000000000000000000000000000000000..c4605fc77dc203f3dd2f89fcdd13111abca531c5 --- /dev/null +++ b/FitConnect/HttpCalls/http-client.env.json @@ -0,0 +1,5 @@ +{ + "dev": { + "routing_api_url": "https://routing-api-testing.fit-connect.fitko.dev" + } +} \ No newline at end of file diff --git a/FitConnect/Models/Area.cs b/FitConnect/Models/Area.cs new file mode 100644 index 0000000000000000000000000000000000000000..739981a83da91efcd0429b419770553eeba78829 --- /dev/null +++ b/FitConnect/Models/Area.cs @@ -0,0 +1,5 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Models; + +public record Area(string Id, string Name, string Type); \ No newline at end of file diff --git a/FitConnect/Models/Attachment.cs b/FitConnect/Models/Attachment.cs new file mode 100644 index 0000000000000000000000000000000000000000..d432c10594ec0b1d0494defa118bbf19064d4d16 --- /dev/null +++ b/FitConnect/Models/Attachment.cs @@ -0,0 +1,3 @@ +namespace FitConnect.Models; + +public record Attachment(string Id, byte[] Content, string Hash); diff --git a/FitConnect/Models/Callback.cs b/FitConnect/Models/Callback.cs index bcf95a72803962d11e1e16c8014b65025e279974..23eaf2c1f13bebcbff6d367fd45b88b395990f10 100644 --- a/FitConnect/Models/Callback.cs +++ b/FitConnect/Models/Callback.cs @@ -1,11 +1,10 @@ using System.Text.Json.Serialization; +using FitConnect.Services.Models; namespace FitConnect.Models; -public class Callback { - [JsonPropertyName("url")] - public string? Url { get; set; } +public record Callback(string? Url, string? Secret) { + public static explicit operator Callback(CallbackDto dto) => new(dto.Url, null); - [JsonPropertyName("secret")] - public string? Secret { get; set; } + public static explicit operator CallbackDto(Callback model) => new() { Url = model.Url }; } diff --git a/FitConnect/Models/Route.cs b/FitConnect/Models/Route.cs new file mode 100644 index 0000000000000000000000000000000000000000..982d1a84388493a86165742c1127cacdab8e38e7 --- /dev/null +++ b/FitConnect/Models/Route.cs @@ -0,0 +1,3 @@ +namespace FitConnect.Models; + +public record Route(); diff --git a/FitConnect/Models/SecurityEventToken.cs b/FitConnect/Models/SecurityEventToken.cs index 3ba016f8bcb1f585a562d2942da54048e165b213..33b2bd5e898c7b418f3480845a97f577172397b9 100644 --- a/FitConnect/Models/SecurityEventToken.cs +++ b/FitConnect/Models/SecurityEventToken.cs @@ -1,5 +1,3 @@ namespace FitConnect.Models; -public class SecurityEventToken { -} - +public record SecurityEventToken(); diff --git a/FitConnect/Models/ServiceType.cs b/FitConnect/Models/ServiceType.cs index 72d2ab5baf5568612500b4397c7b7b4111510c2b..2891064d091dab3785354e259100dd361d6bd438 100644 --- a/FitConnect/Models/ServiceType.cs +++ b/FitConnect/Models/ServiceType.cs @@ -1,11 +1,22 @@ using System.Text.Json.Serialization; +using FitConnect.Services.Models; namespace FitConnect.Models; -public class ServiceType { - [JsonPropertyName("name")] +public record ServiceType { public string? Name { get; set; } - [JsonPropertyName("identifier")] + public string? Description { get; set; } public string? Identifier { get; set; } + + public static explicit operator ServiceType(ServiceTypeDto dto) => new() { + Description = dto.Description, + Identifier = dto.Identifier, + Name = dto.Name + }; + public static explicit operator ServiceTypeDto(ServiceType model) => new() { + Description = model.Description, + Identifier = model.Identifier, + Name = model.Name + }; } diff --git a/FitConnect/Models/Submission.cs b/FitConnect/Models/Submission.cs index 11db33fd8ca805919c484539a67a59254640fbfe..9a8f31d174459092ccb49c7d23674f6f7f6a0e96 100644 --- a/FitConnect/Models/Submission.cs +++ b/FitConnect/Models/Submission.cs @@ -1,17 +1,44 @@ using System.Text.Json.Serialization; +using System.Xml; +using FitConnect.Services.Models; namespace FitConnect.Models; -public class Submission { - [JsonPropertyName("destinationId")] - public string? DestinationId { get; set; } +public record Submission { + public string? Id { get; set; } + public string DestinationId { get; init; } + public string? CaseId { get; set; } - [JsonPropertyName("announcedAttachments")] - public List<string>? AnnouncedAttachments { get; set; } + public List<Attachment> Attachments { get; set; } = new(); - [JsonPropertyName("serviceType")] - public ServiceType? ServiceType { get; set; } + public ServiceType ServiceType { get; init; } - [JsonPropertyName("callback")] public Callback? Callback { get; set; } + + + /// <summary> + /// Fachdaten + /// </summary> + public string? Data { get; set; } + + public static explicit operator Submission(SubmissionForPickupDto dto) => new() { + Id = dto.SubmissionId, + Callback = null, + DestinationId = dto.DestinationId, + ServiceType = null, + }; + + public static explicit operator Submission(SubmissionDto dto) => new() { + Id = dto.SubmissionId, + Callback = (Callback)dto.Callback, + DestinationId = dto.DestinationId, + ServiceType = (ServiceType)dto.ServiceType, + }; + + public static explicit operator CreateSubmissionDto(Submission sub) => new() { + AnnouncedAttachments = sub.Attachments.Select(a => a.Id).ToList(), + DestinationId = sub.DestinationId, + ServiceType = (ServiceTypeDto)sub.ServiceType, + Callback = (CallbackDto)sub.Callback, + }; } diff --git a/FitConnect/Routing.cs b/FitConnect/Routing.cs new file mode 100644 index 0000000000000000000000000000000000000000..63720a3af7129271799bb5653e41d828036f42b2 --- /dev/null +++ b/FitConnect/Routing.cs @@ -0,0 +1,73 @@ +using System.Security.Cryptography.X509Certificates; +using FitConnect.Models; +using Microsoft.Extensions.Logging; + +namespace FitConnect; + +/// <summary> +/// Client to the FitConnect Routing Api +/// </summary> +public class Routing { + private readonly X509Certificate2? _certificate; + private readonly FitConnectEndpoints _endpoints; + private readonly ILogger? _logger; + + public Routing(ILogger? logger, FitConnectEndpoints endpoints, + X509Certificate2? certificate = null) { + _logger = logger; + _endpoints = endpoints; + _certificate = certificate; + } + + + /// <summary> + /// </summary> + /// <param name="filter"></param> + /// <param name="offset"></param> + /// <param name="limit"></param> + /// <returns></returns> + public List<Area> GetAreas(string filter, int offset = 0, int limit = 100) { + return GetAreasAsync(filter, offset, limit).Result; + } + + + /// <summary> + /// </summary> + /// <param name="filter"></param> + /// <param name="offset"></param> + /// <param name="limit"></param> + /// <returns></returns> + public async Task<List<Area>> GetAreasAsync(string filter, int offset = 0, int limit = 100) { + return new List<Area>(); + } + + + /// <summary> + /// </summary> + /// <param name="leikaKey"></param> + /// <param name="ags"></param> + /// <param name="ars"></param> + /// <param name="areaId"></param> + /// <param name="offset"></param> + /// <param name="limit"></param> + /// <returns></returns> + public async Task<List<Route>> GetRoutesAsync(string leikaKey, string ags, string ars, + string areaId, int offset = 0, int limit = 100) { + return new List<Route>(); + } + + + /// <summary> + /// </summary> + /// <param name="leikaKey"></param> + /// <param name="ags"></param> + /// <param name="ars"></param> + /// <param name="areaId"></param> + /// <param name="offset"></param> + /// <param name="limit"></param> + /// <returns></returns> + public List<Route> GetRoutes(string leikaKey, string ags, string ars, + string areaId, int offset = 0, int limit = 100) { + return GetRoutesAsync(leikaKey, ags, ars, areaId, offset, limit).Result; + } +} diff --git a/FitConnect/Security/IEncryption.cs b/FitConnect/Security/IEncryption.cs new file mode 100644 index 0000000000000000000000000000000000000000..048d612c5b138da9a79fc263a714422b3b3f9cd7 --- /dev/null +++ b/FitConnect/Security/IEncryption.cs @@ -0,0 +1,28 @@ +using System.Security.Cryptography.X509Certificates; + +namespace FitConnect.Security; + +public interface IEncryption { + /// <summary> + /// Just for Proof of Concept + /// </summary> + /// <returns></returns> + string GetTestToken(); + + void ImportCertificate(X509Certificate2 cert); + + /// <summary> + /// Import a public key from a PEM file + /// </summary> + /// <param name="certificatePath"></param> + /// <param name="password">Password for the certificate</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="Exception"></exception> + void ImportCertificate(string certificatePath, string password); + + byte[] DecryptDataAsync(byte[] data); + byte[] ExportPublicKey(); + byte[] ExportPrivateKey(); + byte[] EncryptData(byte[] data); + byte[] EncryptData(byte[] data, byte[] publicKey); +} diff --git a/FitConnect/Security/RsaEncryption.cs b/FitConnect/Security/RsaEncryption.cs new file mode 100644 index 0000000000000000000000000000000000000000..860adf39c2040fce9b97241203312b7b6e671e8e --- /dev/null +++ b/FitConnect/Security/RsaEncryption.cs @@ -0,0 +1,160 @@ +using System.IdentityModel.Tokens.Jwt; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Security.Cryptography.X509Certificates; +using Microsoft.Extensions.Logging; +using Microsoft.IdentityModel.Tokens; + +namespace FitConnect.Security; + +public class RsaEncryption : IEncryption { + private readonly X509Certificate2? _certificate; + private readonly ILogger? _logger; + private readonly RSA _rsa; + private RSA? _privateKey; + private RSA? _publicKey; + + + internal RsaEncryption(ILogger? logger, X509Certificate2? certificate) { + _logger = logger; + _rsa = RSA.Create(4096); + + if (certificate != null) { + _certificate = certificate; + ImportCertificate(certificate); + } + } + + + /// <summary> + /// Just for Proof of Concept + /// </summary> + /// <returns></returns> + public string GetTestToken() { + var handler = new JwtSecurityTokenHandler(); + var token = new SecurityTokenDescriptor { + Issuer = "FitConnect", + Audience = "FitConnect", + EncryptingCredentials = + new X509EncryptingCredentials(_certificate ?? + new X509Certificate2(CreateSelfSignedCertificate())), + Subject = new ClaimsIdentity(new Claim[] { + new("Content", "Unencrypted content") + }) + }; + return handler.CreateEncodedJwt(token); + } + + + public void ImportCertificate(X509Certificate2 cert) { + if (!CheckCertificate(cert)) throw new Exception("Invalid certificate"); + + _publicKey = cert.GetRSAPublicKey(); + + if ((_publicKey?.KeySize ?? 0) == 0) + throw new Exception("Invalid certificate, no public key"); + + if (cert.HasPrivateKey) _privateKey = cert.GetRSAPrivateKey(); + + if (_privateKey != null) + _logger?.LogInformation("Certificate with private key imported"); + else + _logger?.LogInformation("Certificate has no private key"); + } + + /// <summary> + /// Import a public key from a PEM file + /// </summary> + /// <param name="certificatePath"></param> + /// <param name="password">Password for the certificate</param> + /// <exception cref="ArgumentException"></exception> + /// <exception cref="Exception"></exception> + public void ImportCertificate(string certificatePath, string password) { + _logger?.LogInformation("Importing certificate {CertPath}", certificatePath); + var cert = + new X509Certificate2(certificatePath, password, X509KeyStorageFlags.MachineKeySet); + + if (!CheckCertificate(cert)) throw new ArgumentException("Certificate is not valid"); + + var parameters = cert.PublicKey.GetRSAPublicKey()?.ExportParameters(true); + if (parameters == null) throw new Exception("Could not get public key from certificate"); + + _rsa.ImportParameters(parameters.Value); + } + + public byte[] DecryptDataAsync(byte[] data) { + return (_privateKey ?? _rsa).Decrypt(data, RSAEncryptionPadding.OaepSHA256); + } + + public byte[] ExportPublicKey() { + return _rsa.ExportRSAPublicKey(); + } + + public byte[] ExportPrivateKey() { + return _rsa.ExportRSAPrivateKey(); + } + + public byte[] EncryptData(byte[] data) { + return (_publicKey ?? _rsa).Encrypt(data, RSAEncryptionPadding.OaepSHA256); + } + + public byte[] EncryptData(byte[] data, byte[] publicKey) { + _logger?.LogInformation( + "Encrypting data with public key: {}", + Convert.ToBase64String(_rsa.ExportRSAPublicKey())); + + _rsa.ImportRSAPublicKey(publicKey, out var read); + return _rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256); + } + + /// <summary> + /// Creating a self signed certificate + /// </summary> + /// <param name="exportPath">Location for storing the certificate files</param> + /// <returns></returns> + /// <exception cref="Exception"></exception> + public static X509Certificate2 CreateSelfSignedCertificate(string? exportPath = null) { + var rsa = RSA.Create(4096); + + var req = new CertificateRequest("c=DE, cn=fitconnect.de", + rsa, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + + var cert = req.CreateSelfSigned(DateTimeOffset.Now.AddSeconds(-5), + DateTimeOffset.Now.AddYears(5)); + + if (cert.GetRSAPublicKey() == null) + throw new Exception("Certificate does not contain a public key"); + + if (cert.GetRSAPrivateKey() == null) + throw new Exception("Certificate does not contain a private key"); + + // Export the certificate to a PEM file, just for + // additional extern testing + if (exportPath != null) ExportCertificateToFile(exportPath, cert); + + return cert; + } + + private static void ExportCertificateToFile(string exportPath, X509Certificate cert) { + // Create PFX (PKCS #12) with private key + File.WriteAllBytes($"{exportPath}/certificate.pfx", + cert.Export(X509ContentType.Pfx, "")); + + // Create Base 64 encoded CER (public key only) + File.WriteAllText($"{exportPath}/certificate.cer", + "-----BEGIN CERTIFICATE-----\r\n" + + Convert.ToBase64String(cert.Export(X509ContentType.Cert, ""), + Base64FormattingOptions.InsertLineBreaks) + + "\r\n-----END CERTIFICATE-----"); + } + + + /// <summary> + /// Checking the certificate for validity + /// </summary> + /// <param name="cert">Certificate to check</param> + /// <returns>true for a fully approved certificate</returns> + private bool CheckCertificate(X509Certificate2 cert) { + return true; + } +} diff --git a/FitConnect/Sender.cs b/FitConnect/Sender.cs index b988be52ef466d29170aebb26ca4a8c9ab5505ca..3b75e37b54739a36cde1cba5cf12c2ab94112fc1 100644 --- a/FitConnect/Sender.cs +++ b/FitConnect/Sender.cs @@ -1,23 +1,19 @@ -using System.Buffers.Text; -using System.ComponentModel; -using System.Security.Cryptography; -using System.Text; +using System.Security.Cryptography.X509Certificates; +using FitConnect.BaseClasses; +using FitConnect.Models; +using FitConnect.Services.Models; using Microsoft.Extensions.Logging; namespace FitConnect; public class Sender : FunctionalBaseClass { - - /// <summary> - /// Constructor for the Sender class. - /// </summary> - /// <param name="logger">ILogger implementation</param> - /// <param name="endpoints">FitConnect endpoints</param> - public Sender(ILogger? logger, FitConnectEndpoints endpoints) : base(logger, endpoints) { + public Sender(ILogger? logger, FitConnectEndpoints endpoints, + X509Certificate2? certificate = null) : base(logger, endpoints, certificate) { } + /// <summary> - /// Check public keys + /// Check public keys /// </summary> /// <returns></returns> public async Task<bool> CheckPublicKeyAsync(string publicKey) { @@ -25,16 +21,16 @@ public class Sender : FunctionalBaseClass { } /// <summary> - /// Encrypt attachments (Anhänge) + /// Encrypt attachments (Anhänge) /// </summary> /// <param name="attachment"></param> /// <returns></returns> - public async Task<string> EncryptAttachmentAsync(string attachment, string publicKey) { + public async Task<string> EncryptAttachment(string attachment, string publicKey) { throw new NotImplementedException(); } /// <summary> - /// Create Metadata incl. Hash + /// Create Metadata incl. Hash /// </summary> /// <param name="data"></param> /// <param name="attachment"></param> @@ -45,7 +41,7 @@ public class Sender : FunctionalBaseClass { } /// <summary> - /// Check public keys + /// Check public keys /// </summary> /// <returns></returns> public async Task<bool> CheckPublicKeyAsync() { @@ -53,25 +49,26 @@ public class Sender : FunctionalBaseClass { } /// <summary> - /// Encrypt Data (Fachdaten) + /// Encrypt Data (Fachdaten) /// </summary> /// <param name="data"></param> /// <returns></returns> - public byte[] EncryptDataAsync(string data) { + public byte[] EncryptDataAsync(string? data) { + if (data == null) return new byte[] { }; throw new NotImplementedException(); } /// <summary> - /// Encrypt attachments (Anhänge) + /// Encrypt attachments (Anhänge) /// </summary> /// <param name="attachment"></param> /// <returns></returns> - public async Task<byte[]> EncryptAttachmentAsync(byte[] attachment) { + public KeyValuePair<string, byte[]> EncryptAttachment(Attachment attachment) { throw new NotImplementedException(); } /// <summary> - /// Create Metadata incl. Hash + /// Create Metadata incl. Hash /// </summary> /// <param name="data"></param> /// <param name="attachment"></param> @@ -79,4 +76,22 @@ public class Sender : FunctionalBaseClass { public async Task<string> CreateMetadataAsync(string data, byte[] attachment) { throw new NotImplementedException(); } + + public CreateSubmissionDto CreateSubmissionDto(Submission submission) => new() { + DestinationId = submission.DestinationId, + ServiceType = (ServiceTypeDto)submission.ServiceType, + AnnouncedAttachments = submission.Attachments.Select(a => a.Id).ToList() + }; + + public async Task UploadAttachmentsAsync( + List<KeyValuePair<string, byte[]>> encryptedAttachments) { + throw new NotImplementedException(); + } + + public async Task SubmitSubmissionAsync(Submission submission) { + throw new NotImplementedException(); + } + + public Task<string> GetDestinationIdAsync(string leiaKey, string? ags, string? ars, + string? areaId) => ApiService.RouteService.GetDestinationIdAsync(leiaKey, ags, ars, areaId); } diff --git a/FitConnect/Subscriber.cs b/FitConnect/Subscriber.cs index 88aad24aabe46ff64ebfcb61055c72cd299e0677..42b37f8b157f912ad15f99c0e4c84a6eeb18ca88 100644 --- a/FitConnect/Subscriber.cs +++ b/FitConnect/Subscriber.cs @@ -1,4 +1,6 @@ using System.Reflection; +using System.Security.Cryptography.X509Certificates; +using FitConnect.BaseClasses; using FitConnect.Models; using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; @@ -7,31 +9,23 @@ using Newtonsoft.Json.Schema; namespace FitConnect; public class Subscriber : FunctionalBaseClass { - /// <summary> - /// Constructor for the Subscriber class. - /// </summary> - /// <param name="logger">ILogger implementation</param> - /// <param name="endpoints">FitConnect endpoints</param> - public Subscriber(ILogger logger, FitConnectEndpoints endpoints) : base(logger, endpoints) { + public Subscriber(ILogger? logger, FitConnectEndpoints endpoints, + X509Certificate2? certificate = null) : base(logger, endpoints, certificate) { } private async Task<string> GetContentFromEmbeddedResource(string filename) { var assembly = typeof(Subscriber).GetTypeInfo().Assembly; var resourceName = $"{assembly.GetName().Name}.{filename}"; - using (var stream = assembly.GetManifestResourceStream(resourceName)) { - if (stream == null) { - throw new Exception($"Could not find embedded resource {resourceName}"); - } + await using var stream = assembly.GetManifestResourceStream(resourceName); + if (stream == null) throw new Exception($"Could not find embedded resource {resourceName}"); - using (var reader = new StreamReader(stream)) { - return await reader.ReadToEndAsync(); - } - } + using var reader = new StreamReader(stream); + return await reader.ReadToEndAsync(); } /// <summary> - /// Decrypt Data (Fachdaten) + /// Decrypt Data (Fachdaten) /// </summary> /// <param name="data">(Fachdaten)</param> /// <returns></returns> @@ -40,7 +34,7 @@ public class Subscriber : FunctionalBaseClass { } /// <summary> - /// Decrypt attachments (Anhänge) + /// Decrypt attachments (Anhänge) /// </summary> /// <param name="attachment">Encrypted attachments</param> /// <returns></returns> @@ -49,7 +43,7 @@ public class Subscriber : FunctionalBaseClass { } /// <summary> - /// Checks the validity of the given metadata against the schema. + /// Checks the validity of the given metadata against the schema. /// </summary> /// <param name="jsonMetaData">JSON meta data</param> /// <returns></returns> @@ -63,7 +57,7 @@ public class Subscriber : FunctionalBaseClass { /// <summary> - /// Check Hash from Metadata + /// Check Hash from Metadata /// </summary> /// <param name="jsonMetaData">Metadata in JSON Format</param> /// <returns></returns> @@ -73,7 +67,7 @@ public class Subscriber : FunctionalBaseClass { /// <summary> - /// Create SecurityEventToken and signature + /// Create SecurityEventToken and signature /// </summary> /// <param name="data"></param> /// <param name="attachment"></param> diff --git a/InitializationTests/BaseClassConstructorTests.cs b/InitializationTests/BaseClassConstructorTests.cs index 0c8379673e7a10913e7ac97e9fe3867410c0928a..e0c7cc7e9cbd73b1dbd451aa57f7e9575aaf4a6e 100644 --- a/InitializationTests/BaseClassConstructorTests.cs +++ b/InitializationTests/BaseClassConstructorTests.cs @@ -16,30 +16,30 @@ public class BaseClassConstructorTests { } [Test] - public void CreateCustomEndpoints() { + public void Sender_CreateCustomEndpoints() { var dut = new Sender(_logger, new FitConnectEndpoints( "http://localhost:5050/token", "http://localhost:5050/submission", "http://localhost:5050/routing")); - dut.Endpoints.RoutingApi.Should().Be("http://localhost:5050/routing"); - dut.Endpoints.SubmissionApi.Should().Be("http://localhost:5050/submission"); + dut.Endpoints.RoutingUrl.Should().Be("http://localhost:5050/routing"); + dut.Endpoints.SubmissionUrl.Should().Be("http://localhost:5050/submission"); dut.Endpoints.TokenUrl.Should().Be("http://localhost:5050/token"); } [Test] - public void CreateDevEndpoints() { + public void Sender_CreateDevEndpoints() { var dut = new Sender(_logger, FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); dut.Endpoints.TokenUrl.Should().Be("https://auth-testing.fit-connect.fitko.dev/token"); - dut.Endpoints.SubmissionApi.Should() + dut.Endpoints.SubmissionUrl.Should() .Be("https://submission-api-testing.fit-connect.fitko.dev"); - dut.Endpoints.RoutingApi.Should().Be("https://routing-api-testing.fit-connect.fitko.dev"); + dut.Endpoints.RoutingUrl.Should().Be("https://routing-api-testing.fit-connect.fitko.dev"); } [Test] - public void CreateTestingEndpoints() { + public void Sender_CreateTestingEndpoints_ShouldThrowArgumentException() { Assert.Throws<ArgumentException>(() => { var sender = new Subscriber(_logger, FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Testing)); @@ -47,7 +47,7 @@ public class BaseClassConstructorTests { } [Test] - public void CreateProductionEndpoints() { + public void Sender_CreateProductionEndpoints_ShouldThrowArgumentException() { Assert.Throws<ArgumentException>(() => { var sender = new Sender(_logger, FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Production)); diff --git a/InitializationTests/InitializationTests.csproj b/InitializationTests/InitializationTests.csproj index 43b1dc07278ee1dd87ce10971772ba841c4ece36..ce48ba3cc6016e040d512812c74b541d24e45901 100644 --- a/InitializationTests/InitializationTests.csproj +++ b/InitializationTests/InitializationTests.csproj @@ -8,16 +8,16 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="FluentAssertions" Version="6.7.0" /> - <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> - <PackageReference Include="Moq" Version="4.18.1" /> - <PackageReference Include="NUnit" Version="3.13.2" /> - <PackageReference Include="NUnit3TestAdapter" Version="4.0.0" /> - <PackageReference Include="coverlet.collector" Version="3.1.0" /> + <PackageReference Include="FluentAssertions" Version="6.7.0"/> + <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0"/> + <PackageReference Include="Moq" Version="4.18.1"/> + <PackageReference Include="NUnit" Version="3.13.2"/> + <PackageReference Include="NUnit3TestAdapter" Version="4.0.0"/> + <PackageReference Include="coverlet.collector" Version="3.1.0"/> </ItemGroup> <ItemGroup> - <ProjectReference Include="..\FitConnect\FitConnect.csproj" /> + <ProjectReference Include="..\FitConnect\FitConnect.csproj"/> </ItemGroup> </Project> diff --git a/FitConnect/FitConnectEndpoints.cs b/Models/FitConnectEndpoints.cs similarity index 79% rename from FitConnect/FitConnectEndpoints.cs rename to Models/FitConnectEndpoints.cs index 57277b6d9aec6eb9314eb6af770f061b69133986..f044d8e51f43f290156e759a5e42caa40e1aa129 100644 --- a/FitConnect/FitConnectEndpoints.cs +++ b/Models/FitConnectEndpoints.cs @@ -1,42 +1,59 @@ namespace FitConnect; public class FitConnectEndpoints { + public enum EndpointType { + Development, + Testing, + Production + } + + private static readonly FitConnectEndpoints DevEndpoints = new( + "https://auth-testing.fit-connect.fitko.dev/token", + "https://submission-api-testing.fit-connect.fitko.dev", + "https://routing-api-testing.fit-connect.fitko.dev" + ); + + private static readonly FitConnectEndpoints TestEndpoints = new( + "https://auth-testing.fit-connect.fitko.dev/token", + "https://submission-api-testing.fit-connect.fitko.dev", + "https://routing-api-testing.fit-connect.fitko.dev" + ); + + private static readonly FitConnectEndpoints ProductionEndpoints = new( + "https://auth-testing.fit-connect.fitko.dev/token", + "https://submission-api-testing.fit-connect.fitko.dev", + "https://routing-api-testing.fit-connect.fitko.dev" + ); + /// <summary> - /// Default constructor. + /// Default constructor. /// </summary> /// <param name="tokenUrl">URL for receiving the OAuth token</param> - /// <param name="submissionApi">URL for the submission API</param> - /// <param name="routingApi">URL for the routing API</param> - public FitConnectEndpoints(string tokenUrl, string submissionApi, string routingApi) { + /// <param name="submissionUrl">URL for the submission API</param> + /// <param name="routingUrl">URL for the routing API</param> + public FitConnectEndpoints(string tokenUrl, string submissionUrl, string routingUrl) { TokenUrl = tokenUrl; - SubmissionApi = submissionApi; - RoutingApi = routingApi; + SubmissionUrl = submissionUrl; + RoutingUrl = routingUrl; } /// <summary> - /// URL for receiving the OAuth token. + /// URL for receiving the OAuth token. /// </summary> public string TokenUrl { get; } /// <summary> - /// URL for the submission API. + /// URL for the submission API. /// </summary> - public string SubmissionApi { get; } + public string SubmissionUrl { get; } /// <summary> - /// URL for the routing API. + /// URL for the routing API. /// </summary> - public string RoutingApi { get; } - - - public enum EndpointType { - Development, - Testing, - Production - } + public string RoutingUrl { get; } /// <summary> - /// Creates the endpoints for the given environment. + /// Creates the endpoints for the given environment. /// </summary> /// <param name="endpointType">Environment to get endpoints for</param> /// <returns></returns> @@ -49,22 +66,4 @@ public class FitConnectEndpoints { _ => throw new ArgumentOutOfRangeException(nameof(endpointType), endpointType, null) }; } - - private static readonly FitConnectEndpoints DevEndpoints = new( - "https://auth-testing.fit-connect.fitko.dev/token", - "https://submission-api-testing.fit-connect.fitko.dev", - "https://routing-api-testing.fit-connect.fitko.dev" - ); - - private static readonly FitConnectEndpoints TestEndpoints = new( - "https://auth-testing.fit-connect.fitko.dev/token", - "https://submission-api-testing.fit-connect.fitko.dev", - "https://routing-api-testing.fit-connect.fitko.dev" - ); - - private static readonly FitConnectEndpoints ProductionEndpoints = new( - "https://auth-testing.fit-connect.fitko.dev/token", - "https://submission-api-testing.fit-connect.fitko.dev", - "https://routing-api-testing.fit-connect.fitko.dev" - ); } diff --git a/Models/Models.csproj b/Models/Models.csproj new file mode 100644 index 0000000000000000000000000000000000000000..42326e5c3ec81e82a29317e741d328d2d68ef0b9 --- /dev/null +++ b/Models/Models.csproj @@ -0,0 +1,10 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <RootNamespace>FitConnect.Models</RootNamespace> + </PropertyGroup> + +</Project> diff --git a/RestService/RestCallService.cs b/RestService/RestCallService.cs new file mode 100644 index 0000000000000000000000000000000000000000..685b80177755ba01e29491440650f38efb18ad11 --- /dev/null +++ b/RestService/RestCallService.cs @@ -0,0 +1,30 @@ +using System.Net.Http.Json; +using System.Text; + +namespace FitConnect.RestService; + +public abstract class RestCallService { + private readonly string _baseUrl; + + protected RestCallService(string baseUrl) { + _baseUrl = baseUrl; + } + + internal async Task<T?> RestCall<T>(Uri uri, HttpMethod method, string body) { + var client = new HttpClient(); + client.DefaultRequestHeaders.Add("Accept", "application/json"); + client.DefaultRequestHeaders.Add("Content-Type", "application/json"); + + var request = new HttpRequestMessage(); + request.Method = method; + request.Content = new StringContent(body, Encoding.UTF8, "application/json"); + request.RequestUri = uri; + + var response = await client.SendAsync(request); + + if (response.IsSuccessStatusCode) + return await response.Content.ReadFromJsonAsync<T>(); + + throw new HttpRequestException("Error calling FitConnect API"); + } +} diff --git a/RestService/RestService.csproj b/RestService/RestService.csproj new file mode 100644 index 0000000000000000000000000000000000000000..b7d19297180b37a203c1072123f0f3035906fafb --- /dev/null +++ b/RestService/RestService.csproj @@ -0,0 +1,10 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <RootNamespace>FitConnect.RestService</RootNamespace> + </PropertyGroup> + +</Project> diff --git a/SenderTest/SenderEncryptionWithCertificateTest.cs b/SenderTest/SenderEncryptionWithCertificateTest.cs deleted file mode 100644 index 9e079737c1dde27b5b9a4560955c0b6ef7436481..0000000000000000000000000000000000000000 --- a/SenderTest/SenderEncryptionWithCertificateTest.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using FitConnect; -using Microsoft.Extensions.Logging; -using NUnit.Framework; - -namespace SenderTest; - -public class SenderEncryptionWithCertificateTest { - private Sender _sender = null!; - private ILogger<SenderEncryptionWithCertificateTest> _logger = null!; - - /* - * Encryption test must be changed for production to only allow extern signed certificates - * and forbid self-signed certificates. - */ - - [SetUp] - public void Setup() { - _logger = LoggerFactory.Create(cfg => cfg.AddConsole()) - .CreateLogger<SenderEncryptionWithCertificateTest>(); - _sender = new Sender(_logger, - FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); - var certificate = CreateSelfSignedCertificate(null); - _sender.ImportCertificate(certificate); - } - - - [Test] - public void CryptWithOutPublicKeyImport() { - var cypher = _sender.EncryptData(Encoding.UTF8.GetBytes("test")); - - _logger.LogInformation("Cypher: {}", Convert.ToBase64String(cypher)); - } - - - [Test] - [Ignore("Not applicable for production")] - public void CryptWithPublicKeyImport() { - var publicKey = Convert.FromBase64String( - "MIIBCgKCAQEAzu/ek6A5AMuROs+12pncbYNteGkd6ReU28ZY5gCM4hNFI0h1E+0+OST+Yxw7zhvbFhZbYdVt8LmzonMAtENituLxzZj7MsWom/ZzxTdp4Cx5zlx8x6Qx/ZPoSS2T2Sf0ttymaMc6ZadpWsDhg/Mnf6beF1W/QoGH/bHBa8U4rhkUa+OKf3wyo08km8oyUJaj6kkB0VdhRp5rSyvXJtUMZ5A0LcYFygnkHTSQlQhdrAK+6nTo//mfNfPtqta2wBb9ONpVwN0V7I5PSdH2WxZMZsYFicLOGbNeF08gibmL+7TeBTssYtrNVM88cG0v+aWeBun0WVrpCntDIA9HIujWowIDAQAB"); - - var cypher = _sender.EncryptData(Encoding.UTF8.GetBytes("test"), publicKey); - - _logger.LogInformation("Cypher: {}", Convert.ToBase64String(cypher)); - } - - [Test] - public void ExportPrivateKey() { - var privateKey = _sender.ExportPrivateKey(); - _logger.LogInformation("Private key: {}", Convert.ToBase64String(privateKey)); - } - - - #region Static helpers - - private X509Certificate2 CreateSelfSignedCertificate(string? exportPath = "../../../") { - var req = new CertificateRequest("cn=foobar", ECDsa.Create(), HashAlgorithmName.SHA256); - var cert = req.CreateSelfSigned(DateTimeOffset.Now, DateTimeOffset.Now.AddYears(5)); - - if (cert.GetRSAPublicKey() == null) - throw new Exception("Certificate does not contain a public key"); - - if (cert.GetRSAPrivateKey() == null) - throw new Exception("Certificate does not contain a private key"); - - // Export the certificate to a PEM file, just for - // additional extern testing - if (exportPath != null) { - ExportCertificateToFile(exportPath, cert); - } - - return cert; - } - - private void ExportCertificateToFile(string exportPath, X509Certificate cert) { - // Create PFX (PKCS #12) with private key - File.WriteAllBytes($"{exportPath}/certificate.pfx", - cert.Export(X509ContentType.Pfx, "")); - - // Create Base 64 encoded CER (public key only) - File.WriteAllText($"{exportPath}/certificate.cer", - "-----BEGIN CERTIFICATE-----\r\n" - + Convert.ToBase64String(cert.Export(X509ContentType.Cert, ""), - Base64FormattingOptions.InsertLineBreaks) - + "\r\n-----END CERTIFICATE-----"); - - _logger?.LogInformation("Exporting {}", - Convert.ToBase64String(cert.Export(X509ContentType.Cert, ""))); - } -} - -#endregion diff --git a/SenderTest/SenderEncryptionWithImportedCertificateTest.cs b/SenderTest/SenderEncryptionWithImportedCertificateTest.cs deleted file mode 100644 index 772bcfde18b62e23b2591303d38c739db24c393b..0000000000000000000000000000000000000000 --- a/SenderTest/SenderEncryptionWithImportedCertificateTest.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace SenderTest; - -public class SenderEncryptionWithImportedCertificateTest { -} diff --git a/SenderTest/SenderEncryptionWithoutCertificateTest.cs b/SenderTest/SenderEncryptionWithoutCertificateTest.cs deleted file mode 100644 index 06bd1d0659f76941caf720b58a463408d85c19d5..0000000000000000000000000000000000000000 --- a/SenderTest/SenderEncryptionWithoutCertificateTest.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Text; -using FitConnect; -using Microsoft.Extensions.Logging; -using NUnit.Framework; - -namespace SenderTest; - - -public class SenderEncryptionWithoutCertificateTest { - private Sender _sender = null!; - private ILogger<SenderEncryptionWithoutCertificateTest> _logger = null!; - - /* - * Encryption test must be changed for production to only allow extern signed certificates - * and forbid self-signed certificates. - */ - - [SetUp] - public void Setup() { - _logger = LoggerFactory.Create(cfg => cfg.AddConsole()) - .CreateLogger<SenderEncryptionWithoutCertificateTest>(); - _sender = new Sender(_logger, - FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development)); - } - - - [Test] - public void CryptWithOutPublicKeyImport() { - var cypher = _sender.EncryptData(Encoding.UTF8.GetBytes("test")); - - _logger.LogInformation("Cypher: {}", Convert.ToBase64String(cypher)); - } - - - [Test] - [Ignore("Not applicable for production")] - public void CryptWithPublicKeyImport() { - var publicKey = Convert.FromBase64String( - "MIIBCgKCAQEAzu/ek6A5AMuROs+12pncbYNteGkd6ReU28ZY5gCM4hNFI0h1E+0+OST+Yxw7zhvbFhZbYdVt8LmzonMAtENituLxzZj7MsWom/ZzxTdp4Cx5zlx8x6Qx/ZPoSS2T2Sf0ttymaMc6ZadpWsDhg/Mnf6beF1W/QoGH/bHBa8U4rhkUa+OKf3wyo08km8oyUJaj6kkB0VdhRp5rSyvXJtUMZ5A0LcYFygnkHTSQlQhdrAK+6nTo//mfNfPtqta2wBb9ONpVwN0V7I5PSdH2WxZMZsYFicLOGbNeF08gibmL+7TeBTssYtrNVM88cG0v+aWeBun0WVrpCntDIA9HIujWowIDAQAB"); - - var cypher = _sender.EncryptData(Encoding.UTF8.GetBytes("test"), publicKey); - - _logger.LogInformation("Cypher: {}", Convert.ToBase64String(cypher)); - } - - [Test] - public void ExportPrivateKey() { - var privateKey = _sender.ExportPrivateKey(); - _logger.LogInformation("Private key: {}", Convert.ToBase64String(privateKey)); - } - -} diff --git a/Services/CasesService.cs b/Services/CasesService.cs new file mode 100644 index 0000000000000000000000000000000000000000..2f679e7685f58b03ea97cdf4572e3fa03039096d --- /dev/null +++ b/Services/CasesService.cs @@ -0,0 +1,37 @@ +using FitConnect.RestService; +using FitConnect.Services.Models; + +namespace FitConnect.Services; + + +public class CasesService : RestCallService { + public CasesService(string baseUrl) : base(baseUrl) { + } + + + /// <summary> + /// <para> + /// @GetMapping("/cases/{caseId}/events") + /// </para> + /// </summary> + /// <param name="caseId">PathVariable</param> + /// <param name="offset">RequestParam</param> + /// <param name="limit">RequestParam</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public EventLogDto GetEventLog(string caseId, int offset, int limit) { + throw new NotImplementedException(); + } + + // + /// <summary> + /// <para>@PostMapping(value = "/cases/{caseId}/events", consumes = "application/jose")</para> + /// </summary> + /// <param name="caseId">PathVariable</param> + /// <param name="eventToken">RequestBody</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public bool ProcessCaseEvent(string caseId, string eventToken) { + throw new NotImplementedException(); + } +} \ No newline at end of file diff --git a/Services/DestinationService.cs b/Services/DestinationService.cs new file mode 100644 index 0000000000000000000000000000000000000000..af415b5299e3f453f23a30d188f5971fbf185b51 --- /dev/null +++ b/Services/DestinationService.cs @@ -0,0 +1,78 @@ +using FitConnect.RestService; +using FitConnect.Services.Models; + +namespace FitConnect.Services; + +public class DestinationService : RestCallService { + public DestinationService(string baseUrl) : base(baseUrl) { + } + + /// <summary> + /// <para>@PostMapping("/destinations")</para> + /// </summary> + /// <param name="createDestination">RequestBody</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public PrivateDestinationDto CreateDestination(CreateDestinationDto createDestination) { + throw new NotImplementedException(); + } + + + /// <summary> + /// <para>@GetMapping("/destinations/{destinationId}")</para> + /// </summary> + /// <param name="destinationId">PathVariable</param> + /// <exception cref="NotImplementedException"></exception> + public void GetDestination(string destinationId) { + throw new NotImplementedException(); + } + + + /// <summary> + /// <para>@DeleteMapping("/destinations/{destinationId}")</para> + /// </summary> + /// <param name="destinationId">PathVariable</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public bool DeleteDestination(string destinationId) { + throw new NotImplementedException(); + } + + + /// <summary> + /// <para>@PatchMapping("/destinations/{destinationId}")</para> + /// </summary> + /// <param name="destinationId">PathVariable</param> + /// <param name="patchDestination">RequestBody</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public PrivateDestinationDto PatchDestination(string destinationId, + PatchDestinationDto patchDestination) { + throw new NotImplementedException(); + } + + + /// <summary> + /// <para>@GetMapping("/destinations")</para> + /// </summary> + /// <param name="offset">RequestParam</param> + /// <param name="limit">RequestParam</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public DestinationListDto ListDestinations(int offset, int limit) { + throw new NotImplementedException(); + } + + + /// <summary> + /// <para>@PutMapping("/destinations/{destinationId}")</para> + /// </summary> + /// <param name="destinationId">PathVariable</param> + /// <param name="updateDestination">RequestBody</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public PrivateDestinationDto UpdateDestination(string destinationId, + UpdateDestinationDto updateDestination) { + throw new NotImplementedException(); + } +} diff --git a/Services/FitConnectApiService.cs b/Services/FitConnectApiService.cs new file mode 100644 index 0000000000000000000000000000000000000000..1b97070eb488f2a0d96421e90948e88fe61c6c56 --- /dev/null +++ b/Services/FitConnectApiService.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.Logging; + +namespace FitConnect.Services; + +public class FitConnectApiService { + protected readonly CasesService CasesService; + protected readonly DestinationService DestinationService; + protected readonly OAuthService OAuthService; + protected readonly SubmissionService SubmissionService; + public readonly RouteService RouteService; + + public FitConnectApiService(FitConnectEndpoints endpoints, ILogger? logger = null) { + CasesService = new CasesService(endpoints.SubmissionUrl); + DestinationService = new DestinationService(endpoints.SubmissionUrl); + SubmissionService = new SubmissionService(endpoints.SubmissionUrl); + OAuthService = new OAuthService(endpoints.TokenUrl); + RouteService = new RouteService(endpoints.RoutingUrl); + } +} diff --git a/Services/InfoService.cs b/Services/InfoService.cs new file mode 100644 index 0000000000000000000000000000000000000000..fd3d9ae5c747bca0e0778410a3d56a883bea718d --- /dev/null +++ b/Services/InfoService.cs @@ -0,0 +1,17 @@ +using FitConnect.RestService; + +namespace FitConnect.Services; + +public class InfoService : RestCallService { + public InfoService(string baseUrl) : base(baseUrl) { + } + + /// <summary> + /// @GetMapping("/info") + /// </summary> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public string GetInfo() { + throw new NotImplementedException(); + } +} diff --git a/Services/Models/ApiInfoDto.cs b/Services/Models/ApiInfoDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..5cc8756064c2fa0b71a55d252659ace554ac430e --- /dev/null +++ b/Services/Models/ApiInfoDto.cs @@ -0,0 +1,4 @@ +namespace FitConnect.Services.Models; + +public class ApiInfoDto { +} diff --git a/Services/Models/CallbackDto.cs b/Services/Models/CallbackDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..2a2e45adfbdf83955c5c784b972d4786f80783c3 --- /dev/null +++ b/Services/Models/CallbackDto.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class CallbackDto { + [JsonPropertyName("url")] + public string Url { get; set; } + + [JsonPropertyName("secret")] + public string? Secret { get; set; } +} diff --git a/Services/Models/Case/EventLogDto.cs b/Services/Models/Case/EventLogDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..02e6e888af58fd89d1c294bd9a5e0b9fef3b535f --- /dev/null +++ b/Services/Models/Case/EventLogDto.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class EventLogDto { + [JsonPropertyName("count")] + public int Count; + + [JsonPropertyName("eventLog")] + public List<string> EventLog; + + [JsonPropertyName("offset")] + public int Offset; + + [JsonPropertyName("totalCount")] + public long TotalCount; +} diff --git a/Services/Models/Destination/ContactInformationDto.cs b/Services/Models/Destination/ContactInformationDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..00b02222857b47e0d1e6cbd083f0cf03b7bd7ba6 --- /dev/null +++ b/Services/Models/Destination/ContactInformationDto.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class ContactInformationDto { + [JsonPropertyName("address")] + public string Address; + + [JsonPropertyName("email")] + public string Email; + + [JsonPropertyName("legalName")] + public string LegalName; + + [JsonPropertyName("phone")] + public string Phone; + + [JsonPropertyName("unit")] + public string Unit; +} diff --git a/Services/Models/Destination/CreateDestinationDto.cs b/Services/Models/Destination/CreateDestinationDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..92767ff8542bc0f5c11cfa00e999a849ccfd6cea --- /dev/null +++ b/Services/Models/Destination/CreateDestinationDto.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class CreateDestinationDto { + [JsonPropertyName("callback")] + public CallbackDto Callback; + + [JsonPropertyName("contactInformation")] + public ContactInformationDto ContactInformation; + + + [JsonPropertyName("encryptionKid")] + public string EncryptionKid; + + + [JsonPropertyName("encryptionPublicKey")] + public string EncryptionPublicKey; + + + [JsonPropertyName("metadataVersions")] + public List<string> metadataVersions; + + + [JsonPropertyName("replyChannels")] + public DestinationReplyChannelsDto ReplyChannels; + + + [JsonPropertyName("services")] + public List<DestinationServiceDto> Services; + + + [JsonPropertyName("signingPublicKey")] + public string SigningPublicKey; +} diff --git a/Services/Models/Destination/DeMailDto.cs b/Services/Models/Destination/DeMailDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..34c7cab917aad09d716cb4aeadc8d261708b046e --- /dev/null +++ b/Services/Models/Destination/DeMailDto.cs @@ -0,0 +1,4 @@ +namespace FitConnect.Services.Models; + +public class DeMailDto { +} diff --git a/Services/Models/Destination/DestinationListDto.cs b/Services/Models/Destination/DestinationListDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..55aa52c4af09b259d42384c6d34f5d5bf82e4d9e --- /dev/null +++ b/Services/Models/Destination/DestinationListDto.cs @@ -0,0 +1,20 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class DestinationListDto { + [JsonPropertyName("count")] + public int Count; + + + [JsonPropertyName("destinations")] + public List<PrivateDestinationDto> Destinations; + + + [JsonPropertyName("offset")] + public int Offset; + + + [JsonPropertyName("totalCount")] + public long TotalCount; +} diff --git a/Services/Models/Destination/DestinationReplyChannelsDto.cs b/Services/Models/Destination/DestinationReplyChannelsDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..95435299ccabf50a5e3cecc7c0133e080eb3eca4 --- /dev/null +++ b/Services/Models/Destination/DestinationReplyChannelsDto.cs @@ -0,0 +1,29 @@ +namespace FitConnect.Services.Models; + +public class DestinationReplyChannelsDto { + public DeMailDto? DeMail; + + + public ElsterDto? Elster; + public EmailDto? EMail; + + + public FinkDto? Fink; + + + public DestinationReplyChannelsDto( + EmailDto eMail, + DeMailDto deMail, + FinkDto fink, + ElsterDto elster) { + EMail = EMail; + DeMail = DeMail; + Fink = Fink; + Elster = Elster; + } + + + public bool IsEmpty() { + return EMail == null && DeMail == null && Fink == null && Elster == null; + } +} diff --git a/Services/Models/Destination/DestinationServiceDto.cs b/Services/Models/Destination/DestinationServiceDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..a3f72f3a9b7c593f7b1f72d46b0888bb66755bad --- /dev/null +++ b/Services/Models/Destination/DestinationServiceDto.cs @@ -0,0 +1,15 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class DestinationServiceDto { + [JsonPropertyName("identifier")] + public string Identifier; + + + [JsonPropertyName("regions")] + public List<string> Regions; + + [JsonPropertyName("submissionSchemas")] + public List<SubmissionSchemaDto> SubmissionSchemas; +} diff --git a/Services/Models/Destination/ElsterDto.cs b/Services/Models/Destination/ElsterDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..05f7cc5f8d5cfaa22e5a6850296c12eb70edc716 --- /dev/null +++ b/Services/Models/Destination/ElsterDto.cs @@ -0,0 +1,4 @@ +namespace FitConnect.Services.Models; + +public class ElsterDto { +} diff --git a/Services/Models/Destination/EmailDto.cs b/Services/Models/Destination/EmailDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..ffaf5ccabc0edd79b1f3dc5183c7daf20a0283e5 --- /dev/null +++ b/Services/Models/Destination/EmailDto.cs @@ -0,0 +1,4 @@ +namespace FitConnect.Services.Models; + +public class EmailDto { +} diff --git a/Services/Models/Destination/FinkDto.cs b/Services/Models/Destination/FinkDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..862620319fa862da5c46b75add2d4bf9dd9d04e6 --- /dev/null +++ b/Services/Models/Destination/FinkDto.cs @@ -0,0 +1,4 @@ +namespace FitConnect.Services.Models; + +public class FinkDto { +} diff --git a/Services/Models/Destination/PatchDestinationDto.cs b/Services/Models/Destination/PatchDestinationDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..f557d726c29f64bb27a8970021ecb7c2901df99b --- /dev/null +++ b/Services/Models/Destination/PatchDestinationDto.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class PatchDestinationDto { + [JsonPropertyName("callback")] + public CallbackDto Callback; + + + [JsonPropertyName("contactInformation")] + public ContactInformationDto ContactInformation; + + + [JsonPropertyName("encryptionKid")] + public string EncryptionKid; + + + [JsonPropertyName("metadataVersions")] + public List<string> metadataVersions; + + + [JsonPropertyName("replyChannels")] + public DestinationReplyChannelsDto ReplyChannels; + + + [JsonPropertyName("services")] + public List<DestinationServiceDto> Services; + + [JsonPropertyName("status")] + public string Status; +} diff --git a/Services/Models/Destination/PrivateDestinationDto.cs b/Services/Models/Destination/PrivateDestinationDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..3db3fa3b67c63aea48b57e0bb3910cc4c801d1a2 --- /dev/null +++ b/Services/Models/Destination/PrivateDestinationDto.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class PrivateDestinationDto { + [JsonPropertyName("callback")] + public CallbackDto Callback; + + + [JsonPropertyName("contactInformation")] + public ContactInformationDto ContactInformation; + + [JsonPropertyName("destinationId")] + public string DestinationId; + + + [JsonPropertyName("encryptionKid")] + public string EncryptionKid; + + + [JsonPropertyName("metadataVersions")] + public List<string> MetadataVersions; + + + [JsonPropertyName("replyChannels")] + public DestinationReplyChannelsDto ReplyChannels; + + + [JsonPropertyName("services")] + public List<DestinationServiceDto> Services; + + + [JsonPropertyName("status")] + public string Status; +} diff --git a/Services/Models/Destination/PublicDestinationDto.cs b/Services/Models/Destination/PublicDestinationDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..4bf90d449e4f99d15a6a358f4f82e0d52770d06c --- /dev/null +++ b/Services/Models/Destination/PublicDestinationDto.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class PublicDestinationDto { + [JsonPropertyName("destinationId")] + public string DestinationId; + + + [JsonPropertyName("encryptionKid")] + public string EncryptionKid; + + + [JsonPropertyName("metadataVersions")] + public List<string> MetadataVersions; + + + [JsonPropertyName("replyChannels")] + public DestinationReplyChannelsDto ReplyChannels; + + + [JsonPropertyName("services")] + public List<DestinationServiceDto> Services; + + + [JsonPropertyName("status")] + public string Status; +} diff --git a/Services/Models/Destination/SubmissionSchemaDto.cs b/Services/Models/Destination/SubmissionSchemaDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..6bb3cc3b7584814403cd96d027eac47bbe2a1dcf --- /dev/null +++ b/Services/Models/Destination/SubmissionSchemaDto.cs @@ -0,0 +1,12 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class SubmissionSchemaDto { + [JsonPropertyName("mimeType")] + //private SubmissionSchemaMimeTypeDto mimeType; + public int mimeType; + + [JsonPropertyName("schemaUri")] + public string schemaUri; +} diff --git a/Services/Models/Destination/UpdateDestinationDto.cs b/Services/Models/Destination/UpdateDestinationDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..92f0aa48091bb79130a3da96ba5593cd4b17a85f --- /dev/null +++ b/Services/Models/Destination/UpdateDestinationDto.cs @@ -0,0 +1,31 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class UpdateDestinationDto { + [JsonPropertyName("callback")] + public CallbackDto Callback; + + + [JsonPropertyName("contactInformation")] + public ContactInformationDto ContactInformation; + + + [JsonPropertyName("encryptionKid")] + public string EncryptionKid; + + + [JsonPropertyName("metadataVersions")] + public List<string> metadataVersions; + + + [JsonPropertyName("replyChannels")] + public DestinationReplyChannelsDto ReplyChannels; + + + [JsonPropertyName("services")] + public List<DestinationServiceDto> Services; + + [JsonPropertyName("status")] + public string Status; +} diff --git a/FitConnect/Models/OAuthAccessToken.cs b/Services/Models/OAuthAccessToken.cs similarity index 91% rename from FitConnect/Models/OAuthAccessToken.cs rename to Services/Models/OAuthAccessToken.cs index 87ac3872e52a9a9e88e1df33eace09bb8fbd3ed1..3fc70d9b9c1063c2df508e3accbd4756e85939e4 100644 --- a/FitConnect/Models/OAuthAccessToken.cs +++ b/Services/Models/OAuthAccessToken.cs @@ -1,6 +1,6 @@ using System.Text.Json.Serialization; -namespace FitConnect.Models; +namespace FitConnect.Services.Models; public class OAuthAccessToken { [JsonPropertyName("access_token")] diff --git a/Services/Models/ServiceTypeDto.cs b/Services/Models/ServiceTypeDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..97b5a604fec11305eb21684cc6675b828f121ddb --- /dev/null +++ b/Services/Models/ServiceTypeDto.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class ServiceTypeDto { + [JsonPropertyName("name")] + public string? Name { get; set; } + + [JsonPropertyName("description")] + public string? Description { get; set; } + + [JsonPropertyName("identifier")] + public string? Identifier { get; set; } +} diff --git a/Services/Models/Submission/CreateSubmissionDto.cs b/Services/Models/Submission/CreateSubmissionDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..c19cad9e5adb2bd8b9ad1c3051f6555dd79fb97b --- /dev/null +++ b/Services/Models/Submission/CreateSubmissionDto.cs @@ -0,0 +1,22 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class CreateSubmissionDto { + [JsonPropertyName("callback")] + public CallbackDto Callback { get; set; } + + + [JsonPropertyName("serviceType")] + public ServiceTypeDto ServiceType { get; set; } + + + [JsonPropertyName("announcedAttachments")] + public List<string> AnnouncedAttachments { get; set; } + + [JsonPropertyName("caseId")] + public string CaseId { get; set; } + + [JsonPropertyName("destinationId")] + public string DestinationId { get; set; } +} diff --git a/Services/Models/Submission/SubmissionCreatedDto.cs b/Services/Models/Submission/SubmissionCreatedDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..950d4096b31f14b86a4274af66bb5fd9c2b44bc4 --- /dev/null +++ b/Services/Models/Submission/SubmissionCreatedDto.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class SubmissionCreatedDto { + [JsonPropertyName("destinationId")] + public string DestinationId { get; set; } + + [JsonPropertyName("submissionId")] + public string SubmissionId { get; set; } + + [JsonPropertyName("caseId")] + public string CaseId { get; set; } +} diff --git a/Services/Models/Submission/SubmissionDto.cs b/Services/Models/Submission/SubmissionDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..d5816c52f505d35d8f575bba9bd150c8db0add1c --- /dev/null +++ b/Services/Models/Submission/SubmissionDto.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class SubmissionDto { + [JsonPropertyName("attachments")] + public List<string> Attachments; + + + [JsonPropertyName("callback")] + public CallbackDto Callback; + + + [JsonPropertyName("caseId")] + public string CaseId; + + [JsonPropertyName("destinationId")] + public string DestinationId; + + + [JsonPropertyName("encryptedData")] + public string EncryptedData; + + + [JsonPropertyName("encryptedMetadata")] + public string EncryptedMetadata; + + + [JsonPropertyName("serviceType")] + public ServiceTypeDto ServiceType; + + + [JsonPropertyName("submissionId")] + public string SubmissionId; +} diff --git a/Services/Models/Submission/SubmissionForPickupDto.cs b/Services/Models/Submission/SubmissionForPickupDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..eab6ce3327722cd6a842d2d946ae12c5aaf80b70 --- /dev/null +++ b/Services/Models/Submission/SubmissionForPickupDto.cs @@ -0,0 +1,14 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class SubmissionForPickupDto { + [JsonPropertyName("caseId")] + public string CaseId; + + [JsonPropertyName("destinationId")] + public string DestinationId; + + [JsonPropertyName("submissionId")] + public string SubmissionId; +} diff --git a/Services/Models/Submission/SubmissionReducedDto.cs b/Services/Models/Submission/SubmissionReducedDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..2d83e86a8accbd84468f71eaf0ab2c27ac43bfa0 --- /dev/null +++ b/Services/Models/Submission/SubmissionReducedDto.cs @@ -0,0 +1,27 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class SubmissionReducedDto { + [JsonPropertyName("serviceType")] + private ServiceTypeDto _serviceTypeDto; + + + [JsonPropertyName("attachments")] + private List<string> Attachments; + + + [JsonPropertyName("callback")] + private CallbackDto Callback; + + + [JsonPropertyName("caseId")] + private string CaseId; + + [JsonPropertyName("destinationId")] + private string DestinationId; + + + [JsonPropertyName("submissionId")] + private string SubmissionId; +} diff --git a/Services/Models/Submission/SubmissionsForPickupDto.cs b/Services/Models/Submission/SubmissionsForPickupDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..e17182b6737733683b49d6477a3097b23ca24f35 --- /dev/null +++ b/Services/Models/Submission/SubmissionsForPickupDto.cs @@ -0,0 +1,17 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class SubmissionsForPickupDto { + [JsonPropertyName("count")] + public int Count; + + [JsonPropertyName("offset")] + public int Offset; + + [JsonPropertyName("submissions")] + public List<SubmissionForPickupDto> Submissions; + + [JsonPropertyName("totalCount")] + public long TotalCount; +} diff --git a/Services/Models/Submission/SubmitSubmissionDto.cs b/Services/Models/Submission/SubmitSubmissionDto.cs new file mode 100644 index 0000000000000000000000000000000000000000..ec47ff8a5fe4adc0f8c9e46ffc8fc5728d407c65 --- /dev/null +++ b/Services/Models/Submission/SubmitSubmissionDto.cs @@ -0,0 +1,11 @@ +using System.Text.Json.Serialization; + +namespace FitConnect.Services.Models; + +public class SubmitSubmissionDto { + [JsonPropertyName("encryptedData")] + public string EncryptedData; + + [JsonPropertyName("encryptedMetadata")] + public string EncryptedMetadata; +} diff --git a/Services/OAuthService.cs b/Services/OAuthService.cs new file mode 100644 index 0000000000000000000000000000000000000000..bc1d939fe0dd0f45825866950cb405cdb374675f --- /dev/null +++ b/Services/OAuthService.cs @@ -0,0 +1,52 @@ +using System.Net.Http.Headers; +using System.Net.Http.Json; +using FitConnect.Services.Models; + +namespace FitConnect.Services; + +public class OAuthService { + private readonly string _tokenUrl; + + public OAuthService(string tokenUrl) { + _tokenUrl = tokenUrl; + } + + /// <summary> + /// Requesting an OAuth token from the FitConnect API. + /// <para> + /// You can get the Client ID and Client Secret from the FitConnect Self Service portal + /// under <br /> + /// https://portal.auth-testing.fit-connect.fitko.dev + /// </para> + /// </summary> + /// <param name="clientId">Your client Id</param> + /// <param name="clientSecret">Your client Secret</param> + /// <param name="scope">Scope if needed</param> + /// <returns>The received token or null</returns> + public async Task<OAuthAccessToken?> GetTokenAsync(string clientId, string clientSecret, + string? scope = null) { + var client = new HttpClient(); + client.DefaultRequestHeaders.Accept.Add( + MediaTypeWithQualityHeaderValue.Parse("application/json")); + + var requestContent = new Dictionary<string, string> { + { "grant_type", "client_credentials" }, + { "client_id", clientId }, + { "client_secret", clientSecret } + }; + + if (scope != null) + requestContent["scope"] = scope; + + var content = new FormUrlEncodedContent(requestContent); + + var request = new HttpRequestMessage(HttpMethod.Post, _tokenUrl) { + Content = content, + Method = HttpMethod.Post + }; + + var response = await client.SendAsync(request); + + return await response.Content.ReadFromJsonAsync<OAuthAccessToken>(); + } +} diff --git a/Services/RouteService.cs b/Services/RouteService.cs new file mode 100644 index 0000000000000000000000000000000000000000..0f11935e238721be49d09b03a7dff869f481e030 --- /dev/null +++ b/Services/RouteService.cs @@ -0,0 +1,22 @@ +using FitConnect.RestService; + +namespace FitConnect.Services; + +public class RouteService : RestCallService { + public RouteService(string baseUrl) : base(baseUrl) { + } + + /// <summary> + /// Returns the destination id for the given intent. + /// </summary> + /// <param name="leiaKey"></param> + /// <param name="ags"></param> + /// <param name="ars"></param> + /// <param name="areaId"></param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public Task<string> GetDestinationIdAsync(string leiaKey, string? ags, string? ars, + string? areaId) { + throw new NotImplementedException(); + } +} diff --git a/Services/Services.csproj b/Services/Services.csproj new file mode 100644 index 0000000000000000000000000000000000000000..4f40401bc9d2194670a8eff2890155ffff494112 --- /dev/null +++ b/Services/Services.csproj @@ -0,0 +1,19 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>net6.0</TargetFramework> + <ImplicitUsings>enable</ImplicitUsings> + <Nullable>enable</Nullable> + <RootNamespace>FitConnect.Services</RootNamespace> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\Models\Models.csproj" /> + <ProjectReference Include="..\RestService\RestService.csproj" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> + </ItemGroup> + +</Project> diff --git a/Services/Services.csproj.DotSettings b/Services/Services.csproj.DotSettings new file mode 100644 index 0000000000000000000000000000000000000000..813beff19dbf997729c10d1fb5277c01d04646ca --- /dev/null +++ b/Services/Services.csproj.DotSettings @@ -0,0 +1,5 @@ +<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Ccase/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cdestination/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Cdestinations/@EntryIndexedValue">True</s:Boolean> + <s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=models_005Csubmission/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> \ No newline at end of file diff --git a/Services/SubmissionService.cs b/Services/SubmissionService.cs new file mode 100644 index 0000000000000000000000000000000000000000..4c37e10da8a62449545b76ee8869fccb0fe0c94c --- /dev/null +++ b/Services/SubmissionService.cs @@ -0,0 +1,86 @@ +using FitConnect.RestService; +using FitConnect.Services.Models; + +namespace FitConnect.Services; + +public class SubmissionService : RestCallService { + public SubmissionService(string baseUrl) : base(baseUrl) { + } + + /// <summary> + /// <para>@PostMapping("/submissions")</para> + /// </summary> + /// <param name="submissionDto">RequestBody</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public SubmissionCreatedDto CreateSubmission(CreateSubmissionDto submissionDto) { + throw new NotImplementedException(); + } + + + /// <summary> + /// <para> + /// @PutMapping(value = "/submissions/{submissionId}/attachments/{attachmentId}", consumes = + /// "application/jose") + /// </para> + /// </summary> + /// <param name="submissionId">PathVariable</param> + /// <param name="attachmentId">PathVariable</param> + /// <param name="encryptedAttachmentContent">RequestBody</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public bool AddSubmissionAttachment(string submissionId, string attachmentId, + string encryptedAttachmentContent) { + throw new NotImplementedException(); + } + + /// <summary> + /// <para>@PutMapping(value = "/submissions/{submissionId}", consumes = "application/json") </para> + /// </summary> + /// <param name="submissionId">PathVariable</param> + /// <param name="submitSubmission">RequestBody</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public SubmissionReducedDto SubmitSubmission(string submissionId, + SubmitSubmissionDto submitSubmission) { + throw new NotImplementedException(); + } + + + /// <summary> + /// <para>@GetMapping("/submissions")</para> + /// </summary> + /// <param name="destinationId">RequestParam</param> + /// <param name="offset">RequestParam</param> + /// <param name="limit">RequestParam</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public async Task<SubmissionsForPickupDto> ListSubmissions(string destinationId, int offset, int limit) { + throw new NotImplementedException(); + } + + /// <summary> + /// <para>@GetMapping("/submissions/{submissionId}")</para> + /// </summary> + /// <param name="submissionId">PathVariable</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public SubmissionDto GetSubmission(string submissionId) { + throw new NotImplementedException(); + } + + + /// <summary> + /// <para> + /// @GetMapping(value = "/submissions/{submissionId}/attachments/{attachmentId}", produces = + /// "application/jose") + /// </para> + /// </summary> + /// <param name="submissionId">PathVariable</param> + /// <param name="attachmentId">PathVariable</param> + /// <returns></returns> + /// <exception cref="NotImplementedException"></exception> + public string GetAttachment(string submissionId, string attachmentId) { + throw new NotImplementedException(); + } +} diff --git a/readme.md b/readme.md index bf546573d17a2fd6f4b40cddaafd25229568d4ff..3dfd9971dd5bd1c8742d13e25621e1801a76d1a8 100644 --- a/readme.md +++ b/readme.md @@ -2,10 +2,90 @@ **Fit-Connect .NET SDK** is a .NET library for the Fit-Connect API. -**IN DEVELOPMENT NOT FOR PRODUCTION USE** +## **!! IN DEVELOPMENT NOT FOR PRODUCTION USE !!** + +## Structure + +```mermaid + classDiagram + class FitConnectSDK{ + FitConnectApiService + } + + class FitConnectApiService{ + CasesService + DestinationService + InfoService + OAuthService + RouteService + } + + FitConnectSDK ..> Sender : public + FitConnectSDK ..> Subscriber : public + Sender --|> FunctionalBaseClass + Subscriber --|> FunctionalBaseClass + FunctionalBaseClass ..> FitConnectApiService : protected + + FitConnectApiService ..> RouteService : public + FitConnectApiService ..> CasesService : public + FitConnectApiService ..> DestinationService : public + FitConnectApiService ..> InfoService : public + FitConnectApiService ..> OAuthService : public + + RouteService --|> RestCallService : Inheritance + CasesService --|> RestCallService : Inheritance + DestinationService --|> RestCallService : Inheritance + InfoService --|> RestCallService : Inheritance + OAuthService --|> RestCallService : Inheritance + +``` + +With that structure the user of the SDK is intended to see the following: + +```mermaid + classDiagram + + class FitConnectSDK { + +SendSumission + +GetSubmissions + } + + class Sender { + << Draft >> + + Einreichung anlegen und ankündigen + + Anlagen verschlüsseln + + Anlagen hochladen + + Metadaten befüllen und verschlüsseln + + Fachdaten verschlüsseln + + Fachdaten & Metadaten hochladen und Einreichung absenden + } + + class Subscriber { + << Draft >> + < TO BE DEFINED > + } + + FitConnectSDK --> Sender + FitConnectSDK --> Subscriber +``` + +### Annotation + +The structure ensures a abstraction of the API calls for the user and a 'simple' way to change the +API calls if a new version of the API is released. Currently the API call is not versioned and hard +coded. + +#### Abstractions for API calls + +- The use of a dependency injection can be added to + inject the API calls into the SDK. But is not yet implemented. +- Another approach would be using a generic class for the API calls. ## Ignored Files +The files listed below are ignored and necessary just for development. In production the information +is provided by the user of the SDK. + You need a secret file for e2e test like: ```json diff --git a/working_notes.md b/working_notes.md index 517d433b53dbfa873b286a70452895f17e60a3eb..91b76b4a0db6dd3058c0fe7392d4bf4edd2a3c84 100644 --- a/working_notes.md +++ b/working_notes.md @@ -18,6 +18,11 @@ | X | | | Sender, Subscriber | Unterstüzung / Abstraktion der API-Nutzung (`fitconnect.sendSubmission(metadata, destinationID, ...)` o.ä.) für die oben genannten Use-Cases | | X | | | Sender, Subscriber | Logging (Logging-Modul muss von außen kommen) | +# Routing API + +- [Description](https://docs.fitko.de/fit-connect/docs/responsibilities/get-destination) +- [Swagger](https://docs.fitko.de/fit-connect/docs/apis/routing-api#get-/routes) + ## Links - [SDK-Konzept im Wiki](https://wiki.fit-connect.fitko.dev/de/Konzeption/Konzeption_SDK) @@ -29,3 +34,4 @@ - [Documentation](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/) +