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

Merge branch 'feature/563-check-callback' into 'main'

Feature/563 check callback

See merge request !4
parents b07bf9fe 26d26e84
No related branches found
No related tags found
1 merge request!4Feature/563 check callback
...@@ -20,7 +20,6 @@ private_notes/ ...@@ -20,7 +20,6 @@ private_notes/
**.nupkg **.nupkg
### JetBrains template ### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
......
...@@ -9,7 +9,7 @@ using NUnit.Framework; ...@@ -9,7 +9,7 @@ using NUnit.Framework;
namespace FluentApiTest; namespace FluentApiTest;
public class FluentSenderTests { public class SenderTests {
private IContainer _container = null!; private IContainer _container = null!;
protected string clientId = null!; protected string clientId = null!;
protected string clientSecret = null!; protected string clientSecret = null!;
......
...@@ -7,7 +7,7 @@ using NUnit.Framework; ...@@ -7,7 +7,7 @@ using NUnit.Framework;
namespace FluentApiTest; namespace FluentApiTest;
public class FluentSubscriberReceiveTests { public class SubscriberReceiveTests {
private readonly string clientId = "clientId"; private readonly string clientId = "clientId";
private readonly string clientSecret = "clientSecret"; private readonly string clientSecret = "clientSecret";
private IContainer _container = null!; private IContainer _container = null!;
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
<PackageReference Include="Autofac" Version="6.4.0" /> <PackageReference Include="Autofac" Version="6.4.0" />
<PackageReference Include="IdentityModel" Version="6.0.0" /> <PackageReference Include="IdentityModel" Version="6.0.0" />
<PackageReference Include="jose-jwt" Version="4.0.0" /> <PackageReference Include="jose-jwt" Version="4.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" /> <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.1" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.21.0" /> <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.21.0" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.21.0" /> <PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.21.0" />
......
using System.Net;
using System.Reflection; using System.Reflection;
using System.Security.Cryptography;
using System.Text;
using Autofac; using Autofac;
using Autofac.Core.Activators.Reflection;
using FitConnect.Encryption; using FitConnect.Encryption;
using FitConnect.Interfaces.Subscriber; using FitConnect.Interfaces.Subscriber;
using FitConnect.Models; using FitConnect.Models;
using FitConnect.Models.v1.Api; using FitConnect.Models.v1.Api;
using FitConnect.Services.Models.v1.Submission; using FitConnect.Services.Models.v1.Submission;
using IdentityModel;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json; using Newtonsoft.Json;
using NJsonSchema; using NJsonSchema;
...@@ -199,6 +205,38 @@ public class Subscriber : FitConnectClient, ...@@ -199,6 +205,38 @@ public class Subscriber : FitConnectClient,
var result = CasesService.FinishSubmission(submission.CaseId, token); var result = CasesService.FinishSubmission(submission.CaseId, token);
Logger?.LogInformation("Submission completed {status}", result); Logger?.LogInformation("Submission completed {status}", result);
} }
public static string VerifyCallback(string callbackSecret,
long timestamp, string body) {
if (timestamp < DateTime.Now.AddMinutes(-5).ToEpochTime())
throw new ArgumentException("Request is too old");
var hmac = new HMACSHA512(Encoding.UTF8.GetBytes(callbackSecret))
.ComputeHash(Encoding.UTF8.GetBytes($"{timestamp}.{body}"));
return Convert.ToHexString(hmac).ToLower();
}
public static bool VerifyCallback(string callbackSecret, HttpRequest request) {
if (!request.Headers.ContainsKey("callback-timestamp"))
throw new ArgumentException("Missing callback-timestamp header");
var timeStampString = request.Headers["callback-timestamp"].ToString();
if (!long.TryParse(timeStampString, out var timestamp)) {
throw new ArgumentException("Invalid callback-timestamp header");
}
var authentication = request.Headers["callback-authentication"];
using var requestStream = request.Body;
var content = new StreamReader(requestStream).ReadToEnd().Trim();
var result = VerifyCallback(callbackSecret, timestamp, content);
if (result != authentication)
throw new ArgumentException("Verified request does not match authentication");
return true;
}
} }
public enum FinishSubmissionStatus { public enum FinishSubmissionStatus {
......
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using Autofac;
using FluentAssertions;
using IdentityModel;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using MockContainer;
using Moq;
using NUnit.Framework;
namespace IntegrationTests;
[TestFixture]
public class CallbackTest {
private HttpRequest _request = null!;
private string _callbackSecret = "";
[SetUp]
public void Setup() {
var memoryStream = new MemoryStream();
var streamWriter = new StreamWriter(memoryStream);
streamWriter.WriteLine(
"{\"type\":\"https://schema.fitko.de/fit-connect/submission-api/callbacks/new-submissions\",\"submissionIds\":[\"f39ab143-d91a-474a-b69f-b00f1a1873c2\"]}");
streamWriter.Flush();
memoryStream.Position = 0;
var headers = new HeaderDictionary(new Dictionary<string, StringValues>() {
{ "callback-timestamp", "1672527599" }, {
"callback-authentication",
"798cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df"
}
});
var mock =
new Mock<HttpRequest>();
mock.Setup(w => w.ContentType).Returns("application/json");
mock.Setup(w => w.Headers).Returns(headers);
mock.Setup(w => w.Method).Returns("POST");
mock.Setup(w => w.Body).Returns(memoryStream);
_request = mock.Object;
_callbackSecret = MockContainer.Container.Create().Resolve<MockSettings>().CallbackSecret;
}
[Test]
public void ValidRequest_WithSingeValues() {
// Arrange
//Act
var authentication = FitConnect.Subscriber.VerifyCallback(_callbackSecret, 1672527599,
"{\"type\":\"https://schema.fitko.de/fit-connect/submission-api/callbacks/new-submissions\",\"submissionIds\":[\"f39ab143-d91a-474a-b69f-b00f1a1873c2\"]}"
);
// Assert
authentication.Should()
.Be(
"798cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df");
}
[Test]
public void ValidRequest() {
// Assert
FitConnect.Subscriber.VerifyCallback(_callbackSecret, _request).Should().Be(true);
}
[Test]
public void RequestAge_Fails() {
// Arrange
_request.Headers["callback-timestamp"] = "1641066653";
// Atc
// Assert
Assert.Throws<ArgumentException>(() => {
FitConnect.Subscriber.VerifyCallback(_callbackSecret, _request);
})
.Message.Should().Be("Request is too old");
}
[Test]
public void RequestAuthentication_Fails() {
// Arrange
_request.Headers["callback-authentication"] =
"898cd0edb70c08e5b32aa8a18cbbc8ff6b3078c51af6d011ff4e32e470c746234fc4314821fe5185264b029e962bd37de33f3b9fc5f1a93c40ce6672845e90df";
// Atc
// Assert
Assert.Throws<ArgumentException>(() => {
FitConnect.Subscriber.VerifyCallback(_callbackSecret, _request);
})
.Message.Should().Be("Request is not authentic");
}
}
...@@ -10,6 +10,8 @@ ...@@ -10,6 +10,8 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DotNet.Testcontainers" Version="1.6.0" /> <PackageReference Include="DotNet.Testcontainers" Version="1.6.0" />
<PackageReference Include="FluentAssertions" Version="6.7.0" /> <PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" /> <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="6.0.0" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.21.0" /> <PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="6.21.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.11.0" />
......
...@@ -22,7 +22,7 @@ namespace MockContainer; ...@@ -22,7 +22,7 @@ namespace MockContainer;
public record MockSettings(string PrivateKeyDecryption, string PrivateKeySigning, public record MockSettings(string PrivateKeyDecryption, string PrivateKeySigning,
string PublicKeyEncryption, string PublicKeySignatureVerification, string SenderClientId, string PublicKeyEncryption, string PublicKeySignatureVerification, string SenderClientId,
string SenderClientSecret, string SubscriberClientId, string SubscriberClientSecret, string SenderClientSecret, string SubscriberClientId, string SubscriberClientSecret,
string DestinationId, string LeikaKey); string DestinationId, string LeikaKey, string CallbackSecret);
public class TestFile { public class TestFile {
public byte[] Content; public byte[] Content;
...@@ -80,13 +80,14 @@ public static class Container { ...@@ -80,13 +80,14 @@ public static class Container {
var subscriberClientSecret = (string)credentials.subscriber.clientSecret; var subscriberClientSecret = (string)credentials.subscriber.clientSecret;
var destinationId = (string)credentials.destinationId; var destinationId = (string)credentials.destinationId;
var leikaKey = (string)credentials.leikaKey; var leikaKey = (string)credentials.leikaKey;
var callbackSecret = (string)credentials.callbackSecret;
builder.Register(c => new MockSettings( builder.Register(c => new MockSettings(
privateKeyDecryption, privateKeySigning, privateKeyDecryption, privateKeySigning,
publicKeyEncryption, publicKeySignature, publicKeyEncryption, publicKeySignature,
senderClientId, senderClientSecret, senderClientId, senderClientSecret,
subscriberClientId, subscriberClientSecret, subscriberClientId, subscriberClientSecret,
destinationId, leikaKey)) destinationId, leikaKey, callbackSecret))
.As<MockSettings>(); .As<MockSettings>();
builder.Register(c => new KeySet { builder.Register(c => new KeySet {
PrivateKeyDecryption = privateKeyDecryption, PrivateKeyDecryption = privateKeyDecryption,
......
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