Skip to content

Commit b08ebb8

Browse files
authored
Merge pull request #98 from mdsol/develop
Merge develop into master
2 parents 459e55d + f752c0d commit b08ebb8

File tree

17 files changed

+311
-33
lines changed

17 files changed

+311
-33
lines changed
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Build and Test (.NET 5.0)
1+
name: Build and Test (.NET 6.0)
22
on: [push]
33
jobs:
44
Test:
@@ -9,6 +9,6 @@ jobs:
99
with:
1010
submodules: 'recursive'
1111
- name: Run the Core tests
12-
run: dotnet test $GITHUB_WORKSPACE/tests/Medidata.MAuth.CoreTests --framework net5.0
12+
run: dotnet test $GITHUB_WORKSPACE/tests/Medidata.MAuth.CoreTests --framework net6.0
1313
- name: Run the ASP.NET Core tests
14-
run: dotnet test $GITHUB_WORKSPACE/tests/Medidata.MAuth.AspNetCoreTests --framework net5.0
14+
run: dotnet test $GITHUB_WORKSPACE/tests/Medidata.MAuth.AspNetCoreTests --framework net6.0

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
# Changes in Medidata.MAuth
2+
## v5.1.6
3+
- **[Core]** Fix bug in MAuth verification.
4+
25
## v5.1.5
36
- **[Core]** Fix bug in MAuth caching response
47
## v5.1.4
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace Medidata.MAuth.Core
4+
{
5+
internal class DateTimeOffsetWrapper : IDateTimeOffsetWrapper
6+
{
7+
/// <summary>
8+
/// A facade around DatetimeOffset.UtcNow
9+
/// </summary>
10+
/// <returns>
11+
/// The value of DateTimeOffset.UtcNow
12+
/// </returns>
13+
public DateTimeOffset GetUtcNow() => DateTimeOffset.UtcNow;
14+
}
15+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
using System;
2+
3+
namespace Medidata.MAuth.Core;
4+
5+
/// <summary>
6+
/// A facade for <see cref="DateTimeOffset" />
7+
/// </summary>
8+
public interface IDateTimeOffsetWrapper
9+
{
10+
/// <summary>
11+
/// A facade for the UtcNow property.
12+
/// </summary>
13+
/// <returns>A <see cref="DateTimeOffset" /> value</returns>
14+
DateTimeOffset GetUtcNow();
15+
}

src/Medidata.MAuth.Core/MAuthAuthenticator.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@ namespace Medidata.MAuth.Core
1313
{
1414
internal class MAuthAuthenticator
1515
{
16+
private const int AllowedDriftSeconds = 300;
17+
private static readonly TimeSpan AllowedDriftTimeSpan = TimeSpan.FromSeconds(AllowedDriftSeconds);
18+
1619
private readonly ICacheService _cache;
1720
private readonly MAuthOptionsBase _options;
1821
private readonly ILogger _logger;
22+
private readonly IDateTimeOffsetWrapper _dateTimeOffsetWrapper;
1923
private readonly Lazy<HttpClient> _lazyHttpClient;
2024

2125
public Guid ApplicationUuid => _options.ApplicationUuid;
@@ -35,6 +39,7 @@ public MAuthAuthenticator(MAuthOptionsBase options, ILogger logger, ICacheServic
3539
_options = options;
3640
_logger = logger;
3741
_lazyHttpClient = new Lazy<HttpClient>(() => CreateHttpClient(options));
42+
_dateTimeOffsetWrapper = options.DateTimeOffsetWrapper;
3843
}
3944

4045
/// <summary>
@@ -104,15 +109,38 @@ private async Task<bool> Authenticate(HttpRequestMessage request, MAuthVersion v
104109

105110
var mAuthCore = MAuthCoreFactory.Instantiate(version);
106111
var authInfo = GetAuthenticationInfo(request, mAuthCore);
107-
112+
113+
if (!IsSignatureTimeValid(authInfo.SignedTime))
114+
{
115+
return false;
116+
}
117+
108118
var appInfo = await _cache.GetOrCreateWithLock(
109119
authInfo.ApplicationUuid.ToString(),
110120
() => SendApplicationInfoRequest(authInfo.ApplicationUuid)).ConfigureAwait(false);
111121

112122
var signature = await mAuthCore.GetSignature(request, authInfo).ConfigureAwait(false);
113123
return mAuthCore.Verify(authInfo.Payload, signature, appInfo.PublicKey);
124+
}
125+
126+
private bool IsSignatureTimeValid(DateTimeOffset signedTime)
127+
{
128+
var now = _dateTimeOffsetWrapper.GetUtcNow();
129+
var lowerBound = now - AllowedDriftTimeSpan;
130+
var upperBound = now + AllowedDriftTimeSpan;
131+
var isValid = signedTime >= lowerBound && signedTime <= upperBound;
114132

133+
if (!isValid)
134+
{
135+
_logger.LogInformation(
136+
"Time verification failed. {signedTime} is not within {AllowedDriftSeconds} seconds of #{now}",
137+
signedTime,
138+
AllowedDriftSeconds,
139+
now);
115140
}
141+
142+
return isValid;
143+
}
116144

117145
private async Task<CacheResult<ApplicationInfo>> SendApplicationInfoRequest(Guid applicationUuid)
118146
{

src/Medidata.MAuth.Core/Medidata.MAuth.Core.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<PropertyGroup>
44
<Description>A core package for Medidata HMAC protocol implementation. This package contains the core functionality which used by the MAuth authentication protocol-specific components. This package also can be used standalone if you want to sign HTTP/HTTPS requests with Medidata MAuth keys using the .NET HttpClient message handler mechanism.</Description>
55
<AssemblyTitle>Medidata.MAuth.Core</AssemblyTitle>
6-
<TargetFrameworks>netstandard2.0;net5.0</TargetFrameworks>
6+
<TargetFrameworks>netstandard2.0;net6.0</TargetFrameworks>
77
<AssemblyName>Medidata.MAuth.Core</AssemblyName>
88
<PackageTags>medidata;mauth;hmac;authentication;core;httpclient;messagehandler</PackageTags>
99
</PropertyGroup>
@@ -12,11 +12,11 @@
1212
<PackageReference Include="ConfigureAwaitChecker.Analyzer" Version="5.0.0">
1313
<PrivateAssets>all</PrivateAssets>
1414
</PackageReference>
15-
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
16-
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
15+
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.9" />
16+
<PackageReference Include="Microsoft.Extensions.Logging" Version="6.0.0" />
1717
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
1818
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
19-
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="5.0.0" />
19+
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="6.0.1" />
2020
<PackageReference Include="Portable.BouncyCastle" Version="1.8.10" />
2121
</ItemGroup>
2222
</Project>

src/Medidata.MAuth.Core/Options/MAuthOptionsBase.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,5 +50,10 @@ public string PrivateKey
5050
/// Determines the boolean value if V1 option of signing should be disabled or not with default value of false.
5151
/// </summary>
5252
public bool DisableV1 { get; set; } = false;
53+
54+
/// <summary>
55+
/// Allow injection of a DateTimeOffset wrapper for testing purposes.
56+
/// </summary>
57+
public IDateTimeOffsetWrapper DateTimeOffsetWrapper { get; set; } = new DateTimeOffsetWrapper();
5358
}
5459
}

tests/Medidata.MAuth.AspNetCoreTests/MAuthAspNetCoreTests.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Threading.Tasks;
55
using Medidata.MAuth.AspNetCore;
66
using Medidata.MAuth.Core;
7+
using Medidata.MAuth.Tests.Common.Infrastructure;
78
using Medidata.MAuth.Tests.Infrastructure;
89
using Microsoft.AspNetCore.Builder;
910
using Microsoft.AspNetCore.Hosting;
@@ -23,6 +24,7 @@ public async Task MAuthMiddleware_WithValidRequest_WillAuthenticate(string metho
2324
{
2425
// Arrange
2526
var testData = await method.FromResourceV2();
27+
var mockDateTimeOffsetWrapper = new MockDateTimeOffsetWrapper { MockedValue = testData.SignedTime };
2628
var serverHandler = await MAuthServerHandler.CreateAsync();
2729

2830
using (var server = new TestServer(new WebHostBuilder().Configure(app =>
@@ -34,6 +36,7 @@ public async Task MAuthMiddleware_WithValidRequest_WillAuthenticate(string metho
3436
options.PrivateKey = TestExtensions.ServerPrivateKey;
3537
options.MAuthServerHandler = serverHandler;
3638
options.HideExceptionsAndReturnUnauthorized = false;
39+
options.DateTimeOffsetWrapper = mockDateTimeOffsetWrapper;
3740
});
3841

3942
app.Run(async context => await new StreamWriter(context.Response.Body).WriteAsync("Done."));
@@ -56,6 +59,7 @@ public async Task MAuthMiddleware_WithoutMAuthHeader_WillNotAuthenticate(string
5659
{
5760
// Arrange
5861
var testData = await method.FromResource();
62+
var mockDateTimeOffsetWrapper = new MockDateTimeOffsetWrapper { MockedValue = testData.SignedTime };
5963
var serverHandler = await MAuthServerHandler.CreateAsync();
6064

6165
using (var server = new TestServer(new WebHostBuilder().Configure(app =>
@@ -66,6 +70,7 @@ public async Task MAuthMiddleware_WithoutMAuthHeader_WillNotAuthenticate(string
6670
options.MAuthServiceUrl = TestExtensions.TestUri;
6771
options.PrivateKey = TestExtensions.ServerPrivateKey;
6872
options.MAuthServerHandler = serverHandler;
73+
options.DateTimeOffsetWrapper = mockDateTimeOffsetWrapper;
6974
});
7075

7176
app.Run(async context => await new StreamWriter(context.Response.Body).WriteAsync("Done."));
@@ -89,6 +94,7 @@ public async Task MAuthMiddleware_WithEnabledExceptions_WillThrowException(strin
8994
{
9095
// Arrange
9196
var testData = await method.FromResource();
97+
var mockDateTimeOffsetWrapper = new MockDateTimeOffsetWrapper { MockedValue = testData.SignedTime };
9298
var serverHandler = await MAuthServerHandler.CreateAsync();
9399

94100
using (var server = new TestServer(new WebHostBuilder().Configure(app =>
@@ -100,6 +106,7 @@ public async Task MAuthMiddleware_WithEnabledExceptions_WillThrowException(strin
100106
options.PrivateKey = TestExtensions.ServerPrivateKey;
101107
options.MAuthServerHandler = serverHandler;
102108
options.HideExceptionsAndReturnUnauthorized = false;
109+
options.DateTimeOffsetWrapper = mockDateTimeOffsetWrapper;
103110
});
104111
})))
105112
{
@@ -122,6 +129,7 @@ public async Task MAuthMiddleware_WithNonSeekableBodyStream_WillRestoreBodyStrea
122129
{
123130
// Arrange
124131
var testData = await method.FromResourceV2();
132+
var mockDateTimeOffsetWrapper = new MockDateTimeOffsetWrapper { MockedValue = testData.SignedTime };
125133
var canSeek = false;
126134
var body = string.Empty;
127135
var serverHandler = await MAuthServerHandler.CreateAsync();
@@ -136,6 +144,7 @@ public async Task MAuthMiddleware_WithNonSeekableBodyStream_WillRestoreBodyStrea
136144
options.MAuthServiceUrl = TestExtensions.TestUri;
137145
options.PrivateKey = TestExtensions.ServerPrivateKey;
138146
options.MAuthServerHandler = serverHandler;
147+
options.DateTimeOffsetWrapper = mockDateTimeOffsetWrapper;
139148
})
140149
.Run(async context =>
141150
{

tests/Medidata.MAuth.AspNetCoreTests/Medidata.MAuth.AspNetCoreTests.csproj

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

33
<PropertyGroup>
4-
<TargetFrameworks>net5.0</TargetFrameworks>
4+
<TargetFrameworks>net6.0</TargetFrameworks>
55
<Description>Unit tests for the Medidata.MAuth.AspNetCore package.</Description>
66
</PropertyGroup>
77

88
<ItemGroup>
9-
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="5.0.6" />
10-
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.2.0" />
11-
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
9+
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="6.0.15" />
1210
</ItemGroup>
1311

1412
<ItemGroup>

0 commit comments

Comments
 (0)