From 24eb2ee7f2ac8f1284c4e55e659d580e6a9938a9 Mon Sep 17 00:00:00 2001 From: Martijn Laarman Date: Thu, 29 Oct 2020 09:35:17 +0100 Subject: [PATCH] Simplify authentication 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. --- nuget.config | 6 ++ .../Components/Connection/HttpConnection.cs | 52 ++++---------- .../Connection/HttpWebRequestConnection.cs | 58 +++++++--------- .../ConnectionPool/CloudConnectionPool.cs | 41 ++--------- .../Components/Pipeline/RequestData.cs | 7 +- .../Components/Pipeline/RequestPipeline.cs | 3 +- .../ITransportConfigurationValues.cs | 17 +---- .../Configuration/RequestConfiguration.cs | 68 +++---------------- .../Configuration/Security/ApiKey.cs | 37 ++++++++++ .../ApiKeyAuthenticationCredentials.cs | 41 ----------- .../Configuration/Security/Base64ApiKey.cs | 47 +++++++++++++ .../BasicAuthenticationCredentials.cs | 35 +++++++--- .../Security/IAuthenticationHeader.cs | 21 ++++++ .../Configuration/TransportConfiguration.cs | 37 +++------- 14 files changed, 205 insertions(+), 265 deletions(-) create mode 100644 nuget.config create mode 100644 src/Elastic.Transport/Configuration/Security/ApiKey.cs delete mode 100644 src/Elastic.Transport/Configuration/Security/ApiKeyAuthenticationCredentials.cs create mode 100644 src/Elastic.Transport/Configuration/Security/Base64ApiKey.cs create mode 100644 src/Elastic.Transport/Configuration/Security/IAuthenticationHeader.cs diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..3f0e003 --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Elastic.Transport/Components/Connection/HttpConnection.cs b/src/Elastic.Transport/Components/Connection/HttpConnection.cs index b7f48b7..05bb440 100644 --- a/src/Elastic.Transport/Components/Connection/HttpConnection.cs +++ b/src/Elastic.Transport/Components/Connection/HttpConnection.cs @@ -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) diff --git a/src/Elastic.Transport/Components/Connection/HttpWebRequestConnection.cs b/src/Elastic.Transport/Components/Connection/HttpWebRequestConnection.cs index 6817e30..a11bbbf 100644 --- a/src/Elastic.Transport/Components/Connection/HttpWebRequestConnection.cs +++ b/src/Elastic.Transport/Components/Connection/HttpWebRequestConnection.cs @@ -311,11 +311,14 @@ protected virtual void SetProxyIfNeeded(HttpWebRequest request, RequestData requ /// Hook for subclasses to set authentication on 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) @@ -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}"; } /// diff --git a/src/Elastic.Transport/Components/ConnectionPool/CloudConnectionPool.cs b/src/Elastic.Transport/Components/ConnectionPool/CloudConnectionPool.cs index 4c0797c..85c5e9a 100644 --- a/src/Elastic.Transport/Components/ConnectionPool/CloudConnectionPool.cs +++ b/src/Elastic.Transport/Components/ConnectionPool/CloudConnectionPool.cs @@ -37,32 +37,8 @@ public class CloudConnectionPool : SingleNodeConnectionPool /// /// /// Optionally inject an instance of used to set - public CloudConnectionPool(string cloudId, BasicAuthenticationCredentials credentials, IDateTimeProvider dateTimeProvider = null) : this(ParseCloudId(cloudId), dateTimeProvider) => - BasicCredentials = credentials; - - /// - /// An implementation that can be seeded with a cloud id - /// and will signal the right defaults for the client to use for Elastic Cloud to . - /// - /// Read more about Elastic Cloud Id here - /// https://www.elastic.co/guide/en/cloud/current/ec-cloud-id.html - /// - /// - /// The Cloud Id, this is available on your cluster's dashboard and is a string in the form of cluster_name:base_64_encoded_string - /// Base64 encoded string contains the following tokens in order separated by $: - /// * Host Name (mandatory) - /// * Elasticsearch UUID (mandatory) - /// * Kibana UUID - /// * APM UUID - /// - /// We then use these tokens to create the URI to your Elastic Cloud cluster! - /// - /// Read more here: https://www.elastic.co/guide/en/cloud/current/ec-cloud-id.html - /// - /// - /// Optionally inject an instance of used to set - 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; @@ -71,11 +47,8 @@ private CloudConnectionPool(ParsedCloudId parsedCloudId, IDateTimeProvider dateT // ReSharper disable once UnusedAutoPropertyAccessor.Local private string ClusterName { get; } - /// Read-only access to the basic authentication credentials that were passed in - public BasicAuthenticationCredentials BasicCredentials { get; } - - /// Read-only access to the api key authentication credentials that were passed in - public ApiKeyAuthenticationCredentials ApiKeyCredentials { get; } + /// + public IAuthenticationHeader AuthenticationHeader { get; } private readonly struct ParsedCloudId { @@ -121,10 +94,6 @@ private static ParsedCloudId ParseCloudId(string cloudId) } /// Allows subclasses to hook into the parents dispose - protected override void DisposeManagedResources() - { - ApiKeyCredentials?.Dispose(); - BasicCredentials?.Dispose(); - } + protected override void DisposeManagedResources() => AuthenticationHeader?.Dispose(); } } diff --git a/src/Elastic.Transport/Components/Pipeline/RequestData.cs b/src/Elastic.Transport/Components/Pipeline/RequestData.cs index 8e295ba..fa512e7 100644 --- a/src/Elastic.Transport/Components/Pipeline/RequestData.cs +++ b/src/Elastic.Transport/Components/Pipeline/RequestData.cs @@ -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.Collection; ClientCertificates = local?.ClientCertificates ?? global.ClientCertificates; UserAgent = global.UserAgent; @@ -109,9 +108,7 @@ IMemoryStreamFactory memoryStreamFactory public string Accept { get; } public IReadOnlyCollection AllowedStatusCodes { get; } - public ApiKeyAuthenticationCredentials ApiKeyAuthenticationCredentials { get; } - - public BasicAuthenticationCredentials BasicAuthorizationCredentials { get; } + public IAuthenticationHeader AuthenticationHeader { get; } public X509CertificateCollection ClientCertificates { get; } public ITransportConfigurationValues ConnectionSettings { get; } diff --git a/src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs b/src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs index af54ca0..a8dd478 100644 --- a/src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs +++ b/src/Elastic.Transport/Components/Pipeline/RequestPipeline.cs @@ -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 }; diff --git a/src/Elastic.Transport/Configuration/ITransportConfigurationValues.cs b/src/Elastic.Transport/Configuration/ITransportConfigurationValues.cs index c69be32..330854c 100644 --- a/src/Elastic.Transport/Configuration/ITransportConfigurationValues.cs +++ b/src/Elastic.Transport/Configuration/ITransportConfigurationValues.cs @@ -19,21 +19,8 @@ namespace Elastic.Transport /// public interface ITransportConfigurationValues : IDisposable { - /// - /// Basic access authorization credentials to specify with all requests. - /// - /// - /// Cannot be used in conjuction with - /// - BasicAuthenticationCredentials BasicAuthenticationCredentials { get; } - - /// - /// Api Key authorization credentials to specify with all requests. - /// - /// - /// Cannot be used in conjuction with - /// - ApiKeyAuthenticationCredentials ApiKeyAuthenticationCredentials { get; } + /// + IAuthenticationHeader AuthenticationHeader { get; } /// Provides a semaphoreslim to transport implementations that need to limit access to a resource SemaphoreSlim BootstrapLock { get; } diff --git a/src/Elastic.Transport/Configuration/RequestConfiguration.cs b/src/Elastic.Transport/Configuration/RequestConfiguration.cs index 2ca19cc..341eeb3 100644 --- a/src/Elastic.Transport/Configuration/RequestConfiguration.cs +++ b/src/Elastic.Transport/Configuration/RequestConfiguration.cs @@ -27,22 +27,9 @@ public interface IRequestConfiguration IReadOnlyCollection AllowedStatusCodes { get; set; } /// - /// 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 /// - /// - /// Cannot be used in conjunction with - /// - BasicAuthenticationCredentials BasicAuthenticationCredentials { get; set; } - - /// - /// An API-key authorization credentials to specify with this request. - /// Overrides any credentials that are set at the global IConnectionSettings level. - /// - /// - /// Cannot be used in conjunction with - /// - ApiKeyAuthenticationCredentials ApiKeyAuthenticationCredentials { get; set; } + IAuthenticationHeader AuthenticationHeader { get; set; } /// /// Use the following client certificates to authenticate this single request @@ -140,9 +127,7 @@ public class RequestConfiguration : IRequestConfiguration /// public IReadOnlyCollection AllowedStatusCodes { get; set; } /// - public BasicAuthenticationCredentials BasicAuthenticationCredentials { get; set; } - /// - public ApiKeyAuthenticationCredentials ApiKeyAuthenticationCredentials { get; set; } + public IAuthenticationHeader AuthenticationHeader { get; set; } /// public X509CertificateCollection ClientCertificates { get; set; } /// @@ -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; @@ -210,8 +194,7 @@ public RequestConfigurationDescriptor(IRequestConfiguration config) string IRequestConfiguration.Accept { get; set; } IReadOnlyCollection 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; } @@ -329,45 +312,10 @@ public RequestConfigurationDescriptor MaxRetries(int retry) return this; } - /// - public RequestConfigurationDescriptor BasicAuthentication(string userName, string password) - { - Self.BasicAuthenticationCredentials = new BasicAuthenticationCredentials(userName, password); - return this; - } - - /// - public RequestConfigurationDescriptor BasicAuthentication(string userName, SecureString password) - { - Self.BasicAuthenticationCredentials = new BasicAuthenticationCredentials(userName, password); - return this; - } - - /// - public RequestConfigurationDescriptor ApiKeyAuthentication(string id, string apiKey) - { - Self.ApiKeyAuthenticationCredentials = new ApiKeyAuthenticationCredentials(id, apiKey); - return this; - } - - /// - public RequestConfigurationDescriptor ApiKeyAuthentication(string id, SecureString apiKey) - { - Self.ApiKeyAuthenticationCredentials = new ApiKeyAuthenticationCredentials(id, apiKey); - return this; - } - - /// - public RequestConfigurationDescriptor ApiKeyAuthentication(string base64EncodedApiKey) - { - Self.ApiKeyAuthenticationCredentials = new ApiKeyAuthenticationCredentials(base64EncodedApiKey); - return this; - } - - /// - public RequestConfigurationDescriptor ApiKeyAuthentication(SecureString base64EncodedApiKey) + /// + public RequestConfigurationDescriptor Authentication(IAuthenticationHeader authentication) { - Self.ApiKeyAuthenticationCredentials = new ApiKeyAuthenticationCredentials(base64EncodedApiKey); + Self.AuthenticationHeader = authentication; return this; } diff --git a/src/Elastic.Transport/Configuration/Security/ApiKey.cs b/src/Elastic.Transport/Configuration/Security/ApiKey.cs new file mode 100644 index 0000000..45bdd4a --- /dev/null +++ b/src/Elastic.Transport/Configuration/Security/ApiKey.cs @@ -0,0 +1,37 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Security; + +namespace Elastic.Transport +{ + /// + /// Credentials for Api Key Authentication + /// + public class ApiKey : IAuthenticationHeader + { + /// + public ApiKey(string apiKey) => Value = apiKey.CreateSecureString(); + + /// + public ApiKey(SecureString apiKey) => Value = apiKey; + + private SecureString Value { get; } + + /// + public virtual string Header { get; } = "Bearer"; + + /// + public bool TryGetHeader(out string value) + { + value = Value.CreateString(); + return true; + } + + /// + public void Dispose() => Value?.Dispose(); + + } +} diff --git a/src/Elastic.Transport/Configuration/Security/ApiKeyAuthenticationCredentials.cs b/src/Elastic.Transport/Configuration/Security/ApiKeyAuthenticationCredentials.cs deleted file mode 100644 index 2018192..0000000 --- a/src/Elastic.Transport/Configuration/Security/ApiKeyAuthenticationCredentials.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to Elasticsearch B.V under one or more agreements. -// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. -// See the LICENSE file in the project root for more information - -using System; -using System.Security; -using System.Text; - -namespace Elastic.Transport -{ - /// - /// Credentials for Api Key Authentication - /// - public class ApiKeyAuthenticationCredentials : IDisposable - { - /// - public ApiKeyAuthenticationCredentials(string id, SecureString apiKey) => - Base64EncodedApiKey = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{id}:{apiKey.CreateString()}")).CreateSecureString(); - - /// - public ApiKeyAuthenticationCredentials(string id, string apiKey) => - Base64EncodedApiKey = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{id}:{apiKey}")).CreateSecureString(); - - /// - public ApiKeyAuthenticationCredentials(string base64EncodedApiKey) => - Base64EncodedApiKey = base64EncodedApiKey.CreateSecureString(); - - /// - public ApiKeyAuthenticationCredentials(SecureString base64EncodedApiKey) => - Base64EncodedApiKey = base64EncodedApiKey; - - /// - /// The Base64 encoded api key with which to authenticate - /// Take the form, id:api_key, which is then base 64 encoded - /// - public SecureString Base64EncodedApiKey { get; } - - /// - public void Dispose() => Base64EncodedApiKey?.Dispose(); - } -} diff --git a/src/Elastic.Transport/Configuration/Security/Base64ApiKey.cs b/src/Elastic.Transport/Configuration/Security/Base64ApiKey.cs new file mode 100644 index 0000000..17d32d1 --- /dev/null +++ b/src/Elastic.Transport/Configuration/Security/Base64ApiKey.cs @@ -0,0 +1,47 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Security; +using System.Text; + +namespace Elastic.Transport +{ + /// + /// Credentials for Api Key Authentication + /// + public class Base64ApiKey : IAuthenticationHeader + { + /// + public Base64ApiKey(string id, SecureString apiKey) => + Value = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{id}:{apiKey.CreateString()}")).CreateSecureString(); + + /// + public Base64ApiKey(string id, string apiKey) => + Value = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{id}:{apiKey}")).CreateSecureString(); + + /// + public Base64ApiKey(string base64EncodedApiKey) => + Value = base64EncodedApiKey.CreateSecureString(); + + /// + public Base64ApiKey(SecureString base64EncodedApiKey) => + Value = base64EncodedApiKey; + + private SecureString Value { get; } + + /// + public void Dispose() => Value?.Dispose(); + + /// + public string Header { get; } = "ApiKey"; + + /// + public bool TryGetHeader(out string value) + { + value = Value.CreateString(); + return true; + } + } +} diff --git a/src/Elastic.Transport/Configuration/Security/BasicAuthenticationCredentials.cs b/src/Elastic.Transport/Configuration/Security/BasicAuthenticationCredentials.cs index 7376b09..074589a 100644 --- a/src/Elastic.Transport/Configuration/Security/BasicAuthenticationCredentials.cs +++ b/src/Elastic.Transport/Configuration/Security/BasicAuthenticationCredentials.cs @@ -4,34 +4,53 @@ using System; using System.Security; +using System.Text; namespace Elastic.Transport { /// Credentials for Basic Authentication - public class BasicAuthenticationCredentials : IDisposable + public class BasicAuthentication : IAuthenticationHeader { - /// - public BasicAuthenticationCredentials(string username, string password) + /// + public BasicAuthentication(string username, string password) { Username = username; - Password = password.CreateSecureString(); + _cachedHeaderValue = GetBase64String($"{Username}:{password}"); } - /// - public BasicAuthenticationCredentials(string username, SecureString password) + /// + public BasicAuthentication(string username, SecureString password) { Username = username; Password = password; } + private readonly string _cachedHeaderValue; + /// The password with which to authenticate - public SecureString Password { get; set; } + private SecureString Password { get; } /// The username with which to authenticate - public string Username { get; set; } + private string Username { get; } /// public void Dispose() => Password?.Dispose(); + /// + public string Header { get; } = Base64Header; + + /// The default http header used for basic authentication + public static string Base64Header { get; } = "Basic"; + + /// + public bool TryGetHeader(out string value) + { + value = _cachedHeaderValue ?? GetBase64String($"{Username}:{Password.CreateString()}"); + return true; + } + + /// Get Base64 representation for string + public static string GetBase64String(string header) => + Convert.ToBase64String(Encoding.UTF8.GetBytes(header)); } } diff --git a/src/Elastic.Transport/Configuration/Security/IAuthenticationHeader.cs b/src/Elastic.Transport/Configuration/Security/IAuthenticationHeader.cs new file mode 100644 index 0000000..37612a0 --- /dev/null +++ b/src/Elastic.Transport/Configuration/Security/IAuthenticationHeader.cs @@ -0,0 +1,21 @@ +using System; + +namespace Elastic.Transport +{ + /// + /// An implementation of describing what http header to use to authenticate with the product. + /// for basic authentication + /// for simple secret token + /// for Elastic Cloud style encoded api keys + /// + public interface IAuthenticationHeader : IDisposable + { + /// The header to use to authenticate the request + public string Header { get; } + + /// + /// If this instance is valid return the header name and value to use for authentication + /// + bool TryGetHeader(out string value); + } +} diff --git a/src/Elastic.Transport/Configuration/TransportConfiguration.cs b/src/Elastic.Transport/Configuration/TransportConfiguration.cs index 7bb4946..6d9a74e 100644 --- a/src/Elastic.Transport/Configuration/TransportConfiguration.cs +++ b/src/Elastic.Transport/Configuration/TransportConfiguration.cs @@ -118,14 +118,14 @@ public TransportConfiguration(Uri uri = null, IProductRegistration productRegist /// Sets up the client to communicate to Elastic Cloud using , /// documentation for more information on how to obtain your Cloud Id /// - public TransportConfiguration(string cloudId, BasicAuthenticationCredentials credentials, IProductRegistration productRegistration = null) + public TransportConfiguration(string cloudId, BasicAuthentication credentials, IProductRegistration productRegistration = null) : this(new CloudConnectionPool(cloudId, credentials), productRegistration: productRegistration) { } /// /// Sets up the client to communicate to Elastic Cloud using , /// documentation for more information on how to obtain your Cloud Id /// - public TransportConfiguration(string cloudId, ApiKeyAuthenticationCredentials credentials, IProductRegistration productRegistration = null) + public TransportConfiguration(string cloudId, Base64ApiKey credentials, IProductRegistration productRegistration = null) : this(new CloudConnectionPool(cloudId, credentials), productRegistration: productRegistration) { } /// @@ -156,8 +156,7 @@ public abstract class TransportConfigurationBase : ITransportConfigurationVal private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); private readonly UrlFormatter _urlFormatter; - private BasicAuthenticationCredentials _basicAuthCredentials; - private ApiKeyAuthenticationCredentials _apiKeyAuthCredentials; + private IAuthenticationHeader _authenticationHeader; private X509CertificateCollection _clientCertificates; private Action _completedRequestHandler = DefaultCompletedRequestHandler; private int _connectionLimit; @@ -223,8 +222,7 @@ protected TransportConfigurationBase(IConnectionPool connectionPool, IConnection if (connectionPool is CloudConnectionPool cloudPool) { - _basicAuthCredentials = cloudPool.BasicCredentials; - _apiKeyAuthCredentials = cloudPool.ApiKeyCredentials; + _authenticationHeader = cloudPool.AuthenticationHeader; _enableHttpCompression = true; } @@ -238,8 +236,7 @@ protected TransportConfigurationBase(IConnectionPool connectionPool, IConnection // ReSharper disable once AutoPropertyCanBeMadeGetOnly.Global protected ITransportSerializer UseThisRequestResponseSerializer { get; set; } - BasicAuthenticationCredentials ITransportConfigurationValues.BasicAuthenticationCredentials => _basicAuthCredentials; - ApiKeyAuthenticationCredentials ITransportConfigurationValues.ApiKeyAuthenticationCredentials => _apiKeyAuthCredentials; + IAuthenticationHeader ITransportConfigurationValues.AuthenticationHeader => _authenticationHeader; SemaphoreSlim ITransportConfigurationValues.BootstrapLock => _semaphore; X509CertificateCollection ITransportConfigurationValues.ClientCertificates => _clientCertificates; IConnection ITransportConfigurationValues.Connection => _connection; @@ -392,25 +389,8 @@ public T OnRequestCompleted(Action handler) => public T OnRequestDataCreated(Action handler) => Assign(handler, (a, v) => a._onRequestDataCreated += v ?? DefaultRequestDataCreated); - /// - public T BasicAuthentication(string username, string password) => - Assign(new BasicAuthenticationCredentials(username, password), (a, v) => a._basicAuthCredentials = v); - - /// - public T BasicAuthentication(string username, SecureString password) => - Assign(new BasicAuthenticationCredentials(username, password), (a, v) => a._basicAuthCredentials = v); - - /// - public T ApiKeyAuthentication(string id, SecureString apiKey) => - Assign(new ApiKeyAuthenticationCredentials(id, apiKey), (a, v) => a._apiKeyAuthCredentials = v); - - /// - public T ApiKeyAuthentication(string id, string apiKey) => - Assign(new ApiKeyAuthenticationCredentials(id, apiKey), (a, v) => a._apiKeyAuthCredentials = v); - - /// - public T ApiKeyAuthentication(ApiKeyAuthenticationCredentials credentials) => - Assign(credentials, (a, v) => a._apiKeyAuthCredentials = v); + /// + public T Authentication(IAuthenticationHeader header) => Assign(header, (a, v) => a._authenticationHeader = v); /// public T EnableHttpPipelining(bool enabled = true) => Assign(enabled, (a, v) => a._enableHttpPipelining = v); @@ -491,8 +471,7 @@ protected virtual void DisposeManagedResources() _connection?.Dispose(); _semaphore?.Dispose(); _proxyPassword?.Dispose(); - _basicAuthCredentials?.Dispose(); - _apiKeyAuthCredentials?.Dispose(); + _authenticationHeader?.Dispose(); } /// Allows subclasses to add/remove default global query string parameters