Skip to content

Commit 512544b

Browse files
author
Herry Kurniawan
authored
Merge pull request #61 from mdsol/develop
Release of v4.0.2
2 parents 22b3028 + aa23188 commit 512544b

File tree

10 files changed

+112
-53
lines changed

10 files changed

+112
-53
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changes in Medidata.MAuth
22

3+
## v4.0.2
4+
- **[AspNetCore]** Update aspnetcore version to aspnetcore2.1 LTS.
5+
- **[Core]** Fallback to V1 protocol when V2 athentication fails.
6+
37
## v4.0.1
48
- **[Core]** Fixed default sigining with both MWS and MWSV2 instead of option selected by consuming application.
59
- **[Core]** Fixed an issue related to token request path which is same for both MWS and MWSV2 protocol.

README.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,8 @@ public async Task<HttpResponseMessage> SignAndSendRequest(HttpRequestMessage req
125125
}
126126
}
127127
```
128-
With the MAuth V2 protocol, there are two new options `MAuthVersion` and `DisableV1` added for Signing MAuth request.
129-
`MAuthVersion` is passed either as `MAuthVersion.MWSV2` for signing with V2 protocol or `MAuthVersion.MWS` for continue
130-
signing with V1 protocol. By default, `DisableV1` option is set to false (if not included). When we are ready to
128+
With the release of support for MAuth V2 protocol, by default MAuth request signs with both V1 and V2 protocol.
129+
Also by default, `DisableV1` option is set to false (if not included). When we are ready to
131130
disable all the V1 request, then we need to include this disable option as : `DisableV1 = true`.
132131
Signing with V2 protocol supports query string.
133132

@@ -140,8 +139,7 @@ The `MAuthSigningOptions` has the following properties to determine the required
140139
| ---- | ----------- |
141140
| **ApplicationUuid** | Determines the unique identifier of the client application used for the MAuth service authentication requests. This uuid needs to be registered with the MAuth Server in order for the authenticating server application to be able to authenticate the signed request. |
142141
| **PrivateKey** | Determines the RSA private key of the client for signing a request. This key must be in a PEM ASN.1 format. The value of this property can be set as a valid path to a readable key file as well. |
143-
| **MAuthVersion** | Determines the MAuth version of the request used for signing. This is enumeration value which is `MAuthVersion.MWSV2` for V2 requests. |
144-
| **DisableV1** | Determines the boolean value which controls whether to disable the signing requests with `MAuthVersion.MWS` requests or not. If not supplied, this value is `false`. |
142+
| **DisableV1** | Determines the boolean value which controls whether to disable the signing requests with V1 protocol or not. If not supplied, this value is `false`. |
145143

146144
### Authenticating Incoming Requests with the OWIN and ASP.NET Core Middlewares
147145

@@ -319,7 +317,7 @@ in your project in order to make Medidata.MAuth work for you.
319317
##### Is there an .NET Standard/Core support?
320318

321319
Yes, for signing outgoing requests you can use the library with any framework which implements
322-
the **.NET Standard 2.0** and onwards; additionally we support the **ASP.NET Core App 2.0** and onwards with a middleware
320+
the **.NET Standard 2.0** and onwards; additionally we support the **ASP.NET Core App 2.1** and onwards with a middleware
323321
for authenticating the incoming requests.
324322

325323
##### What Cryptographic provider is used for the encryption/decryption?

src/Medidata.MAuth.AspNetCore/Medidata.MAuth.AspNetCore.csproj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<Import Project="..\..\build\common.props" />
33

44
<PropertyGroup>
5-
<TargetFramework>netcoreapp2.0</TargetFramework>
5+
<TargetFramework>netcoreapp2.1</TargetFramework>
66
<Description>This package contains an ASP.NET Core middleware to validate signed http requests with the Medidata MAuth protocol. The middleware communicates with an MAuth server in order to confirm the validity of the request authentication header. Include this package in your ASP.NET Core web api if you want to authenticate the api requests signed with the MAuth protocol.</Description>
77
<AssemblyTitle>Medidata.MAuth.AspNetCore</AssemblyTitle>
88
<AssemblyName>Medidata.MAuth.AspNetCore</AssemblyName>
@@ -14,9 +14,9 @@
1414
<PrivateAssets>all</PrivateAssets>
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
1616
</PackageReference>
17-
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
18-
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
19-
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
17+
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.1.1" />
18+
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
19+
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.1.1" />
2020
</ItemGroup>
2121

2222
<ItemGroup>

src/Medidata.MAuth.Core/MAuthAuthenticator.cs

Lines changed: 35 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,17 @@ public async Task<bool> AuthenticateRequest(HttpRequestMessage request)
4545
logger.LogInformation("Initiating Authentication of the request.");
4646
var version = request.GetAuthHeaderValue().GetVersionFromAuthenticationHeader();
4747

48-
logger.LogInformation("Authentication is for the {version}.",version);
49-
5048
if (options.DisableV1 && version == MAuthVersion.MWS)
5149
throw new InvalidVersionException($"Authentication with {version} version is disabled.");
5250

53-
var mAuthCore = MAuthCoreFactory.Instantiate(version);
54-
var authInfo = GetAuthenticationInfo(request, version);
55-
var appInfo = await GetApplicationInfo(authInfo.ApplicationUuid, version).ConfigureAwait(false);
56-
var signature = await mAuthCore.GetSignature(request, authInfo).ConfigureAwait(false);
57-
58-
return mAuthCore.Verify(authInfo.Payload, signature, appInfo.PublicKey);
51+
var authenticated = await Authenticate(request, version).ConfigureAwait(false);
52+
if (!authenticated && version == MAuthVersion.MWSV2 && !options.DisableV1)
53+
{
54+
// fall back to V1 authentication
55+
authenticated = await Authenticate(request, MAuthVersion.MWS).ConfigureAwait(false);
56+
logger.LogWarning("Completed successful authentication attempt after fallback to V1");
57+
}
58+
return authenticated;
5959
}
6060
catch (ArgumentException ex)
6161
{
@@ -89,10 +89,24 @@ public async Task<bool> AuthenticateRequest(HttpRequestMessage request)
8989
}
9090
}
9191

92-
private Task<ApplicationInfo> GetApplicationInfo(Guid applicationUuid, MAuthVersion version) =>
92+
private async Task<bool> Authenticate(HttpRequestMessage request, MAuthVersion version)
93+
{
94+
var logMessage = "Mauth-client attempting to authenticate request from app with mauth app uuid" +
95+
$" {options.ApplicationUuid} using version {version}";
96+
logger.LogInformation(logMessage);
97+
98+
var mAuthCore = MAuthCoreFactory.Instantiate(version);
99+
var authInfo = GetAuthenticationInfo(request, mAuthCore);
100+
var appInfo = await GetApplicationInfo(authInfo.ApplicationUuid).ConfigureAwait(false);
101+
102+
var signature = await mAuthCore.GetSignature(request, authInfo).ConfigureAwait(false);
103+
return mAuthCore.Verify(authInfo.Payload, signature, appInfo.PublicKey);
104+
}
105+
106+
private Task<ApplicationInfo> GetApplicationInfo(Guid applicationUuid) =>
93107
cache.GetOrCreateAsync(applicationUuid, async entry =>
94108
{
95-
var retrier = new MAuthRequestRetrier(options, version);
109+
var retrier = new MAuthRequestRetrier(options);
96110
var response = await retrier.GetSuccessfulResponse(
97111
applicationUuid,
98112
CreateRequest,
@@ -109,38 +123,41 @@ private Task<ApplicationInfo> GetApplicationInfo(Guid applicationUuid, MAuthVers
109123
return result;
110124
});
111125

112-
private HttpRequestMessage CreateRequest(Guid applicationUuid) =>
113-
new HttpRequestMessage(HttpMethod.Get, new Uri(options.MAuthServiceUrl,
114-
$"{Constants.MAuthTokenRequestPath}{applicationUuid.ToHyphenString()}.json"));
115-
116126
/// <summary>
117127
/// Extracts the authentication information from a <see cref="HttpRequestMessage"/>.
118128
/// </summary>
119129
/// <param name="request">The request that has the authentication information.</param>
120-
/// <param name="version">Enum value of the MAuthVersion.</param>
130+
/// <param name="mAuthCore">Instantiation of mAuthCore class.</param>
121131
/// <returns>The authentication information with the payload from the request.</returns>
122-
internal PayloadAuthenticationInfo GetAuthenticationInfo(HttpRequestMessage request, MAuthVersion version)
132+
internal static PayloadAuthenticationInfo GetAuthenticationInfo(HttpRequestMessage request, IMAuthCore mAuthCore)
123133
{
124-
var mAuthCore = MAuthCoreFactory.Instantiate(version);
125134
var headerKeys = mAuthCore.GetHeaderKeys();
126135
var authHeader = request.Headers.GetFirstValueOrDefault<string>(headerKeys.mAuthHeaderKey);
127136

128137
if (authHeader == null)
138+
{
129139
throw new ArgumentNullException(nameof(authHeader), "The MAuth header is missing from the request.");
140+
}
130141

131142
var signedTime = request.Headers.GetFirstValueOrDefault<long>(headerKeys.mAuthTimeHeaderKey);
132143

133144
if (signedTime == default(long))
145+
{
134146
throw new ArgumentException("Invalid MAuth signed time header value.", nameof(signedTime));
147+
}
135148

136149
var (uuid, payload) = authHeader.ParseAuthenticationHeader();
137150

138-
return new PayloadAuthenticationInfo()
151+
return new PayloadAuthenticationInfo
139152
{
140153
ApplicationUuid = uuid,
141154
Payload = Convert.FromBase64String(payload),
142155
SignedTime = signedTime.FromUnixTimeSeconds()
143156
};
144157
}
158+
159+
private HttpRequestMessage CreateRequest(Guid applicationUuid) =>
160+
new HttpRequestMessage(HttpMethod.Get, new Uri(options.MAuthServiceUrl,
161+
$"{Constants.MAuthTokenRequestPath}{applicationUuid.ToHyphenString()}.json"));
145162
}
146163
}

src/Medidata.MAuth.Core/MAuthRequestRetrier.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
using System;
22
using System.Net.Http;
33
using System.Threading.Tasks;
4-
using Medidata.MAuth.Core.Models;
54

65
namespace Medidata.MAuth.Core
76
{
87
internal class MAuthRequestRetrier
98
{
109
private readonly HttpClient client;
1110

12-
public MAuthRequestRetrier(MAuthOptionsBase options, MAuthVersion version)
11+
public MAuthRequestRetrier(MAuthOptionsBase options)
1312
{
1413
var signingHandler = new MAuthSigningHandler(options: new MAuthSigningOptions()
1514
{

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
1616
</PackageReference>
1717
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
18-
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.2.0" />
19-
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="2.2.0" />
20-
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="2.2.0" />
18+
<PackageReference Include="Microsoft.Extensions.Logging" Version="2.1.1" />
2119
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
2220
<PackageReference Include="System.ValueTuple" Version="4.3.1" />
2321
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="1.1.2" />

tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
using System.Threading;
55
using System.Threading.Tasks;
66
using Medidata.MAuth.Core;
7-
using Medidata.MAuth.Core.Models;
87
using Microsoft.Extensions.Logging.Abstractions;
98
using Newtonsoft.Json;
109

@@ -26,10 +25,7 @@ protected override async Task<HttpResponseMessage> SendAsync(
2625

2726
if (currentNumberOfAttempts < SucceedAfterThisManyAttempts)
2827
return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
29-
30-
var authenticator = new MAuthAuthenticator(TestExtensions.ServerOptions, NullLogger<MAuthAuthenticator>.Instance);
31-
32-
var authInfo = authenticator.GetAuthenticationInfo(request, version);
28+
var authInfo = MAuthAuthenticator.GetAuthenticationInfo(request, mAuthCore);
3329

3430
if (!mAuthCore.Verify(authInfo.Payload,
3531
await mAuthCore.GetSignature(request, authInfo),

tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
using Medidata.MAuth.Core.Exceptions;
77
using Medidata.MAuth.Core.Models;
88
using Medidata.MAuth.Tests.Infrastructure;
9+
using Microsoft.Extensions.Logging;
910
using Microsoft.Extensions.Logging.Abstractions;
11+
using Microsoft.Extensions.Logging.Internal;
12+
using Moq;
1013
using Xunit;
1114

1215
namespace Medidata.MAuth.Tests
@@ -41,11 +44,6 @@ public static async Task AuthenticateRequest_WithValidMWSRequest_WillAuthenticat
4144
{
4245
// Arrange
4346
var testData = await method.FromResource();
44-
45-
//var testOptions = TestExtensions.ServerOptions;
46-
//testOptions.MAuthServerHandler = new MAuthServerHandler()
47-
// { AuthenticateOnlyV1 = true };
48-
4947
var authenticator = new MAuthAuthenticator(TestExtensions.ServerOptions, NullLogger<MAuthAuthenticator>.Instance);
5048
var mAuthCore = new MAuthCore();
5149

@@ -313,10 +311,10 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSV2Version
313311
var testData = await method.FromResourceV2();
314312
var version = MAuthVersion.MWSV2;
315313
var testOptions = TestExtensions.ServerOptions;
316-
var authenticator = new MAuthAuthenticator(testOptions, NullLogger<MAuthAuthenticator>.Instance);
314+
var mAuthCore = MAuthCoreFactory.Instantiate(version);
317315

318316
// Act
319-
var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), version);
317+
var actual = MAuthAuthenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore);
320318

321319
// Assert
322320
Assert.Equal(testData.ApplicationUuid, actual.ApplicationUuid);
@@ -335,15 +333,63 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSVersion_W
335333
var testData = await method.FromResource();
336334
var version = MAuthVersion.MWS;
337335
var testOptions = TestExtensions.ServerOptions;
338-
var authenticator = new MAuthAuthenticator(testOptions, NullLogger<MAuthAuthenticator>.Instance);
336+
var mAuthCore = MAuthCoreFactory.Instantiate(version);
339337

340338
// Act
341-
var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), version);
339+
var actual = MAuthAuthenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore);
342340

343341
// Assert
344342
Assert.Equal(testData.ApplicationUuid, actual.ApplicationUuid);
345343
Assert.Equal(Convert.FromBase64String(testData.Payload), actual.Payload);
346344
Assert.Equal(testData.SignedTime, actual.SignedTime);
347345
}
346+
347+
[Fact]
348+
public static async Task AuthenticateRequest_WithDefaultRequest_WhenV2Fails_FallBackToV1AndAuthenticate()
349+
{
350+
// Arrange
351+
var testData = await "GET".FromResource();
352+
var mockLogger = new Mock<ILogger>();
353+
354+
var authenticator = new MAuthAuthenticator(TestExtensions.ServerOptions, mockLogger.Object);
355+
var requestData = testData.ToDefaultHttpRequestMessage();
356+
357+
// Act
358+
var isAuthenticated = await authenticator.AuthenticateRequest(requestData);
359+
360+
// Assert
361+
Assert.True(isAuthenticated);
362+
mockLogger.Verify(x => x.Log(
363+
LogLevel.Warning, It.IsAny<EventId>(),
364+
It.Is<FormattedLogValues>(v => v.ToString()
365+
.Contains("Completed successful authentication attempt after fallback to V1")),
366+
It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()
367+
));
368+
}
369+
370+
[Fact]
371+
public static async Task AuthenticateRequest_WithDefaultRequest_AndDisableV1_WhenV2Fails_NotFallBackToV1()
372+
{
373+
// Arrange
374+
var testData = await "GET".FromResource();
375+
var mockLogger = new Mock<ILogger>();
376+
var testOptions = TestExtensions.ServerOptions;
377+
testOptions.DisableV1 = true;
378+
379+
var authenticator = new MAuthAuthenticator(testOptions, mockLogger.Object);
380+
var requestData = testData.ToDefaultHttpRequestMessage();
381+
382+
// Act
383+
var isAuthenticated = await authenticator.AuthenticateRequest(requestData);
384+
385+
// Assert
386+
Assert.False(isAuthenticated);
387+
mockLogger.Verify(x => x.Log(
388+
LogLevel.Warning, It.IsAny<EventId>(),
389+
It.Is<FormattedLogValues>(v => v.ToString()
390+
.Contains("Completed successful authentication attempt after fallback to V1")),
391+
It.IsAny<Exception>(), It.IsAny<Func<object, Exception, string>>()
392+
), Times.Never);
393+
}
348394
}
349395
}

tests/Medidata.MAuth.Tests/Medidata.MAuth.Tests.csproj

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<Copyright>Copyright © Medidata Solutions, Inc. 2017</Copyright>
66
<AssemblyTitle>Medidata.MAuth.Tests</AssemblyTitle>
77
<Authors>Medidata Solutions, Inc.</Authors>
8-
<TargetFrameworks>net461;netcoreapp2.0</TargetFrameworks>
8+
<TargetFrameworks>net461;netcoreapp2.1</TargetFrameworks>
99
<AssemblyName>Medidata.MAuth.Tests</AssemblyName>
1010
<PackageId>Medidata.MAuth.Tests</PackageId>
1111
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
@@ -60,17 +60,18 @@
6060
<Compile Remove="MAuthAspNetCoreTests.cs" />
6161
</ItemGroup>
6262

63-
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.0' ">
63+
<ItemGroup Condition=" '$(TargetFramework)' == 'netcoreapp2.1' ">
6464
<ProjectReference Include="..\..\src\Medidata.MAuth.AspNetCore\Medidata.MAuth.AspNetCore.csproj" />
65-
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="1.1.2" />
66-
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.2" />
65+
<PackageReference Include="Microsoft.AspNetCore.TestHost" Version="2.1.1" />
66+
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="2.1.1" />
6767
<Compile Remove="MAuthOwinTests.cs" />
6868
<Compile Remove="MAuthWebApiTests.cs" />
6969
</ItemGroup>
7070

7171
<ItemGroup>
72-
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
72+
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.1.1" />
7373
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" />
74+
<PackageReference Include="Moq" Version="4.13.1" />
7475
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
7576
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
7677
<PrivateAssets>all</PrivateAssets>

version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<Project>
33
<PropertyGroup>
4-
<Version>4.0.1</Version>
4+
<Version>4.0.2</Version>
55
</PropertyGroup>
66
</Project>

0 commit comments

Comments
 (0)