Skip to content

Commit

Permalink
[MCC-575820] Incorporate protocol test suite and implement (#64)
Browse files Browse the repository at this point in the history
  • Loading branch information
prajon84 authored Aug 6, 2020
1 parent 664b4de commit 50dfbd9
Show file tree
Hide file tree
Showing 17 changed files with 443 additions and 60 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
- **[Core]** Added normalization of Uri AbsolutePath.
- **[Core]** Added unescape step in query_string encoding to remove `double encoding`.
- **[Core]** Replace `DisableV1`option with `SignVersions` option and change the default signing to `MAuthVersion.MWS` only.
- **[Core]** Added parsing code to test with mauth-protocol-test-suite.
- **[Core]** Fixed bug in sorting of query parameters.

## v4.0.2
- **[AspNetCore]** Update aspnetcore version to aspnetcore2.1 LTS.
Expand Down
25 changes: 25 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Contributing

## General Information
* Clone this repo in your workspace. Checkout latest `develop` branch.
* Make new changes or updates into `feature/bugfix` branch.
* Make sure to add unit tests for it so that there is no breaking changes.
* Commit and push your branch to compare and create PR against latest `develop` branch.

## Running Tests
To run tests, go the folder `mauth-client-dotnet\tests\Medidata.MAuth.Tests`
Next, run the tests as:

```
dotnet test --filter "Category!=ProtocolTestSuite"
```

## Running mauth-protocol-test-suite
To run the mauth-protocol-test-suite clone the latest suite onto your machine and place it in the same parent directory as this repo (or supply the ENV var
`TEST_SUITE_PATH` with the path to the test suite relative to this repo).
Then navigate to :`mauth-client-dotnet\tests\Medidata.MAuth.Tests`
And, run the tests as:

```
dotnet test --filter "Category=ProtocolTestSuite"
```
2 changes: 1 addition & 1 deletion build/build.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ Write-Host "Running unit tests..." -ForegroundColor Cyan

Push-Location -Path .\tests\Medidata.MAuth.Tests

dotnet test
dotnet test --filter "Category!=ProtocolTestSuite"

Pop-Location

Expand Down
24 changes: 9 additions & 15 deletions src/Medidata.MAuth.Core/MAuthCoreExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,33 +188,27 @@ public static string BuildEncodedQueryParams(this string queryString)
return string.Empty;

var queryArray = queryString.Split('&');
var unescapedKeysAndValues = new KeyValuePair<string, string>[queryArray.Length];

// unescaping
for (int i = 0; i < queryArray.Length; i++)
{
var keyValue = queryArray[i].Split('=');
var unEscapedKey = Uri.UnescapeDataString(keyValue[0]);
var unEscapedValue = Uri.UnescapeDataString(keyValue[1]);
queryArray[i] = $"{unEscapedKey}={unEscapedValue}";
unescapedKeysAndValues[i] = new KeyValuePair<string, string>(
Uri.UnescapeDataString(keyValue[0]), Uri.UnescapeDataString(keyValue[1]));
}

// sorting
Array.Sort(queryArray, StringComparer.Ordinal);

// escaping
for (int i = 0; i < queryArray.Length; i++)
{
var keyValue = queryArray[i].Split('=');
var escapedKey = Uri.EscapeDataString(keyValue[0]);
var escapedValue = Uri.EscapeDataString(keyValue[1]);
queryArray[i] = $"{escapedKey}={escapedValue}";
}
// sorting and escaping
var escapedKeyValues = unescapedKeysAndValues
.OrderBy(kv => kv.Key, StringComparer.Ordinal)
.ThenBy(kv => kv.Value, StringComparer.Ordinal)
.Select(kv => $"{Uri.EscapeDataString(kv.Key)}={Uri.EscapeDataString(kv.Value)}");

// Above encoding converts space as `%20` and `+` as `%2B`
// But space and `+` both needs to be converted as `%20` as per
// reference https://github.com/mdsol/mauth-client-ruby/blob/v6.0.0/lib/mauth/request_and_response.rb#L113
// so this convert `%2B` into `%20` to match encodedqueryparams to that of other languages.
return string.Join("&", queryArray).Replace("%2B", "%20");
return string.Join("&", escapedKeyValues).Replace("%2B", "%20");
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ protected override Task<HttpResponseMessage> SendAsync(
MAuthTimeHeaderV2 = request.Headers.GetFirstValueOrDefault<string>(Constants.MAuthTimeHeaderKeyV2);
MAuthTimeHeader = request.Headers.GetFirstValueOrDefault<string>(Constants.MAuthTimeHeaderKey);

return Task.Run(() => new HttpResponseMessage(HttpStatusCode.OK));
return Task.FromResult(new HttpResponseMessage(HttpStatusCode.OK));
}
}
}
43 changes: 35 additions & 8 deletions tests/Medidata.MAuth.Tests/Infrastructure/MAuthServerHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,45 @@
using System.Threading;
using System.Threading.Tasks;
using Medidata.MAuth.Core;
using Microsoft.Extensions.Logging.Abstractions;
using Medidata.MAuth.Tests.ProtocolTestSuite;
using Newtonsoft.Json;

namespace Medidata.MAuth.Tests.Infrastructure
{
internal class MAuthServerHandler : HttpMessageHandler
{
private static readonly Guid clientUuid = new Guid("192cce84-8466-490e-b03e-074f82da3ee2");
private int currentNumberOfAttempts = 0;
private MAuthServerHandler() { }

private static readonly Guid _clientUuid = new Guid("192cce84-8466-490e-b03e-074f82da3ee2");
private int _currentNumberOfAttempts = 0;

public int SucceedAfterThisManyAttempts { get; set; } = 1;

private static string _signingPublicKey;
private static Guid _signingAppUuid;

private async Task<MAuthServerHandler> InitializeAsync()
{
var protocolSuite = new ProtocolTestSuiteHelper();
_signingPublicKey = await protocolSuite.GetPublicKey();
_signingAppUuid = await protocolSuite.ReadSignInAppUuid();
return this;
}

public static Task<MAuthServerHandler> CreateAsync()
{
var ret = new MAuthServerHandler();
return ret.InitializeAsync();
}

protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
currentNumberOfAttempts += 1;
_currentNumberOfAttempts += 1;
var version = request.GetAuthHeaderValue().GetVersionFromAuthenticationHeader();
var mAuthCore = MAuthCoreFactory.Instantiate(version);

if (currentNumberOfAttempts < SucceedAfterThisManyAttempts)
if (_currentNumberOfAttempts < SucceedAfterThisManyAttempts)
return new HttpResponseMessage(HttpStatusCode.ServiceUnavailable);
var authInfo = MAuthAuthenticator.GetAuthenticationInfo(request, mAuthCore);

Expand All @@ -34,9 +53,17 @@ await mAuthCore.GetSignature(request, authInfo),
return new HttpResponseMessage(HttpStatusCode.Unauthorized) { RequestMessage = request };

if (!request.RequestUri.AbsolutePath.Equals(
$"{Constants.MAuthTokenRequestPath}{clientUuid.ToHyphenString()}.json",
$"{Constants.MAuthTokenRequestPath}{_clientUuid.ToHyphenString()}.json",
StringComparison.OrdinalIgnoreCase) &&
!request.RequestUri.AbsolutePath.Equals(
$"{Constants.MAuthTokenRequestPath}{_signingAppUuid.ToHyphenString()}.json",
StringComparison.OrdinalIgnoreCase))
{
return new HttpResponseMessage(HttpStatusCode.NotFound) { RequestMessage = request };
}

bool isProtocolSuiteTest = request.RequestUri.AbsolutePath.Contains(
_signingAppUuid.ToHyphenString());

return new HttpResponseMessage(HttpStatusCode.OK)
{
Expand All @@ -46,10 +73,10 @@ await mAuthCore.GetSignature(request, authInfo),
{
security_token = new ApplicationInfo()
{
Uuid = clientUuid,
Uuid = isProtocolSuiteTest ? _signingAppUuid : _clientUuid,
Name = "Medidata.MAuth.Tests",
CreationDate = new DateTimeOffset(2016, 8, 1, 0, 0, 0, TimeSpan.Zero),
PublicKey = TestExtensions.ClientPublicKey
PublicKey = isProtocolSuiteTest ? _signingPublicKey : TestExtensions.ClientPublicKey
}
})
)
Expand Down
16 changes: 8 additions & 8 deletions tests/Medidata.MAuth.Tests/Infrastructure/TestExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ internal static class TestExtensions
public static readonly string ServerPrivateKey = GetKeyFromResource(nameof(ServerPrivateKey)).Result;
public static readonly string ServerPublicKey = GetKeyFromResource(nameof(ServerPublicKey)).Result;

public static MAuthOptionsBase ServerOptions => new MAuthTestOptions()
public static MAuthOptionsBase ServerOptions(MAuthServerHandler serverHandler) => new MAuthTestOptions()
{
ApplicationUuid = ServerUuid,
MAuthServiceUrl = TestUri,
PrivateKey = ServerPrivateKey,
MAuthServiceRetryPolicy = MAuthServiceRetryPolicy.RetryOnce,
MAuthServerHandler = new MAuthServerHandler()
MAuthServerHandler = serverHandler
};

public static MAuthSigningOptions ClientOptions(DateTimeOffset signedTime) => new MAuthSigningOptions()
Expand All @@ -37,18 +37,18 @@ internal static class TestExtensions
};

public static MAuthOptionsBase GetServerOptionsWithAttempts(MAuthServiceRetryPolicy policy,
bool shouldSucceedWithin) =>
new MAuthTestOptions()
bool shouldSucceedWithin, MAuthServerHandler serverHandler)
{
serverHandler.SucceedAfterThisManyAttempts = (int)policy + (shouldSucceedWithin ? 1 : 2);
return new MAuthTestOptions()
{
ApplicationUuid = ServerUuid,
MAuthServiceUrl = TestUri,
PrivateKey = ServerPrivateKey,
MAuthServiceRetryPolicy = policy,
MAuthServerHandler = new MAuthServerHandler()
{
SucceedAfterThisManyAttempts = (int)policy + (shouldSucceedWithin ? 1 : 2)
}
MAuthServerHandler = serverHandler
};
}

public static Task<string> GetStringFromResource(string resourceName)
{
Expand Down
13 changes: 9 additions & 4 deletions tests/Medidata.MAuth.Tests/MAuthAspNetCoreTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public async Task MAuthMiddleware_WithValidRequest_WillAuthenticate(string metho
{
// Arrange
var testData = await method.FromResourceV2();
var serverHandler = await MAuthServerHandler.CreateAsync();

using (var server = new TestServer(new WebHostBuilder().Configure(app =>
{
Expand All @@ -31,7 +32,7 @@ public async Task MAuthMiddleware_WithValidRequest_WillAuthenticate(string metho
options.ApplicationUuid = TestExtensions.ServerUuid;
options.MAuthServiceUrl = TestExtensions.TestUri;
options.PrivateKey = TestExtensions.ServerPrivateKey;
options.MAuthServerHandler = new MAuthServerHandler();
options.MAuthServerHandler = serverHandler;
options.HideExceptionsAndReturnUnauthorized = false;
});

Expand All @@ -55,6 +56,7 @@ public async Task MAuthMiddleware_WithoutMAuthHeader_WillNotAuthenticate(string
{
// Arrange
var testData = await method.FromResource();
var serverHandler = await MAuthServerHandler.CreateAsync();

using (var server = new TestServer(new WebHostBuilder().Configure(app =>
{
Expand All @@ -63,7 +65,7 @@ public async Task MAuthMiddleware_WithoutMAuthHeader_WillNotAuthenticate(string
options.ApplicationUuid = TestExtensions.ServerUuid;
options.MAuthServiceUrl = TestExtensions.TestUri;
options.PrivateKey = TestExtensions.ServerPrivateKey;
options.MAuthServerHandler = new MAuthServerHandler();
options.MAuthServerHandler = serverHandler;
});

app.Run(async context => await new StreamWriter(context.Response.Body).WriteAsync("Done."));
Expand All @@ -87,6 +89,7 @@ public async Task MAuthMiddleware_WithEnabledExceptions_WillThrowException(strin
{
// Arrange
var testData = await method.FromResource();
var serverHandler = await MAuthServerHandler.CreateAsync();

using (var server = new TestServer(new WebHostBuilder().Configure(app =>
{
Expand All @@ -95,7 +98,7 @@ public async Task MAuthMiddleware_WithEnabledExceptions_WillThrowException(strin
options.ApplicationUuid = TestExtensions.ServerUuid;
options.MAuthServiceUrl = TestExtensions.TestUri;
options.PrivateKey = TestExtensions.ServerPrivateKey;
options.MAuthServerHandler = new MAuthServerHandler();
options.MAuthServerHandler = serverHandler;
options.HideExceptionsAndReturnUnauthorized = false;
});
})))
Expand All @@ -121,6 +124,8 @@ public async Task MAuthMiddleware_WithNonSeekableBodyStream_WillRestoreBodyStrea
var testData = await method.FromResourceV2();
var canSeek = false;
var body = string.Empty;
var serverHandler = await MAuthServerHandler.CreateAsync();

using (var server = new TestServer(new WebHostBuilder().Configure(app =>
{
app
Expand All @@ -130,7 +135,7 @@ public async Task MAuthMiddleware_WithNonSeekableBodyStream_WillRestoreBodyStrea
options.ApplicationUuid = TestExtensions.ServerUuid;
options.MAuthServiceUrl = TestExtensions.TestUri;
options.PrivateKey = TestExtensions.ServerPrivateKey;
options.MAuthServerHandler = new MAuthServerHandler();
options.MAuthServerHandler = serverHandler;
})
.Run(async context =>
{
Expand Down
Loading

0 comments on commit 50dfbd9

Please sign in to comment.