Skip to content

Commit

Permalink
Simplify authentication
Browse files Browse the repository at this point in the history
This PR moves authentication configuration behind a single property and
interface.

Prior to this we had two mutually exclusive properties for basic auth
and cloud api keys.

If we want to support other projects and different authentication
schemes this needs to be more open.
  • Loading branch information
Mpdreamz committed Oct 29, 2020
1 parent 3c65776 commit 24eb2ee
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 265 deletions.
6 changes: 6 additions & 0 deletions nuget.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<packageSources>
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" />
</packageSources>
</configuration>
52 changes: 15 additions & 37 deletions src/Elastic.Transport/Components/Connection/HttpConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -286,53 +286,31 @@ protected virtual void SetAuthenticationIfNeeded(HttpRequestMessage requestMessa
return;
}

// Api Key authentication takes precedence
var apiKeySet = SetApiKeyAuthenticationIfNeeded(requestMessage, requestData);

if (!apiKeySet)
SetBasicAuthenticationIfNeeded(requestMessage, requestData);
SetConfiguredAuthenticationHeaderIfNeeded(requestMessage, requestData);
}

private static bool SetApiKeyAuthenticationIfNeeded(HttpRequestMessage requestMessage, RequestData requestData)
{
// ApiKey auth credentials take the following precedence (highest -> lowest):
// 1 - Specified on the request (highest precedence)
// 2 - Specified at the global IConnectionSettings level


string apiKey = null;
if (requestData.ApiKeyAuthenticationCredentials != null)
apiKey = requestData.ApiKeyAuthenticationCredentials.Base64EncodedApiKey.CreateString();

if (string.IsNullOrWhiteSpace(apiKey))
return false;

requestMessage.Headers.Authorization = new AuthenticationHeaderValue("ApiKey", apiKey);
return true;

}

private static void SetBasicAuthenticationIfNeeded(HttpRequestMessage requestMessage, RequestData requestData)
private static void SetConfiguredAuthenticationHeaderIfNeeded(HttpRequestMessage requestMessage, RequestData requestData)
{
// Basic auth credentials take the following precedence (highest -> lowest):
// 1 - Specified on the request (highest precedence)
// 2 - Specified at the global IConnectionSettings level
// 3 - Specified with the URI (lowest precedence)
// 1 - Specified with the URI (highest precedence)
// 2 - Specified on the request
// 3 - Specified at the global IConnectionSettings level (lowest precedence)

string userInfo = null;
string value = null;
string key = null;
if (!requestData.Uri.UserInfo.IsNullOrEmpty())
userInfo = Uri.UnescapeDataString(requestData.Uri.UserInfo);
else if (requestData.BasicAuthorizationCredentials != null)
{
userInfo =
$"{requestData.BasicAuthorizationCredentials.Username}:{requestData.BasicAuthorizationCredentials.Password.CreateString()}";
value = BasicAuthentication.GetBase64String(Uri.UnescapeDataString(requestData.Uri.UserInfo));
key = BasicAuthentication.Base64Header;
}

if (!userInfo.IsNullOrEmpty())
else if (requestData.AuthenticationHeader != null && requestData.AuthenticationHeader.TryGetHeader(out var v))
{
var credentials = Convert.ToBase64String(Encoding.UTF8.GetBytes(userInfo!));
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials);
value = v;
key = requestData.AuthenticationHeader.Header;
}

if (value.IsNullOrEmpty()) return;
requestMessage.Headers.Authorization = new AuthenticationHeaderValue(key, value);
}

private static HttpRequestMessage CreateRequestMessage(RequestData requestData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,14 @@ protected virtual void SetProxyIfNeeded(HttpWebRequest request, RequestData requ
/// <summary> Hook for subclasses to set authentication on <paramref name="request"/></summary>
protected virtual void SetAuthenticationIfNeeded(RequestData requestData, HttpWebRequest request)
{
// Api Key authentication takes precedence
var apiKeySet = SetApiKeyAuthenticationIfNeeded(request, requestData);

if (!apiKeySet)
SetBasicAuthenticationIfNeeded(request, requestData);
//If user manually specifies an Authorization Header give it preference
if (requestData.Headers.HasKeys() && requestData.Headers.AllKeys.Contains("Authorization"))
{
var header = requestData.Headers["Authorization"];
request.Headers["Authorization"] = header;
return;
}
SetBasicAuthenticationIfNeeded(request, requestData);
}

private static void SetBasicAuthenticationIfNeeded(HttpWebRequest request, RequestData requestData)
Expand All @@ -325,37 +328,28 @@ private static void SetBasicAuthenticationIfNeeded(HttpWebRequest request, Reque
// 2 - Specified at the global IConnectionSettings level
// 3 - Specified with the URI (lowest precedence)

string userInfo = null;
if (!string.IsNullOrEmpty(requestData.Uri.UserInfo))
userInfo = Uri.UnescapeDataString(requestData.Uri.UserInfo);
else if (requestData.BasicAuthorizationCredentials != null)
{
userInfo =
$"{requestData.BasicAuthorizationCredentials.Username}:{requestData.BasicAuthorizationCredentials.Password.CreateString()}";
}

if (string.IsNullOrWhiteSpace(userInfo))
return;

request.Headers["Authorization"] = $"Basic {Convert.ToBase64String(Encoding.UTF8.GetBytes(userInfo))}";
}

private static bool SetApiKeyAuthenticationIfNeeded(HttpWebRequest request, RequestData requestData)
{
// ApiKey auth credentials take the following precedence (highest -> lowest):
// 1 - Specified on the request (highest precedence)
// 2 - Specified at the global IConnectionSettings level

string apiKey = null;
if (requestData.ApiKeyAuthenticationCredentials != null)
apiKey = requestData.ApiKeyAuthenticationCredentials.Base64EncodedApiKey.CreateString();
// Basic auth credentials take the following precedence (highest -> lowest):
// 1 - Specified with the URI (highest precedence)
// 2 - Specified on the request
// 3 - Specified at the global IConnectionSettings level (lowest precedence)

if (string.IsNullOrWhiteSpace(apiKey))
return false;
string value = null;
string key = null;
if (!requestData.Uri.UserInfo.IsNullOrEmpty())
{
value = BasicAuthentication.GetBase64String(Uri.UnescapeDataString(requestData.Uri.UserInfo));
key = BasicAuthentication.Base64Header;
}
else if (requestData.AuthenticationHeader != null && requestData.AuthenticationHeader.TryGetHeader(out var v))
{
value = v;
key = requestData.AuthenticationHeader.Header;
}

request.Headers["Authorization"] = $"ApiKey {apiKey}";
return true;
if (value.IsNullOrEmpty()) return;

request.Headers["Authorization"] = $"{key} {value}";
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,32 +37,8 @@ public class CloudConnectionPool : SingleNodeConnectionPool
/// </param>
/// <param name="credentials"></param>
/// <param name="dateTimeProvider">Optionally inject an instance of <see cref="IDateTimeProvider"/> used to set <see cref="IConnectionPool.LastUpdate"/></param>
public CloudConnectionPool(string cloudId, BasicAuthenticationCredentials credentials, IDateTimeProvider dateTimeProvider = null) : this(ParseCloudId(cloudId), dateTimeProvider) =>
BasicCredentials = credentials;

/// <summary>
/// An <see cref="IConnectionPool"/> implementation that can be seeded with a cloud id
/// and will signal the right defaults for the client to use for Elastic Cloud to <see cref="ITransportConfigurationValues"/>.
///
/// <para>Read more about Elastic Cloud Id here</para>
/// <para>https://www.elastic.co/guide/en/cloud/current/ec-cloud-id.html</para>
/// </summary>
/// <param name="cloudId">
/// The Cloud Id, this is available on your cluster's dashboard and is a string in the form of <code>cluster_name:base_64_encoded_string</code>
/// <para>Base64 encoded string contains the following tokens in order separated by $:</para>
/// <para>* Host Name (mandatory)</para>
/// <para>* Elasticsearch UUID (mandatory)</para>
/// <para>* Kibana UUID</para>
/// <para>* APM UUID</para>
/// <para></para>
/// <para> We then use these tokens to create the URI to your Elastic Cloud cluster!</para>
/// <para></para>
/// <para> Read more here: https://www.elastic.co/guide/en/cloud/current/ec-cloud-id.html</para>
/// </param>
/// <param name="credentials"></param>
/// <param name="dateTimeProvider">Optionally inject an instance of <see cref="IDateTimeProvider"/> used to set <see cref="IConnectionPool.LastUpdate"/></param>
public CloudConnectionPool(string cloudId, ApiKeyAuthenticationCredentials credentials, IDateTimeProvider dateTimeProvider = null) : this(ParseCloudId(cloudId), dateTimeProvider) =>
ApiKeyCredentials = credentials;
public CloudConnectionPool(string cloudId, IAuthenticationHeader credentials, IDateTimeProvider dateTimeProvider = null) : this(ParseCloudId(cloudId), dateTimeProvider) =>
AuthenticationHeader = credentials;

private CloudConnectionPool(ParsedCloudId parsedCloudId, IDateTimeProvider dateTimeProvider = null) : base(parsedCloudId.Uri, dateTimeProvider) =>
ClusterName = parsedCloudId.Name;
Expand All @@ -71,11 +47,8 @@ private CloudConnectionPool(ParsedCloudId parsedCloudId, IDateTimeProvider dateT
// ReSharper disable once UnusedAutoPropertyAccessor.Local
private string ClusterName { get; }

/// <summary> Read-only access to the basic authentication credentials that were passed in </summary>
public BasicAuthenticationCredentials BasicCredentials { get; }

/// <summary> Read-only access to the api key authentication credentials that were passed in </summary>
public ApiKeyAuthenticationCredentials ApiKeyCredentials { get; }
/// <inheritdoc cref="IAuthenticationHeader"/>
public IAuthenticationHeader AuthenticationHeader { get; }

private readonly struct ParsedCloudId
{
Expand Down Expand Up @@ -121,10 +94,6 @@ private static ParsedCloudId ParseCloudId(string cloudId)
}

/// <summary> Allows subclasses to hook into the parents dispose </summary>
protected override void DisposeManagedResources()
{
ApiKeyCredentials?.Dispose();
BasicCredentials?.Dispose();
}
protected override void DisposeManagedResources() => AuthenticationHeader?.Dispose();
}
}
7 changes: 2 additions & 5 deletions src/Elastic.Transport/Components/Pipeline/RequestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,7 @@ IMemoryStreamFactory memoryStreamFactory
ProxyUsername = global.ProxyUsername;
ProxyPassword = global.ProxyPassword;
DisableAutomaticProxyDetection = global.DisableAutomaticProxyDetection;
BasicAuthorizationCredentials = local?.BasicAuthenticationCredentials ?? global.BasicAuthenticationCredentials;
ApiKeyAuthenticationCredentials = local?.ApiKeyAuthenticationCredentials ?? global.ApiKeyAuthenticationCredentials;
AuthenticationHeader = local?.AuthenticationHeader ?? global.AuthenticationHeader;
AllowedStatusCodes = local?.AllowedStatusCodes ?? EmptyReadOnly<int>.Collection;
ClientCertificates = local?.ClientCertificates ?? global.ClientCertificates;
UserAgent = global.UserAgent;
Expand All @@ -109,9 +108,7 @@ IMemoryStreamFactory memoryStreamFactory
public string Accept { get; }
public IReadOnlyCollection<int> AllowedStatusCodes { get; }

public ApiKeyAuthenticationCredentials ApiKeyAuthenticationCredentials { get; }

public BasicAuthenticationCredentials BasicAuthorizationCredentials { get; }
public IAuthenticationHeader AuthenticationHeader { get; }

public X509CertificateCollection ClientCertificates { get; }
public ITransportConfigurationValues ConnectionSettings { get; }
Expand Down
3 changes: 1 addition & 2 deletions src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,7 @@ IRequestParameters requestParameters
{
PingTimeout = PingTimeout,
RequestTimeout = PingTimeout,
BasicAuthenticationCredentials = _settings.BasicAuthenticationCredentials,
ApiKeyAuthenticationCredentials = _settings.ApiKeyAuthenticationCredentials,
AuthenticationHeader = _settings.AuthenticationHeader,
EnableHttpPipelining = RequestConfiguration?.EnableHttpPipelining ?? _settings.HttpPipeliningEnabled,
ForceNode = RequestConfiguration?.ForceNode
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,8 @@ namespace Elastic.Transport
/// </summary>
public interface ITransportConfigurationValues : IDisposable
{
/// <summary>
/// Basic access authorization credentials to specify with all requests.
/// </summary>
/// <remarks>
/// Cannot be used in conjuction with <see cref="ApiKeyAuthenticationCredentials"/>
/// </remarks>
BasicAuthenticationCredentials BasicAuthenticationCredentials { get; }

/// <summary>
/// Api Key authorization credentials to specify with all requests.
/// </summary>
/// <remarks>
/// Cannot be used in conjuction with <see cref="BasicAuthenticationCredentials"/>
/// </remarks>
ApiKeyAuthenticationCredentials ApiKeyAuthenticationCredentials { get; }
/// <inheritdoc cref="IAuthenticationHeader"/>
IAuthenticationHeader AuthenticationHeader { get; }

/// <summary> Provides a semaphoreslim to transport implementations that need to limit access to a resource</summary>
SemaphoreSlim BootstrapLock { get; }
Expand Down
68 changes: 8 additions & 60 deletions src/Elastic.Transport/Configuration/RequestConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,9 @@ public interface IRequestConfiguration
IReadOnlyCollection<int> AllowedStatusCodes { get; set; }

/// <summary>
/// Basic access authorization credentials to specify with this request.
/// Overrides any credentials that are set at the global IConnectionSettings level.
/// Provide an authentication header override for this request
/// </summary>
/// <remarks>
/// Cannot be used in conjunction with <see cref="ApiKeyAuthenticationCredentials"/>
/// </remarks>
BasicAuthenticationCredentials BasicAuthenticationCredentials { get; set; }

/// <summary>
/// An API-key authorization credentials to specify with this request.
/// Overrides any credentials that are set at the global IConnectionSettings level.
/// </summary>
/// <remarks>
/// Cannot be used in conjunction with <see cref="BasicAuthenticationCredentials"/>
/// </remarks>
ApiKeyAuthenticationCredentials ApiKeyAuthenticationCredentials { get; set; }
IAuthenticationHeader AuthenticationHeader { get; set; }

/// <summary>
/// Use the following client certificates to authenticate this single request
Expand Down Expand Up @@ -140,9 +127,7 @@ public class RequestConfiguration : IRequestConfiguration
/// <inheritdoc />
public IReadOnlyCollection<int> AllowedStatusCodes { get; set; }
/// <inheritdoc />
public BasicAuthenticationCredentials BasicAuthenticationCredentials { get; set; }
/// <inheritdoc />
public ApiKeyAuthenticationCredentials ApiKeyAuthenticationCredentials { get; set; }
public IAuthenticationHeader AuthenticationHeader { get; set; }
/// <inheritdoc />
public X509CertificateCollection ClientCertificates { get; set; }
/// <inheritdoc />
Expand Down Expand Up @@ -195,8 +180,7 @@ public RequestConfigurationDescriptor(IRequestConfiguration config)
Self.DisablePing = config?.DisablePing;
Self.DisableDirectStreaming = config?.DisableDirectStreaming;
Self.AllowedStatusCodes = config?.AllowedStatusCodes;
Self.BasicAuthenticationCredentials = config?.BasicAuthenticationCredentials;
Self.ApiKeyAuthenticationCredentials = config?.ApiKeyAuthenticationCredentials;
Self.AuthenticationHeader = config?.AuthenticationHeader;
Self.EnableHttpPipelining = config?.EnableHttpPipelining ?? true;
Self.RunAs = config?.RunAs;
Self.ClientCertificates = config?.ClientCertificates;
Expand All @@ -210,8 +194,7 @@ public RequestConfigurationDescriptor(IRequestConfiguration config)

string IRequestConfiguration.Accept { get; set; }
IReadOnlyCollection<int> IRequestConfiguration.AllowedStatusCodes { get; set; }
BasicAuthenticationCredentials IRequestConfiguration.BasicAuthenticationCredentials { get; set; }
ApiKeyAuthenticationCredentials IRequestConfiguration.ApiKeyAuthenticationCredentials { get; set; }
IAuthenticationHeader IRequestConfiguration.AuthenticationHeader { get; set; }
X509CertificateCollection IRequestConfiguration.ClientCertificates { get; set; }
string IRequestConfiguration.ContentType { get; set; }
bool? IRequestConfiguration.DisableDirectStreaming { get; set; }
Expand Down Expand Up @@ -329,45 +312,10 @@ public RequestConfigurationDescriptor MaxRetries(int retry)
return this;
}

/// <inheritdoc cref="IRequestConfiguration.BasicAuthenticationCredentials"/>
public RequestConfigurationDescriptor BasicAuthentication(string userName, string password)
{
Self.BasicAuthenticationCredentials = new BasicAuthenticationCredentials(userName, password);
return this;
}

/// <inheritdoc cref="IRequestConfiguration.BasicAuthenticationCredentials"/>
public RequestConfigurationDescriptor BasicAuthentication(string userName, SecureString password)
{
Self.BasicAuthenticationCredentials = new BasicAuthenticationCredentials(userName, password);
return this;
}

/// <inheritdoc cref="IRequestConfiguration.ApiKeyAuthenticationCredentials"/>
public RequestConfigurationDescriptor ApiKeyAuthentication(string id, string apiKey)
{
Self.ApiKeyAuthenticationCredentials = new ApiKeyAuthenticationCredentials(id, apiKey);
return this;
}

/// <inheritdoc cref="IRequestConfiguration.ApiKeyAuthenticationCredentials"/>
public RequestConfigurationDescriptor ApiKeyAuthentication(string id, SecureString apiKey)
{
Self.ApiKeyAuthenticationCredentials = new ApiKeyAuthenticationCredentials(id, apiKey);
return this;
}

/// <inheritdoc cref="IRequestConfiguration.RunAs"/>
public RequestConfigurationDescriptor ApiKeyAuthentication(string base64EncodedApiKey)
{
Self.ApiKeyAuthenticationCredentials = new ApiKeyAuthenticationCredentials(base64EncodedApiKey);
return this;
}

/// <inheritdoc cref="IRequestConfiguration.RunAs"/>
public RequestConfigurationDescriptor ApiKeyAuthentication(SecureString base64EncodedApiKey)
/// <inheritdoc cref="IAuthenticationHeader"/>
public RequestConfigurationDescriptor Authentication(IAuthenticationHeader authentication)
{
Self.ApiKeyAuthenticationCredentials = new ApiKeyAuthenticationCredentials(base64EncodedApiKey);
Self.AuthenticationHeader = authentication;
return this;
}

Expand Down
Loading

0 comments on commit 24eb2ee

Please sign in to comment.