Skip to content

Commit

Permalink
Merge pull request #50 from mdsol/develop
Browse files Browse the repository at this point in the history
Release of v4.0.0
  • Loading branch information
Herry Kurniawan authored Oct 8, 2019
2 parents 461a28f + 4d5339a commit 8f1d3cc
Show file tree
Hide file tree
Showing 47 changed files with 1,531 additions and 397 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# Changes in Medidata.MAuth

## v4.0.0
- **[All]** Added implementation for MWSV2 signinig and authentication. Added logging support during MAuthentication.

## v3.1.3
- **[Core]** Refactored `MAuthCoreExtensions.cs` and moved Signing and Verification method into `IMAuthCore.cs`.

## v3.1.2
- **[Core]** Fixed and enabled caching of the `ApplicationInfo` from the MAuth server.

Expand Down
69 changes: 63 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ An example:

```C#
using Medidata.MAuth.Core;
using Medidata.MAuth.Core.Models;

public async Task<HttpResponseMessage> SignAndSendRequest(HttpRequestMessage request)
{
Expand All @@ -111,7 +112,13 @@ public async Task<HttpResponseMessage> SignAndSendRequest(HttpRequestMessage req
ApplicationUuid = new Guid("7c872d75-986b-4c61-bb17-f2569d42bfb0"),

// The following can be either a path to the key file or the contents of the file itself
PrivateKey = "ClientPrivateKey.pem"
PrivateKey = "ClientPrivateKey.pem",

// With 4.0.0 version, V2 protocol is supported
MAuthVersion = (MAuthVersion.MWSV2 || MAuthVersion.MWS)

// when ready to disable authentication of V1 protococl
DisableV1 = true
});

using (var client = new HttpClient(signingHandler))
Expand All @@ -120,6 +127,11 @@ public async Task<HttpResponseMessage> SignAndSendRequest(HttpRequestMessage req
}
}
```
With the MAuth V2 protocol, there are two new options `MAuthVersion` and `DisableV1` added for Signing MAuth request.
`MAuthVersion` is passed either as `MAuthVersion.MWSV2` for signing with V2 protocol or `MAuthVersion.MWS` for continue
signing with V1 protocol. By default, `DisableV1` option is set to false (if not included). When we are ready to
disable all the V1 request, then we need to include this disable option as : `DisableV1 = true`.
Signing with V2 protocol supports query string.

The example above is creating a new instance of a `HttpClient` with the handler responsible for signing the
requests and sends the request to its designation. Finally it returns the response from the remote server.
Expand All @@ -130,6 +142,8 @@ The `MAuthSigningOptions` has the following properties to determine the required
| ---- | ----------- |
| **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. |
| **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. |
| **MAuthVersion** | Determines the MAuth version of the request used for signing. This is enumeration value which is `MAuthVersion.MWSV2` for V2 requests. |
| **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`. |

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

Expand All @@ -138,6 +152,9 @@ provided by the Owin and AspNetCore NuGet packages.

The setting and usage is as follows in case of OWIN (in the application's `Startup` class):

With the update of MAuth V2 protocol, the authentication will first check for mauth header with `MWSV2` and if not found then only fallback to authenticate for `MWS` version.
Also, there is options of `DisableV1`, which is `false` by default and when we are ready to disable authentication of V1 protocl i.e. `MWS` version, then we need to pass this option too as `true`.

```C#
using Medidata.MAuth.Owin;

Expand All @@ -154,10 +171,23 @@ public class Startup
options.HideExceptionsAndReturnUnauthorized = true;
options.PrivateKey = "ServerPrivateKey.pem";
options.Bypass = (request) => request.Uri.AbsolutePath.StartsWith("/allowed");

// when ready to disable authentication of V1 protococl
options.DisableV1 = true;
});
}
}
```
For enabling the logging from Owin application, the following configuration will need to be added or updated:
```C#
<configuration>
<system.diagnostics>
<switches>
<add name="Microsoft.Owin" value="Information, Error, Warning" />
</switches>
</system.diagnostics>
</configuration>
```

A similar way can be implemented for ASP.NET Core (also in the `Startup` class):

Expand All @@ -177,6 +207,9 @@ public class Startup
options.HideExceptionsAndReturnUnauthorized = true;
options.PrivateKey = "ServerPrivateKey.pem";
options.Bypass = (request) => request.Uri.AbsolutePath.StartsWith("/allowed");

// when ready to disable authentication of V1 protococl
options.DisableV1 = true;
});
}
}
Expand All @@ -193,6 +226,7 @@ The middlewares take an `MAuthMiddlewareOptions` instance to set up the authenti
| **MAuthServiceRetryPolicy** | The policy for the retry attempts when communicating with the MAuth service. The following policies can be used: `NoRetry` (no retries), `RetryOnce` (one additional attempt), `RetryTwice` (two additional attempts) and `Agressive` (9 additional attempts) - the default value is **RetryOnce**. |
| **HideExceptionsAndReturnUnauthorized** | An optional parameter that determines if the middleware should swallow all exceptions and return an empty HTTP response with a status code Unauthorized (401) in case of any errors (including authentication and validation errors). The default is **true**. |
| **Bypass** | Determines a function which evaluates if a given request should bypass the MAuth authentication. |
| **DisableV1** | Determines the boolean value which controls whether to disable the signing requests with `MAuthVersion.MWS` requests or not. If not supplied, this default value is `false`. |

The **HideExceptionsAndReturnUnauthorized** parameter is useful (if set to **false**) when you have an exception handler
mechanism (for example a logger) in your middleware pipeline. In this case the MAuth middleware won't swallow the
Expand All @@ -209,6 +243,8 @@ should produce **true** as a result, if the given request satisfies the conditio
otherwise it should result **false** therefore an authentication attempt will occur. If no Bypass predicate provided
in the options, every request will be authenticated by default.

When authentication of V1 requests needs to be disabled, then **DisableV1** should be passed as **true**.

### Authenticating Incoming Requests with the WebApi Message Handler

If your application does not use the OWIN or ASP.NET Core middleware infrastructure, but it uses the ASP.NET WebAPI
Expand All @@ -232,7 +268,13 @@ public static class WebApiConfig
AuthenticateRequestTimeoutSeconds = 3,
MAuthServiceRetryPolicy = MAuthServiceRetryPolicy.RetryOnce,
HideExceptionsAndReturnUnauthorized = true,
PrivateKey = "ServerPrivateKey.pem"
PrivateKey = "ServerPrivateKey.pem",

// when ready to disable authentication of V1 protococl
options.DisableV1 = true

// if loggerfactory is present
options.loggerfactory = new Microsoft.Extensions.Logging.LoggerFactory(); // or provide the existing loggerfactory
};

config.MessageHandlers.Add(new MAuthAuthenticatingHandler(options));
Expand Down Expand Up @@ -273,21 +315,38 @@ The framework is licensed under the [MIT licensing terms](https://github.com/mds

##### What is the current target .NET Framework version?

The current target is **.NET Framework 4.5.2** - this means that you have to use at least this target framework version
The current target is **.NET Framework 4.6.1** - this means that you have to use at least this target framework version
in your project in order to make Medidata.MAuth work for you.

##### Is there an .NET Standard/Core support?

Yes, for signing outgoing requests you can use the library with any framework which implements
the **.NET Standard 1.4** and onwards; additionally we support the **ASP.NET Core App 1.1** and onwards with a middleware
the **.NET Standard 2.0** and onwards; additionally we support the **ASP.NET Core App 2.0** and onwards with a middleware
for authenticating the incoming requests.

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

In the latest version of 4.0.0, we are using the available dotnet security [System.Security.Cryptography] which works
for both **.NET Framework 4.6.1** and **.NET Standard 2.0** in case of V2 protocol. However, for the continue support
of V1 protcol, we are still maintaining the BouncyCastle library as mentioned below.

On the .NET Framework side (WebAPI, Owin, Core) we are using the latest version (as of date 1.81) of the
[BouncyCastle](https://github.com/bcgit/bc-csharp) library; on the .NET Standard side (Core, AspNetCore) we are using
the portable fork of the [BouncyCastle](https://github.com/onovotny/BouncyCastle-PCL) library.

##### What are the major changes in the 4.0.0 version?

In this version we have added support for V2 protocol which uses `MCC-Authentication` as MAuthHeader and `MCC-Time` as
MAuthTimeHeader. And, this V2 protocol supports for signing and authenticating url with query string parameters.
For Signing, we added two new options in `MAuthSigningOptions`: `MAuthVersion` which is mandatory and takes enumeration
value of `MAuthVersion.MWSV2` for V2 protocol or `MAuthVersion.MWS` for continue of using V1 protocol.
Another option `DisableV1` is `false` by default if not provided. But, it is needed to provide as `true` when the client
need to sign on by no more supporting V1 protocol.

Also while authentication, the logic defaults to check for V2 protcol header `MWSV2` and if fails then only fallback to
check for V1 protocol header for `MWS`. Also, `MAuthOptionsBase` includes new option as `DisableV1` which is `false` by
default and need to be passed as `true` if the authenticating client no longer wants to support V1 protocol.

##### What are the major changes in the 2.0.0 version?

In this version we have only one major and a minor change: from this version the `MAuthSigningHandler` is accepting an
Expand All @@ -314,5 +373,3 @@ This policy will make the number of requests to the MAuth service to an overall
to receive a successful response from the MAuth service is gradually decreasing by the number of attempts (the more
the clients are sending requests to a presumably overloaded server the less the chance for a successful response) -
therefore we do not recommend to use this policy in any production scenario.


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 xunit
dotnet test

Pop-Location

Expand Down
1 change: 1 addition & 0 deletions src/Medidata.MAuth.AspNetCore/MAuthAspNetCoreExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Medidata.MAuth.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.Extensions.Logging;

namespace Medidata.MAuth.AspNetCore
{
Expand Down
25 changes: 21 additions & 4 deletions src/Medidata.MAuth.AspNetCore/MAuthMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,43 @@
using System.Threading.Tasks;
using Medidata.MAuth.Core;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;

namespace Medidata.MAuth.AspNetCore
{
/// <summary>
/// Enables the middleware for the aspnet core applications.
/// </summary>
internal class MAuthMiddleware
{
private readonly MAuthMiddlewareOptions options;
private readonly MAuthAuthenticator authenticator;
private readonly RequestDelegate next;

public MAuthMiddleware(RequestDelegate next, MAuthMiddlewareOptions options)
/// <summary>
/// Creates a new <see cref="MAuthMiddleware"/>
/// </summary>
/// <param name="next">The <see cref="RequestDelegate"/> representing the next middleware in the pipeline.</param>
/// <param name="options">The <see cref="MAuthMiddlewareOptions"/> representing the options for the middleware.</param>
/// <param name="loggerFactory">The <see cref="ILoggerFactory"/> representing the factory that used to create logger instances.</param>
public MAuthMiddleware(RequestDelegate next, MAuthMiddlewareOptions options, ILoggerFactory loggerFactory)
{
this.next = next;
this.options = options;
this.authenticator = new MAuthAuthenticator(options);
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
ILogger logger = loggerFactory.CreateLogger<MAuthMiddleware>();
this.authenticator = new MAuthAuthenticator(options, logger);
}

/// <summary>
/// Invokes the logic of the middleware.
/// </summary>
/// <param name="context"> The <see cref="HttpContext"/>.</param>
/// <returns>A <see cref="Task"/> that completes when the middleware has completed processing.</returns>
public async Task Invoke(HttpContext context)
{
context.Request.EnableRewind();
context.Request.EnableBuffering();

if (!options.Bypass(context.Request) &&
!await context.TryAuthenticate(authenticator, options.HideExceptionsAndReturnUnauthorized))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@
<Import Project="..\..\build\common.props" />

<PropertyGroup>
<TargetFramework>netcoreapp1.1</TargetFramework>
<TargetFramework>netcoreapp2.0</TargetFramework>
<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>
<AssemblyTitle>Medidata.MAuth.AspNetCore</AssemblyTitle>
<AssemblyName>Medidata.MAuth.AspNetCore</AssemblyName>
<PackageTags>medidata;mauth;hmac;authentication;core;aspnetcore;middleware;webapi</PackageTags>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Http" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="1.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Http" Version="2.2.2" />
<PackageReference Include="Microsoft.AspNetCore.Http.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
</ItemGroup>

<ItemGroup>
Expand Down
19 changes: 16 additions & 3 deletions src/Medidata.MAuth.Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,32 @@ internal static class Constants
"(?:[0-9a-zA-Z+/]{4})*" +
"(?:[0-9a-zA-Z+/]{2}==|[0-9a-zA-Z+/]{3}=)" +
"?" +
")$"
")$", RegexOptions.Compiled
);

public static readonly string MAuthHeaderKey = "X-MWS-Authentication";

public static readonly string MAuthTimeHeaderKey = "X-MWS-Time";

public static readonly string MAuthTokenRequestPath = "/mauth/v1/security_tokens/";

public static readonly string KeyNormalizeLinesStartRegexPattern = "^(?<begin>-----BEGIN [A-Z ]+[-]+)";

public static readonly string KeyNormalizeLinesEndRegexPattern = "(?<end>-----END [A-Z ]+[-]+)$";

public static readonly byte[] NewLine = Encoding.UTF8.GetBytes("\n");

public static readonly Regex AuthenticationHeaderRegexV2 = new Regex(
"^MWSV2 " +
"(?<uuid>[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})" +
":" +
"(?<payload>" +
"(?:[0-9a-zA-Z+/]{4})*" +
"(?:[0-9a-zA-Z+/]{2}==|[0-9a-zA-Z+/]{3}=)" +
"?" +
");$", RegexOptions.Compiled
);

public static readonly string MAuthHeaderKeyV2 = "MCC-Authentication";

public static readonly string MAuthTimeHeaderKeyV2 = "MCC-Time";
}
}
24 changes: 24 additions & 0 deletions src/Medidata.MAuth.Core/Exceptions/InvalidVersionException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Medidata.MAuth.Core.Exceptions
{
/// <summary>
/// The exception that is thrown when version no longer allowed is passed.
/// </summary>
public class InvalidVersionException : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="InvalidVersionException"/> class with the specified message.
/// </summary>
/// <param name="message">A message that describes the invalid version failure.</param>
public InvalidVersionException(string message) : base(message) { }

/// <summary>
/// Initializes a new instance of the <see cref="InvalidVersionException"/> class with the specified message
/// and inner exception.
/// </summary>
/// <param name="message">A message that describes the invalid version failure.</param>
/// <param name="innerException">An exception that is the cause of the current exception.</param>
public InvalidVersionException(string message, Exception innerException) : base(message, innerException) { }
}
}
18 changes: 18 additions & 0 deletions src/Medidata.MAuth.Core/IMAuthCore.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Net.Http;
using System.Threading.Tasks;

namespace Medidata.MAuth.Core
{
internal interface IMAuthCore
{
Task<HttpRequestMessage> Sign(HttpRequestMessage request, MAuthSigningOptions options);

bool Verify(byte[] signedData, byte[] signature, string publicKey);

Task<byte[]> GetSignature(HttpRequestMessage request, AuthenticationInfo authInfo);

string GetMAuthTokenRequestPath();

(string mAuthHeaderKey, string mAuthTimeHeaderKey) GetHeaderKeys();
}
}
Loading

0 comments on commit 8f1d3cc

Please sign in to comment.