From 441231607db38ee8a1d2e00fbaac6457e1a1e9a0 Mon Sep 17 00:00:00 2001 From: Prajon Date: Fri, 17 Jan 2020 11:16:22 -0500 Subject: [PATCH 1/5] Added logic to fall back to V1 authentication when V2 authentication fails --- CHANGELOG.md | 1 + src/Medidata.MAuth.Core/MAuthAuthenticator.cs | 31 +++++++---- .../MAuthRequestRetrier.cs | 3 +- .../MAuthAuthenticatorTests.cs | 51 +++++++++++++++++++ .../Medidata.MAuth.Tests.csproj | 1 + 5 files changed, 75 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ad36ce..336b97a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## v4.0.2 - **[AspNetCore]** Update aspnetcore version to aspnetcore2.1 LTS. +- **[Core]** Fallback to V1 protocol when V2 athentication fails. ## v4.0.1 - **[Core]** Fixed default sigining with both MWS and MWSV2 instead of option selected by consuming application. diff --git a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs index 251ef6d..c1abb47 100644 --- a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs +++ b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs @@ -48,16 +48,17 @@ public async Task AuthenticateRequest(HttpRequestMessage request) if (options.DisableV1 && version == MAuthVersion.MWS) throw new InvalidVersionException($"Authentication with {version} version is disabled."); - var mAuthCore = MAuthCoreFactory.Instantiate(version); - var authInfo = GetAuthenticationInfo(request, version); var logMessage = "Mauth-client attempting to authenticate request from app with mauth app uuid" + - $" {authInfo.ApplicationUuid} using version {version}"; + $" {options.ApplicationUuid} using version {version}"; logger.LogInformation(logMessage); - - var appInfo = await GetApplicationInfo(authInfo.ApplicationUuid, version).ConfigureAwait(false); - var signature = await mAuthCore.GetSignature(request, authInfo).ConfigureAwait(false); - - return mAuthCore.Verify(authInfo.Payload, signature, appInfo.PublicKey); + var authenticated = await Authenticate(request, version).ConfigureAwait(false); + if (version == MAuthVersion.MWSV2 && !authenticated && !options.DisableV1) + { + // fall back to V1 authentication + authenticated = await Authenticate(request, MAuthVersion.MWS).ConfigureAwait(false); + logger.LogWarning("Completed successful authentication attempt after fallback to v1"); + } + return authenticated; } catch (ArgumentException ex) { @@ -91,10 +92,20 @@ public async Task AuthenticateRequest(HttpRequestMessage request) } } - private Task GetApplicationInfo(Guid applicationUuid, MAuthVersion version) => + private async Task Authenticate(HttpRequestMessage request, MAuthVersion version) + { + var authInfo = GetAuthenticationInfo(request, version); + var appInfo = await GetApplicationInfo(authInfo.ApplicationUuid).ConfigureAwait(false); + + var mAuthCore = MAuthCoreFactory.Instantiate(version); + var signature = await mAuthCore.GetSignature(request, authInfo).ConfigureAwait(false); + return mAuthCore.Verify(authInfo.Payload, signature, appInfo.PublicKey); + } + + private Task GetApplicationInfo(Guid applicationUuid) => cache.GetOrCreateAsync(applicationUuid, async entry => { - var retrier = new MAuthRequestRetrier(options, version); + var retrier = new MAuthRequestRetrier(options); var response = await retrier.GetSuccessfulResponse( applicationUuid, CreateRequest, diff --git a/src/Medidata.MAuth.Core/MAuthRequestRetrier.cs b/src/Medidata.MAuth.Core/MAuthRequestRetrier.cs index ac60a3a..bac3f16 100644 --- a/src/Medidata.MAuth.Core/MAuthRequestRetrier.cs +++ b/src/Medidata.MAuth.Core/MAuthRequestRetrier.cs @@ -1,7 +1,6 @@ using System; using System.Net.Http; using System.Threading.Tasks; -using Medidata.MAuth.Core.Models; namespace Medidata.MAuth.Core { @@ -9,7 +8,7 @@ internal class MAuthRequestRetrier { private readonly HttpClient client; - public MAuthRequestRetrier(MAuthOptionsBase options, MAuthVersion version) + public MAuthRequestRetrier(MAuthOptionsBase options) { var signingHandler = new MAuthSigningHandler(options: new MAuthSigningOptions() { diff --git a/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs b/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs index 1737bbf..d89a620 100644 --- a/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs +++ b/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs @@ -6,7 +6,10 @@ using Medidata.MAuth.Core.Exceptions; using Medidata.MAuth.Core.Models; using Medidata.MAuth.Tests.Infrastructure; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Logging.Internal; +using Moq; using Xunit; namespace Medidata.MAuth.Tests @@ -340,5 +343,53 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSVersion_W Assert.Equal(Convert.FromBase64String(testData.Payload), actual.Payload); Assert.Equal(testData.SignedTime, actual.SignedTime); } + + [Fact] + public static async Task AuthenticateRequest_WithDefaultRequest_WhenV2Fails_FallBackToV1AndAuthenticate() + { + // Arrange + var testData = await "GET".FromResource(); + var mockLogger = new Mock(); + + var authenticator = new MAuthAuthenticator(TestExtensions.ServerOptions, mockLogger.Object); + var requestData = testData.ToDefaultHttpRequestMessage(); + + // Act + var isAuthenticated = await authenticator.AuthenticateRequest(requestData); + + // Assert + Assert.True(isAuthenticated); + mockLogger.Verify(x => x.Log( + LogLevel.Warning, It.IsAny(), + It.Is(v => v.ToString() + .Contains("Completed successful authentication attempt after fallback to v1")), + It.IsAny(), It.IsAny>() + )); + } + + [Fact] + public static async Task AuthenticateRequest_WithDefaultRequest_AndDisableV1_WhenV2Fails_NotFallBackToV1() + { + // Arrange + var testData = await "GET".FromResource(); + var mockLogger = new Mock(); + var testOptions = TestExtensions.ServerOptions; + testOptions.DisableV1 = true; + + var authenticator = new MAuthAuthenticator(testOptions, mockLogger.Object); + var requestData = testData.ToDefaultHttpRequestMessage(); + + // Act + var isAuthenticated = await authenticator.AuthenticateRequest(requestData); + + // Assert + Assert.False(isAuthenticated); + mockLogger.Verify(x => x.Log( + LogLevel.Warning, It.IsAny(), + It.Is(v => v.ToString() + .Contains("Completed successful authentication attempt after fallback to v1")), + It.IsAny(), It.IsAny>() + ), Times.Never); + } } } diff --git a/tests/Medidata.MAuth.Tests/Medidata.MAuth.Tests.csproj b/tests/Medidata.MAuth.Tests/Medidata.MAuth.Tests.csproj index f704377..d0b83dc 100644 --- a/tests/Medidata.MAuth.Tests/Medidata.MAuth.Tests.csproj +++ b/tests/Medidata.MAuth.Tests/Medidata.MAuth.Tests.csproj @@ -71,6 +71,7 @@ + all From 2237d5b4b30ea17b7e6794ab0dd2758875d2e64e Mon Sep 17 00:00:00 2001 From: Prajon Date: Fri, 17 Jan 2020 15:22:01 -0500 Subject: [PATCH 2/5] Updated nits feedback --- src/Medidata.MAuth.Core/MAuthAuthenticator.cs | 4 ++-- tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs index c1abb47..97c342a 100644 --- a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs +++ b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs @@ -52,11 +52,11 @@ public async Task AuthenticateRequest(HttpRequestMessage request) $" {options.ApplicationUuid} using version {version}"; logger.LogInformation(logMessage); var authenticated = await Authenticate(request, version).ConfigureAwait(false); - if (version == MAuthVersion.MWSV2 && !authenticated && !options.DisableV1) + if (!authenticated && version == MAuthVersion.MWSV2 && !options.DisableV1) { // fall back to V1 authentication authenticated = await Authenticate(request, MAuthVersion.MWS).ConfigureAwait(false); - logger.LogWarning("Completed successful authentication attempt after fallback to v1"); + logger.LogWarning("Completed successful authentication attempt after fallback to V1"); } return authenticated; } diff --git a/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs b/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs index d89a620..89a95cc 100644 --- a/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs +++ b/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs @@ -362,7 +362,7 @@ public static async Task AuthenticateRequest_WithDefaultRequest_WhenV2Fails_Fall mockLogger.Verify(x => x.Log( LogLevel.Warning, It.IsAny(), It.Is(v => v.ToString() - .Contains("Completed successful authentication attempt after fallback to v1")), + .Contains("Completed successful authentication attempt after fallback to V1")), It.IsAny(), It.IsAny>() )); } @@ -387,7 +387,7 @@ public static async Task AuthenticateRequest_WithDefaultRequest_AndDisableV1_Whe mockLogger.Verify(x => x.Log( LogLevel.Warning, It.IsAny(), It.Is(v => v.ToString() - .Contains("Completed successful authentication attempt after fallback to v1")), + .Contains("Completed successful authentication attempt after fallback to V1")), It.IsAny(), It.IsAny>() ), Times.Never); } From ca180f3a792d10f241fad79db105bcc2f61fbfb3 Mon Sep 17 00:00:00 2001 From: Prajon Date: Fri, 17 Jan 2020 15:37:21 -0500 Subject: [PATCH 3/5] Updated to to pass mAuthCore instantiation instead of version to GetAuthenticationInfo(..) method --- src/Medidata.MAuth.Core/MAuthAuthenticator.cs | 9 ++++----- .../Infrastructure/MAuthServerHandler.cs | 2 +- tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs | 6 ++++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs index 97c342a..ff6a42a 100644 --- a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs +++ b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs @@ -94,10 +94,10 @@ public async Task AuthenticateRequest(HttpRequestMessage request) private async Task Authenticate(HttpRequestMessage request, MAuthVersion version) { - var authInfo = GetAuthenticationInfo(request, version); + var mAuthCore = MAuthCoreFactory.Instantiate(version); + var authInfo = GetAuthenticationInfo(request, mAuthCore); var appInfo = await GetApplicationInfo(authInfo.ApplicationUuid).ConfigureAwait(false); - var mAuthCore = MAuthCoreFactory.Instantiate(version); var signature = await mAuthCore.GetSignature(request, authInfo).ConfigureAwait(false); return mAuthCore.Verify(authInfo.Payload, signature, appInfo.PublicKey); } @@ -130,11 +130,10 @@ private HttpRequestMessage CreateRequest(Guid applicationUuid) => /// Extracts the authentication information from a . /// /// The request that has the authentication information. - /// Enum value of the MAuthVersion. + /// Instantiation of mAuthCore class. /// The authentication information with the payload from the request. - internal PayloadAuthenticationInfo GetAuthenticationInfo(HttpRequestMessage request, MAuthVersion version) + internal PayloadAuthenticationInfo GetAuthenticationInfo(HttpRequestMessage request, IMAuthCore mAuthCore) { - var mAuthCore = MAuthCoreFactory.Instantiate(version); var headerKeys = mAuthCore.GetHeaderKeys(); var authHeader = request.Headers.GetFirstValueOrDefault(headerKeys.mAuthHeaderKey); diff --git a/tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs b/tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs index 9bb4410..c0b4e8c 100644 --- a/tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs +++ b/tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs @@ -29,7 +29,7 @@ protected override async Task SendAsync( var authenticator = new MAuthAuthenticator(TestExtensions.ServerOptions, NullLogger.Instance); - var authInfo = authenticator.GetAuthenticationInfo(request, version); + var authInfo = authenticator.GetAuthenticationInfo(request, mAuthCore); if (!mAuthCore.Verify(authInfo.Payload, await mAuthCore.GetSignature(request, authInfo), diff --git a/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs b/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs index 89a95cc..a780d2d 100644 --- a/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs +++ b/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs @@ -312,9 +312,10 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSV2Version var version = MAuthVersion.MWSV2; var testOptions = TestExtensions.ServerOptions; var authenticator = new MAuthAuthenticator(testOptions, NullLogger.Instance); + var mAuthCore = MAuthCoreFactory.Instantiate(version); // Act - var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), version); + var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore); // Assert Assert.Equal(testData.ApplicationUuid, actual.ApplicationUuid); @@ -334,9 +335,10 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSVersion_W var version = MAuthVersion.MWS; var testOptions = TestExtensions.ServerOptions; var authenticator = new MAuthAuthenticator(testOptions, NullLogger.Instance); + var mAuthCore = MAuthCoreFactory.Instantiate(version); // Act - var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), version); + var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore); // Assert Assert.Equal(testData.ApplicationUuid, actual.ApplicationUuid); From e7ba136d6976a30959e0f835f0eeb5867c7b204a Mon Sep 17 00:00:00 2001 From: Prajon Date: Tue, 21 Jan 2020 08:53:20 -0500 Subject: [PATCH 4/5] Moving LogInformation(...) into Authenticate(..) method so that it gets logged when eith V2 or fall back V1 is authenticated --- src/Medidata.MAuth.Core/MAuthAuthenticator.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs index ff6a42a..271b3ef 100644 --- a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs +++ b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs @@ -48,9 +48,6 @@ public async Task AuthenticateRequest(HttpRequestMessage request) if (options.DisableV1 && version == MAuthVersion.MWS) throw new InvalidVersionException($"Authentication with {version} version is disabled."); - var logMessage = "Mauth-client attempting to authenticate request from app with mauth app uuid" + - $" {options.ApplicationUuid} using version {version}"; - logger.LogInformation(logMessage); var authenticated = await Authenticate(request, version).ConfigureAwait(false); if (!authenticated && version == MAuthVersion.MWSV2 && !options.DisableV1) { @@ -94,6 +91,10 @@ public async Task AuthenticateRequest(HttpRequestMessage request) private async Task Authenticate(HttpRequestMessage request, MAuthVersion version) { + var logMessage = "Mauth-client attempting to authenticate request from app with mauth app uuid" + + $" {options.ApplicationUuid} using version {version}"; + logger.LogInformation(logMessage); + var mAuthCore = MAuthCoreFactory.Instantiate(version); var authInfo = GetAuthenticationInfo(request, mAuthCore); var appInfo = await GetApplicationInfo(authInfo.ApplicationUuid).ConfigureAwait(false); From 440f91f5a5d1188bce3594c40b115be124e82c8a Mon Sep 17 00:00:00 2001 From: Prajon Date: Wed, 22 Jan 2020 08:41:29 -0500 Subject: [PATCH 5/5] Updated the method to be static and updated the unit tests and references --- src/Medidata.MAuth.Core/MAuthAuthenticator.cs | 16 ++++++++++------ .../Infrastructure/MAuthServerHandler.cs | 6 +----- .../MAuthAuthenticatorTests.cs | 6 ++---- 3 files changed, 13 insertions(+), 15 deletions(-) diff --git a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs index 271b3ef..9cb1bf7 100644 --- a/src/Medidata.MAuth.Core/MAuthAuthenticator.cs +++ b/src/Medidata.MAuth.Core/MAuthAuthenticator.cs @@ -123,37 +123,41 @@ private Task GetApplicationInfo(Guid applicationUuid) => return result; }); - private HttpRequestMessage CreateRequest(Guid applicationUuid) => - new HttpRequestMessage(HttpMethod.Get, new Uri(options.MAuthServiceUrl, - $"{Constants.MAuthTokenRequestPath}{applicationUuid.ToHyphenString()}.json")); - /// /// Extracts the authentication information from a . /// /// The request that has the authentication information. /// Instantiation of mAuthCore class. /// The authentication information with the payload from the request. - internal PayloadAuthenticationInfo GetAuthenticationInfo(HttpRequestMessage request, IMAuthCore mAuthCore) + internal static PayloadAuthenticationInfo GetAuthenticationInfo(HttpRequestMessage request, IMAuthCore mAuthCore) { var headerKeys = mAuthCore.GetHeaderKeys(); var authHeader = request.Headers.GetFirstValueOrDefault(headerKeys.mAuthHeaderKey); if (authHeader == null) + { throw new ArgumentNullException(nameof(authHeader), "The MAuth header is missing from the request."); + } var signedTime = request.Headers.GetFirstValueOrDefault(headerKeys.mAuthTimeHeaderKey); if (signedTime == default(long)) + { throw new ArgumentException("Invalid MAuth signed time header value.", nameof(signedTime)); + } var (uuid, payload) = authHeader.ParseAuthenticationHeader(); - return new PayloadAuthenticationInfo() + return new PayloadAuthenticationInfo { ApplicationUuid = uuid, Payload = Convert.FromBase64String(payload), SignedTime = signedTime.FromUnixTimeSeconds() }; } + + private HttpRequestMessage CreateRequest(Guid applicationUuid) => + new HttpRequestMessage(HttpMethod.Get, new Uri(options.MAuthServiceUrl, + $"{Constants.MAuthTokenRequestPath}{applicationUuid.ToHyphenString()}.json")); } } diff --git a/tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs b/tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs index c0b4e8c..3499c89 100644 --- a/tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs +++ b/tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using Medidata.MAuth.Core; -using Medidata.MAuth.Core.Models; using Microsoft.Extensions.Logging.Abstractions; using Newtonsoft.Json; @@ -26,10 +25,7 @@ protected override async Task SendAsync( if (currentNumberOfAttempts < SucceedAfterThisManyAttempts) return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable); - - var authenticator = new MAuthAuthenticator(TestExtensions.ServerOptions, NullLogger.Instance); - - var authInfo = authenticator.GetAuthenticationInfo(request, mAuthCore); + var authInfo = MAuthAuthenticator.GetAuthenticationInfo(request, mAuthCore); if (!mAuthCore.Verify(authInfo.Payload, await mAuthCore.GetSignature(request, authInfo), diff --git a/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs b/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs index a780d2d..b112142 100644 --- a/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs +++ b/tests/Medidata.MAuth.Tests/MAuthAuthenticatorTests.cs @@ -311,11 +311,10 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSV2Version var testData = await method.FromResourceV2(); var version = MAuthVersion.MWSV2; var testOptions = TestExtensions.ServerOptions; - var authenticator = new MAuthAuthenticator(testOptions, NullLogger.Instance); var mAuthCore = MAuthCoreFactory.Instantiate(version); // Act - var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore); + var actual = MAuthAuthenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore); // Assert Assert.Equal(testData.ApplicationUuid, actual.ApplicationUuid); @@ -334,11 +333,10 @@ public static async Task GetAuthenticationInfo_WithSignedRequest_ForMWSVersion_W var testData = await method.FromResource(); var version = MAuthVersion.MWS; var testOptions = TestExtensions.ServerOptions; - var authenticator = new MAuthAuthenticator(testOptions, NullLogger.Instance); var mAuthCore = MAuthCoreFactory.Instantiate(version); // Act - var actual = authenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore); + var actual = MAuthAuthenticator.GetAuthenticationInfo(testData.ToHttpRequestMessage(version), mAuthCore); // Assert Assert.Equal(testData.ApplicationUuid, actual.ApplicationUuid);