Skip to content

Commit aa23188

Browse files
author
Herry Kurniawan
authored
Merge pull request #60 from mdsol/feature/MCC-575471
[MCC-575471] Fall back to V1 authentication when V2 authentication fails
2 parents 9b5a8a7 + 440f91f commit aa23188

File tree

6 files changed

+94
-31
lines changed

6 files changed

+94
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## v4.0.2
44
- **[AspNetCore]** Update aspnetcore version to aspnetcore2.1 LTS.
5+
- **[Core]** Fallback to V1 protocol when V2 athentication fails.
56

67
## v4.0.1
78
- **[Core]** Fixed default sigining with both MWS and MWSV2 instead of option selected by consuming application.

src/Medidata.MAuth.Core/MAuthAuthenticator.cs

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,16 +48,14 @@ public async Task<bool> AuthenticateRequest(HttpRequestMessage request)
4848
if (options.DisableV1 && version == MAuthVersion.MWS)
4949
throw new InvalidVersionException($"Authentication with {version} version is disabled.");
5050

51-
var mAuthCore = MAuthCoreFactory.Instantiate(version);
52-
var authInfo = GetAuthenticationInfo(request, version);
53-
var logMessage = "Mauth-client attempting to authenticate request from app with mauth app uuid" +
54-
$" {authInfo.ApplicationUuid} using version {version}";
55-
logger.LogInformation(logMessage);
56-
57-
var appInfo = await GetApplicationInfo(authInfo.ApplicationUuid, version).ConfigureAwait(false);
58-
var signature = await mAuthCore.GetSignature(request, authInfo).ConfigureAwait(false);
59-
60-
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;
6159
}
6260
catch (ArgumentException ex)
6361
{
@@ -91,10 +89,24 @@ public async Task<bool> AuthenticateRequest(HttpRequestMessage request)
9189
}
9290
}
9391

94-
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) =>
95107
cache.GetOrCreateAsync(applicationUuid, async entry =>
96108
{
97-
var retrier = new MAuthRequestRetrier(options, version);
109+
var retrier = new MAuthRequestRetrier(options);
98110
var response = await retrier.GetSuccessfulResponse(
99111
applicationUuid,
100112
CreateRequest,
@@ -111,38 +123,41 @@ private Task<ApplicationInfo> GetApplicationInfo(Guid applicationUuid, MAuthVers
111123
return result;
112124
});
113125

114-
private HttpRequestMessage CreateRequest(Guid applicationUuid) =>
115-
new HttpRequestMessage(HttpMethod.Get, new Uri(options.MAuthServiceUrl,
116-
$"{Constants.MAuthTokenRequestPath}{applicationUuid.ToHyphenString()}.json"));
117-
118126
/// <summary>
119127
/// Extracts the authentication information from a <see cref="HttpRequestMessage"/>.
120128
/// </summary>
121129
/// <param name="request">The request that has the authentication information.</param>
122-
/// <param name="version">Enum value of the MAuthVersion.</param>
130+
/// <param name="mAuthCore">Instantiation of mAuthCore class.</param>
123131
/// <returns>The authentication information with the payload from the request.</returns>
124-
internal PayloadAuthenticationInfo GetAuthenticationInfo(HttpRequestMessage request, MAuthVersion version)
132+
internal static PayloadAuthenticationInfo GetAuthenticationInfo(HttpRequestMessage request, IMAuthCore mAuthCore)
125133
{
126-
var mAuthCore = MAuthCoreFactory.Instantiate(version);
127134
var headerKeys = mAuthCore.GetHeaderKeys();
128135
var authHeader = request.Headers.GetFirstValueOrDefault<string>(headerKeys.mAuthHeaderKey);
129136

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

133142
var signedTime = request.Headers.GetFirstValueOrDefault<long>(headerKeys.mAuthTimeHeaderKey);
134143

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

138149
var (uuid, payload) = authHeader.ParseAuthenticationHeader();
139150

140-
return new PayloadAuthenticationInfo()
151+
return new PayloadAuthenticationInfo
141152
{
142153
ApplicationUuid = uuid,
143154
Payload = Convert.FromBase64String(payload),
144155
SignedTime = signedTime.FromUnixTimeSeconds()
145156
};
146157
}
158+
159+
private HttpRequestMessage CreateRequest(Guid applicationUuid) =>
160+
new HttpRequestMessage(HttpMethod.Get, new Uri(options.MAuthServiceUrl,
161+
$"{Constants.MAuthTokenRequestPath}{applicationUuid.ToHyphenString()}.json"));
147162
}
148163
}

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
{

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 & 4 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
@@ -308,10 +311,10 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSV2Version
308311
var testData = await method.FromResourceV2();
309312
var version = MAuthVersion.MWSV2;
310313
var testOptions = TestExtensions.ServerOptions;
311-
var authenticator = new MAuthAuthenticator(testOptions, NullLogger<MAuthAuthenticator>.Instance);
314+
var mAuthCore = MAuthCoreFactory.Instantiate(version);
312315

313316
// Act
314-
var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), version);
317+
var actual = MAuthAuthenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore);
315318

316319
// Assert
317320
Assert.Equal(testData.ApplicationUuid, actual.ApplicationUuid);
@@ -330,15 +333,63 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSVersion_W
330333
var testData = await method.FromResource();
331334
var version = MAuthVersion.MWS;
332335
var testOptions = TestExtensions.ServerOptions;
333-
var authenticator = new MAuthAuthenticator(testOptions, NullLogger<MAuthAuthenticator>.Instance);
336+
var mAuthCore = MAuthCoreFactory.Instantiate(version);
334337

335338
// Act
336-
var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), version);
339+
var actual = MAuthAuthenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore);
337340

338341
// Assert
339342
Assert.Equal(testData.ApplicationUuid, actual.ApplicationUuid);
340343
Assert.Equal(Convert.FromBase64String(testData.Payload), actual.Payload);
341344
Assert.Equal(testData.SignedTime, actual.SignedTime);
342345
}
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+
}
343394
}
344395
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
<ItemGroup>
7272
<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>

0 commit comments

Comments
 (0)