Skip to content
Snippets Groups Projects
Commit 4fe0d972 authored by Klaus Fischer's avatar Klaus Fischer
Browse files

Merge branch 'feature/438-method-signatures' into 'main'

Methodensignaturen - planning#438

See merge request !1
parents 15dad14b 4b4c4b6a
No related branches found
No related tags found
1 merge request!1Methodensignaturen - planning#438
Showing
with 450 additions and 29 deletions
*private.env.json
### VisualStudioCode template
.vscode/*
!.vscode/settings.json
......
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
\ No newline at end of file
FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base
WORKDIR /app
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["DummyClient/DummyClient.csproj", "DummyClient/"]
RUN dotnet restore "DummyClient/DummyClient.csproj"
COPY . .
WORKDIR "/src/DummyClient"
RUN dotnet build "DummyClient.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "DummyClient.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "DummyClient.dll"]
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\FitConnect\FitConnect.csproj"/>
</ItemGroup>
</Project>
<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:String x:Key="/Default/CodeInspection/Highlighting/UsageCheckingInspectionLevel/@EntryValue">Off</s:String></wpf:ResourceDictionary>
\ No newline at end of file
using System;
using FitConnect;
using FitConnect.Models;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
Client client;
void FluentSenderCall() {
client.Sender
.WithDestination("destinationId")
.WithAttachments(Array.Empty<Attachment>())
.WithData(@"{""data"":""content""}")
.Submit();
client.Sender
.WithDestination("destinationId")
.WithAttachments(Array.Empty<Attachment>())
.Submit();
}
void FluentSubscriberCall() {
var submissions = client.Subscriber
.GetAvailableSubmissions("destinationId");
client.Subscriber.RequestSubmission("submissionId")
.GetAttachments((attachments => {
// Check if the attachments are valid
return true;
}));
}
ILogger logger = new Logger<AppDomain>(new NullLoggerFactory());
client = new Client(
FitConnectEnvironments.Create(FitConnectEnvironments.EndpointType.Development),
"clientId", "clientSecret",
logger);
Console.WriteLine(
"This is a dummy client to demonstrate the usage of the FitConnect SDK for .NET");
FluentSenderCall();
FluentSubscriberCall();
File added
......@@ -2,9 +2,7 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FitConnect", "FitConnect\FitConnect.csproj", "{DFF6A0D9-5AA1-4640-B26C-4A0A28E42FA1}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{D382641C-B027-411A-814C-C8C20A9505F3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InitializationTests", "InitializationTests\InitializationTests.csproj", "{73CE5625-4C13-458E-B524-0DAA850F4041}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DummyClient", "DummyClient\DummyClient.csproj", "{DEF51494-6BCD-4441-8D76-6769BBA2C089}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
......@@ -16,12 +14,11 @@ Global
{DFF6A0D9-5AA1-4640-B26C-4A0A28E42FA1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DFF6A0D9-5AA1-4640-B26C-4A0A28E42FA1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DFF6A0D9-5AA1-4640-B26C-4A0A28E42FA1}.Release|Any CPU.Build.0 = Release|Any CPU
{73CE5625-4C13-458E-B524-0DAA850F4041}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{73CE5625-4C13-458E-B524-0DAA850F4041}.Debug|Any CPU.Build.0 = Debug|Any CPU
{73CE5625-4C13-458E-B524-0DAA850F4041}.Release|Any CPU.ActiveCfg = Release|Any CPU
{73CE5625-4C13-458E-B524-0DAA850F4041}.Release|Any CPU.Build.0 = Release|Any CPU
{DEF51494-6BCD-4441-8D76-6769BBA2C089}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEF51494-6BCD-4441-8D76-6769BBA2C089}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEF51494-6BCD-4441-8D76-6769BBA2C089}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEF51494-6BCD-4441-8D76-6769BBA2C089}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{73CE5625-4C13-458E-B524-0DAA850F4041} = {D382641C-B027-411A-814C-C8C20A9505F3}
EndGlobalSection
EndGlobal
using System;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace FitConnect;
/// <summary>
/// The FitConnect API Client
/// </summary>
// ReSharper disable once UnusedType.Global
public class Client {
internal string? ClientId;
internal string? ClientSecret;
public IFluentSender Sender { get; }
public IFluentSubscriber Subscriber { get; }
// private Routing Routing { get; }
/// <summary>
/// Constructor for the FitConnect API Client
/// </summary>
/// <param name="environments">Choose one endpoint or create your own one</param>
/// <param name="clientId">Your client id</param>
/// <param name="clientSecret">Your client secret</param>
/// <param name="logger">Optional logger</param>
public Client(
FitConnectEnvironments environments,
string clientId,
string clientSecret,
ILogger? logger = null) {
ClientId = clientId;
ClientSecret = clientSecret;
}
}
......@@ -11,7 +11,20 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="6.4.0" />
<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.20.0" />
</ItemGroup>
<ItemGroup>
<None Remove="metadata.schema.json" />
<EmbeddedResource Include="metadata.schema.json" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Services\Services.csproj" />
</ItemGroup>
</Project>
<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/=interfaces/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
\ No newline at end of file
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Security.Cryptography;
using System.Text;
using FitConnect.Models;
using Microsoft.Extensions.Logging;
namespace FitConnect;
public class FitConnectEndpoints {
public FitConnectEndpoints(string tokenUrl, string submissionApi, string routingApi) {
public class FitConnectEnvironments {
/// <summary>
/// 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 FitConnectEnvironments(string tokenUrl, string[] submissionApi, string routingApi) {
TokenUrl = tokenUrl;
SubmissionApi = submissionApi;
RoutingApi = routingApi;
}
public string TokenUrl { get; }
public string SubmissionApi { get; }
/// <summary>
/// URL for receiving the OAuth token.
/// </summary>
public string TokenUrl { get; }
/// <summary>
/// URL for the submission API.
/// </summary>
public string[] SubmissionApi { get; }
/// <summary>
/// URL for the routing API.
/// </summary>
public string RoutingApi { get; }
public enum EndpointType {
Development,
......@@ -22,46 +42,122 @@ public class FitConnectEndpoints {
Production
}
public static FitConnectEndpoints Create(EndpointType endpointType) {
/// <summary>
/// Creates the endpoints for the given environment.
/// </summary>
/// <param name="endpointType">Environment to get endpoints for</param>
/// <returns></returns>
/// <exception cref="ArgumentException">Not all environments are ready to use</exception>
public static FitConnectEnvironments Create(EndpointType endpointType) {
return endpointType switch {
EndpointType.Development => DevEndpoints,
EndpointType.Development => DevEnvironments,
EndpointType.Testing => throw new ArgumentException("Not approved for online testing"),
EndpointType.Production => throw new ArgumentException("NOT PRODUCTION READY"),
_ => 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 FitConnectEnvironments DevEnvironments = new(
"https://auth-testing.fit-connect.fitko.dev/token",
new []{"https://submission-api-testing.fit-connect.fitko.dev"},
"https://routing-api-testing.fit-connect.fitko.dev"
);
private static readonly FitConnectEndpoints TestEndpoints = new(
private static readonly FitConnectEnvironments TestEnvironments = new(
"https://auth-testing.fit-connect.fitko.dev/token",
"https://submission-api-testing.fit-connect.fitko.dev",
new []{"https://submission-api-testing.fit-connect.fitko.dev"},
"https://routing-api-testing.fit-connect.fitko.dev"
);
private static readonly FitConnectEndpoints ProductionEndpoints = new(
private static readonly FitConnectEnvironments ProductionEnvironments = new(
"https://auth-testing.fit-connect.fitko.dev/token",
"https://submission-api-testing.fit-connect.fitko.dev",
new []{"https://submission-api-testing.fit-connect.fitko.dev"},
"https://routing-api-testing.fit-connect.fitko.dev"
);
}
public class FunctionalBaseClass {
private readonly ILogger? logger;
public FitConnectEndpoints Endpoints { get; }
public abstract class FunctionalBaseClass {
protected readonly ILogger? logger;
private RSA _rsa;
public FitConnectEnvironments Environments { get; }
protected FunctionalBaseClass(ILogger? logger, FitConnectEndpoints? endpoints) {
Endpoints = endpoints ??
FitConnectEndpoints.Create(FitConnectEndpoints.EndpointType.Development);
protected FunctionalBaseClass(ILogger? logger, FitConnectEnvironments? endpoints) {
Environments = endpoints ??
FitConnectEnvironments.Create(FitConnectEnvironments.EndpointType.Development);
this.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, Environments.TokenUrl) {
Content = content,
Method = HttpMethod.Post
};
var response = await client.SendAsync(request);
return await response.Content.ReadFromJsonAsync<OAuthAccessToken>();
}
public Task<SecurityEventToken> GetSetDataAsync() {
throw new NotImplementedException();
}
public byte[] EncryptDataAsync(byte[] data,
byte[]? publicKey,
string? password,
int numberOfIterations) {
_rsa = RSA.Create(2048);
// _rsa.ImportRSAPublicKey(publicKey, out var read);
logger?.LogInformation(
"Encrypting data with public key: {}",
Convert.ToBase64String(_rsa.ExportRSAPublicKey()));
// var keyParams = new PbeParameters(
// PbeEncryptionAlgorithm.Aes256Cbc, HashAlgorithmName.SHA256, numberOfIterations);
//
// var privateKey =
// rsa.ExportEncryptedPkcs8PrivateKey(Encoding.UTF8.GetBytes(password), keyParams);
//
// logger?.LogInformation(
// "Private key: {}", privateKey);
return _rsa.Encrypt(data, RSAEncryptionPadding.OaepSHA256);
}
public byte[] DecryptDataAsync(byte[] data, byte[] privateKey) {
return _rsa.Decrypt(data, RSAEncryptionPadding.OaepSHA256);
}
private async Task<T?> RestCall<T>(Uri uri, HttpMethod method, string body) {
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Accept", "application/json");
......
using System.Threading.Tasks;
using FitConnect.Models;
namespace FitConnect;
public interface IBaseFunctionality {
Task<OAuthAccessToken> GetTokenAsync(string clientId, string clientSecret,
string? scope = null);
Task<SecurityEventToken> GetSetDataAsync();
// Receive SecurityEventToken and check signature
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using FitConnect.Models;
namespace FitConnect;
public interface IFluentSender {
/// <summary>
/// Configures the client for the given destination and loads the public key
/// </summary>
/// <param name="destinationId">unique identifier of the clients destination</param>
/// <returns>the upload step for attachments</returns>
public IFluentSenderWithDestination WithDestination(string destinationId);
public IFluentSenderWithData WithData(string data);
/// <summary>
/// Send submission to FIT-Connect API.
/// </summary>
/// <returns>submitted submission</returns>
public Submission Submit();
}
public interface IFluentSenderWithDestination {
/// <summary>
/// Sends the submission with a list of attachments
/// </summary>
/// <param name="attachments">that are sent with the submission</param>
/// <returns>the step where additional data can be added to the submission</returns>
public IFluentSenderWithAttachments WithAttachments(IEnumerable<Attachment> attachments);
}
public interface IFluentSenderWithAttachments : IFluentSenderReady {
/// <summary>
/// Data as string.
/// </summary>
/// <param name="data">json or xml as string</param>
/// <returns>next step to submit the data</returns>
public IFluentSenderWithData WithData(string data);
}
public interface IFluentSenderWithData : IFluentSenderReady {
}
public interface IFluentSenderReady {
/// <summary>
/// Send submission to FIT-Connect API.
/// </summary>
/// <returns>submitted submission</returns>
public IFluentSender Submit();
}
public interface IFluentSubscriber {
/// <summary>
/// Loads a list of available Submissions that were submitted to the subscriber.
/// </summary>
/// <returns>List of available submissions for pickup</returns>
public IEnumerable<string> GetAvailableSubmissions(string? destinationId = null);
/// <summary>
/// Loads a single Submission by id.
/// </summary>
/// <param name="submissionId">unique identifier of a <see cref="Submission"/></param>
/// <returns></returns>
public IFluentSubscriberWithSubmission RequestSubmission(string submissionId);
}
public interface IFluentSubscriberWithSubmission {
public Submission Submission { get; }
/// <summary>
/// Loads the <see cref="Attachment"/>s for the given <see cref="Submission"/>.
/// </summary>
/// <param name="canSubmitSubmission">Function that returns a boolean if the <see cref="Submission"/> can be confirmed</param>
/// <returns></returns>
public IFluentSenderWithAttachments GetAttachments(
Func<IEnumerable<Attachment>, bool> canSubmitSubmission);
}
using System.Threading.Tasks;
namespace FitConnect;
public interface ISender : IBaseFunctionality {
// Check public keys
Task<bool> CheckPublicKeyAsync(string publicKey);
// Encrypt Data (Fachdaten)
byte[] EncryptDataAsync(string data, string publicKey);
// Encrypt attachments (Anhänge)
Task<string> EncryptAttachmentAsync(string attachment, string publicKey);
// Create Metadata incl. Hash
Task<string> CreateMetadataAsync(string data, string attachment, string publicKey);
}
\ No newline at end of file
using System.Threading.Tasks;
using FitConnect.Models;
namespace FitConnect;
public interface ISubscriber : IBaseFunctionality {
/// <summary>
/// Decrypt Data (Fachdaten)
/// </summary>
/// <param name="data">(Fachdaten)</param>
/// <param name="privateKey">Your private key for decryption</param>
/// <returns></returns>
Task<string> DecryptDataAsync(string data, string privateKey);
// Decrypt attachments (Anhänge)
Task<string> DecryptAttachmentAsync(string attachment, string privateKey);
/// <summary>
/// Checks the validity of the given metadata against the schema.
/// </summary>
/// <param name="jsonMetaData">JSON meta data</param>
/// <returns></returns>
Task<bool> CheckMetadataAsync(string jsonMetaData);
// Check Hash from Metadata
Task<bool> CheckHashAsync(string metadata);
// Create SecurityEventToken and signature
Task<SecurityEventToken> CreateSecurityEventTokenAsync(string data, string attachment,
string privateKey);
}
\ No newline at end of file
namespace FitConnect.Models;
public record Area(string Id, string Name, string Type);
namespace FitConnect.Models;
public record Attachment(string Id, byte[] Content, string Hash, string Filename);
namespace FitConnect.Models;
public record Callback(string? Url, string? Secret) {
}
using System;
namespace FitConnect.Models;
/// <summary>
/// Representation of FitConnect error responses
/// </summary>
public class FitConnectException : Exception {
public enum ErrorTypeEnum {
Unknown
}
public FitConnectException(string message, ErrorTypeEnum errorType = ErrorTypeEnum.Unknown,
Exception? innerException = null) : base(message, innerException) {
ErrorType = errorType;
}
public ErrorTypeEnum ErrorType { get; set; }
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment