Skip to content

Commit

Permalink
Merge branch 'users/ealsur/gwnullref' of https://github.com/Azure/azu…
Browse files Browse the repository at this point in the history
…re-cosmos-dotnet-v3 into users/ealsur/gwnullref
  • Loading branch information
ealsur committed Sep 25, 2020
2 parents aec0639 + cb30bf6 commit 5c37a95
Show file tree
Hide file tree
Showing 37 changed files with 1,404 additions and 580 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Globalization;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Collections;

internal abstract class AuthorizationTokenProvider : ICosmosAuthorizationTokenProvider, IAuthorizationTokenProvider, IDisposable
{
public async Task AddSystemAuthorizationHeaderAsync(
DocumentServiceRequest request,
string federationId,
string verb,
string resourceId)
{
request.Headers[HttpConstants.HttpHeaders.XDate] = DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture);

request.Headers[HttpConstants.HttpHeaders.Authorization] = (await this.GetUserAuthorizationAsync(
resourceId ?? request.ResourceAddress,
PathsHelper.GetResourcePath(request.ResourceType),
verb,
request.Headers,
request.RequestAuthorizationTokenType)).token;
}

public abstract ValueTask AddAuthorizationHeaderAsync(
INameValueCollection headersCollection,
Uri requestAddress,
string verb,
AuthorizationTokenType tokenType);

public abstract ValueTask<(string token, string payload)> GetUserAuthorizationAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType);

public abstract ValueTask<string> GetUserAuthorizationTokenAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType,
CosmosDiagnosticsContext diagnosticsContext);

public abstract void TraceUnauthorized(
DocumentClientException dce,
string authorizationToken,
string payload);

public static AuthorizationTokenProvider CreateWithResourceTokenOrAuthKey(string authKeyOrResourceToken)
{
if (AuthorizationHelper.IsResourceToken(authKeyOrResourceToken))
{
return new AuthorizationTokenProviderResourceToken(authKeyOrResourceToken);
}
else
{
return new AuthorizationTokenProviderMasterKey(authKeyOrResourceToken);
}
}

public abstract void Dispose();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Globalization;
using System.Net;
using System.Net.Http;
using System.Security;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Core.Trace;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Collections;

internal sealed class AuthorizationTokenProviderMasterKey : AuthorizationTokenProvider
{
////The MAC signature found in the HTTP request is not the same as the computed signature.Server used following string to sign
////The input authorization token can't serve the request. Please check that the expected payload is built as per the protocol, and check the key being used. Server used the following payload to sign
private const string MacSignatureString = "to sign";
private const string EnableAuthFailureTracesConfig = "enableAuthFailureTraces";
private readonly Lazy<bool> enableAuthFailureTraces;
private readonly IComputeHash authKeyHashFunction;
private bool isDisposed = false;

public AuthorizationTokenProviderMasterKey(IComputeHash computeHash)
{
this.authKeyHashFunction = computeHash ?? throw new ArgumentNullException(nameof(computeHash));
this.enableAuthFailureTraces = new Lazy<bool>(() =>
{
#if NETSTANDARD20
// GetEntryAssembly returns null when loaded from native netstandard2.0
if (System.Reflection.Assembly.GetEntryAssembly() == null)
{
return false;
}
#endif
string enableAuthFailureTracesString = System.Configuration.ConfigurationManager.AppSettings[EnableAuthFailureTracesConfig];
if (string.IsNullOrEmpty(enableAuthFailureTracesString) ||
!bool.TryParse(enableAuthFailureTracesString, out bool enableAuthFailureTracesFlag))
{
return false;
}
return enableAuthFailureTracesFlag;
});
}

public AuthorizationTokenProviderMasterKey(SecureString authKey)
: this(new SecureStringHMACSHA256Helper(authKey))
{
}

public AuthorizationTokenProviderMasterKey(string authKey)
: this(new StringHMACSHA256Hash(authKey))
{
}

public override ValueTask<(string token, string payload)> GetUserAuthorizationAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType)
{
// this is masterkey authZ
headers[HttpConstants.HttpHeaders.XDate] = DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture);

string authorizationToken = AuthorizationHelper.GenerateKeyAuthorizationSignature(
requestVerb,
resourceAddress,
resourceType,
headers,
this.authKeyHashFunction,
out AuthorizationHelper.ArrayOwner arrayOwner);

using (arrayOwner)
{
string payload = null;
if (arrayOwner.Buffer.Count > 0)
{
payload = Encoding.UTF8.GetString(arrayOwner.Buffer.Array, arrayOwner.Buffer.Offset, (int)arrayOwner.Buffer.Count);
}

return new ValueTask<(string token, string payload)>((authorizationToken, payload));
}
}

public override ValueTask<string> GetUserAuthorizationTokenAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType,
CosmosDiagnosticsContext diagnosticsContext)
{
// this is masterkey authZ
headers[HttpConstants.HttpHeaders.XDate] = DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture);

string authorizationToken = AuthorizationHelper.GenerateKeyAuthorizationSignature(
requestVerb,
resourceAddress,
resourceType,
headers,
this.authKeyHashFunction,
out AuthorizationHelper.ArrayOwner arrayOwner);

using (arrayOwner)
{
return new ValueTask<string>(authorizationToken);
}
}

public override ValueTask AddAuthorizationHeaderAsync(
INameValueCollection headersCollection,
Uri requestAddress,
string verb,
AuthorizationTokenType tokenType)
{
string dateTime = DateTime.UtcNow.ToString("r", CultureInfo.InvariantCulture);
headersCollection[HttpConstants.HttpHeaders.XDate] = dateTime;

string token = AuthorizationHelper.GenerateKeyAuthorizationSignature(
verb,
requestAddress,
headersCollection,
this.authKeyHashFunction);

headersCollection.Add(HttpConstants.HttpHeaders.Authorization, token);
return default;
}

public override void TraceUnauthorized(
DocumentClientException dce,
string authorizationToken,
string payload)
{
if (payload != null
&& dce.Message != null
&& dce.StatusCode.HasValue
&& dce.StatusCode.Value == HttpStatusCode.Unauthorized
&& dce.Message.Contains(AuthorizationTokenProviderMasterKey.MacSignatureString))
{
// The following code is added such that we get trace data on unexpected 401/HMAC errors and it is
// disabled by default. The trace will be trigger only when "enableAuthFailureTraces" named configuration
// is set to true (currently true for CTL runs).
// For production we will work directly with specific customers in order to enable this configuration.
string normalizedPayload = AuthorizationTokenProviderMasterKey.NormalizeAuthorizationPayload(payload);
if (this.enableAuthFailureTraces.Value)
{
string tokenFirst5 = HttpUtility.UrlDecode(authorizationToken).Split('&')[2].Split('=')[1].Substring(0, 5);
ulong authHash = 0;
if (this.authKeyHashFunction?.Key != null)
{
byte[] bytes = Encoding.UTF8.GetBytes(this.authKeyHashFunction?.Key?.ToString());
authHash = Documents.Routing.MurmurHash3.Hash64(bytes, bytes.Length);
}
DefaultTrace.TraceError("Un-expected authorization payload mis-match. Actual payload={0}, token={1}..., hash={2:X}..., error={3}",
normalizedPayload, tokenFirst5, authHash, dce.Message);
}
else
{
DefaultTrace.TraceError("Un-expected authorization payload mis-match. Actual {0} service expected {1}", normalizedPayload, dce.Message);
}
}
}

public override void Dispose()
{
if (!this.isDisposed)
{
this.authKeyHashFunction.Dispose();
this.isDisposed = true;
}
}

private static string NormalizeAuthorizationPayload(string input)
{
const int expansionBuffer = 12;
StringBuilder builder = new StringBuilder(input.Length + expansionBuffer);
for (int i = 0; i < input.Length; i++)
{
switch (input[i])
{
case '\n':
builder.Append("\\n");
break;
case '/':
builder.Append("\\/");
break;
default:
builder.Append(input[i]);
break;
}
}

return builder.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------

namespace Microsoft.Azure.Cosmos
{
using System;
using System.Net.Http;
using System.Threading.Tasks;
using Microsoft.Azure.Cosmos.Core.Trace;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Collections;

internal sealed class AuthorizationTokenProviderResourceToken : AuthorizationTokenProvider
{
private readonly string urlEncodedAuthKeyResourceToken;
private readonly ValueTask<string> urlEncodedAuthKeyResourceTokenValueTask;
private readonly ValueTask<(string, string)> urlEncodedAuthKeyResourceTokenValueTaskWithPayload;
private readonly ValueTask defaultValueTask;

public AuthorizationTokenProviderResourceToken(
string authKeyResourceToken)
{
this.urlEncodedAuthKeyResourceToken = HttpUtility.UrlEncode(authKeyResourceToken);
this.urlEncodedAuthKeyResourceTokenValueTask = new ValueTask<string>(this.urlEncodedAuthKeyResourceToken);
this.urlEncodedAuthKeyResourceTokenValueTaskWithPayload = new ValueTask<(string, string)>((this.urlEncodedAuthKeyResourceToken, default));
this.defaultValueTask = new ValueTask();
}

public override ValueTask<(string token, string payload)> GetUserAuthorizationAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType)
{
// If the input auth token is a resource token, then use it as a bearer-token.
return this.urlEncodedAuthKeyResourceTokenValueTaskWithPayload;
}

public override ValueTask<string> GetUserAuthorizationTokenAsync(
string resourceAddress,
string resourceType,
string requestVerb,
INameValueCollection headers,
AuthorizationTokenType tokenType,
CosmosDiagnosticsContext diagnosticsContext)
{
// If the input auth token is a resource token, then use it as a bearer-token.
return this.urlEncodedAuthKeyResourceTokenValueTask;
}

public override ValueTask AddAuthorizationHeaderAsync(
INameValueCollection headersCollection,
Uri requestAddress,
string verb,
AuthorizationTokenType tokenType)
{
headersCollection.Add(HttpConstants.HttpHeaders.Authorization, this.urlEncodedAuthKeyResourceToken);
return this.defaultValueTask;
}

public override void TraceUnauthorized(
DocumentClientException dce,
string authorizationToken,
string payload)
{
DefaultTrace.TraceError($"Un-expected authorization for resource token. {dce.Message}");
}

public override void Dispose()
{
}
}
}
Loading

0 comments on commit 5c37a95

Please sign in to comment.