diff --git a/src/Elasticsearch.Net/Connection/Configuration/ConnectionConfiguration.cs b/src/Elasticsearch.Net/Connection/Configuration/ConnectionConfiguration.cs index 1e470dea816..63c10e0ff78 100644 --- a/src/Elasticsearch.Net/Connection/Configuration/ConnectionConfiguration.cs +++ b/src/Elasticsearch.Net/Connection/Configuration/ConnectionConfiguration.cs @@ -5,6 +5,7 @@ using System.Linq; using Elasticsearch.Net.ConnectionPool; using Elasticsearch.Net.Serialization; +using Elasticsearch.Net.Connection.Security; namespace Elasticsearch.Net.Connection { @@ -123,6 +124,9 @@ public class ConnectionConfiguration : IConnectionConfigurationValues, IHideO IElasticsearchSerializer IConnectionConfigurationValues.Serializer { get; set; } + private BasicAuthorizationCredentials _basicAuthCredentials; + BasicAuthorizationCredentials IConnectionConfigurationValues.BasicAuthorizationCredentials { get { return _basicAuthCredentials; } } + public ConnectionConfiguration(IConnectionPool connectionPool) { this._timeout = 60*1000; @@ -328,6 +332,17 @@ public T SetConnectionStatusHandler(Action handler) return (T)this; } + /// + /// Basic access authorization credentials to specify with all requests. + /// + public T SetBasicAuthorization(string userName, string password) + { + if (this._basicAuthCredentials == null) + this._basicAuthCredentials = new BasicAuthorizationCredentials(); + this._basicAuthCredentials.UserName = userName; + this._basicAuthCredentials.Password = password; + return (T)this; + } } } diff --git a/src/Elasticsearch.Net/Connection/Configuration/IConnectionConfigurationValues.cs b/src/Elasticsearch.Net/Connection/Configuration/IConnectionConfigurationValues.cs index b9106b9eee8..f126cb27708 100644 --- a/src/Elasticsearch.Net/Connection/Configuration/IConnectionConfigurationValues.cs +++ b/src/Elasticsearch.Net/Connection/Configuration/IConnectionConfigurationValues.cs @@ -2,6 +2,7 @@ using System.Collections.Specialized; using Elasticsearch.Net.ConnectionPool; using Elasticsearch.Net.Serialization; +using Elasticsearch.Net.Connection.Security; namespace Elasticsearch.Net.Connection { @@ -65,5 +66,10 @@ public interface IConnectionConfigurationValues /// /// IElasticsearchSerializer Serializer { get; set; } + + /// + /// Basic access authorization credentials to specify with all requests. + /// + BasicAuthorizationCredentials BasicAuthorizationCredentials { get; } } } \ No newline at end of file diff --git a/src/Elasticsearch.Net/Connection/Configuration/IRequestConfiguration.cs b/src/Elasticsearch.Net/Connection/Configuration/IRequestConfiguration.cs index 7821502c43a..560cd000e27 100644 --- a/src/Elasticsearch.Net/Connection/Configuration/IRequestConfiguration.cs +++ b/src/Elasticsearch.Net/Connection/Configuration/IRequestConfiguration.cs @@ -1,3 +1,4 @@ +using Elasticsearch.Net.Connection.Security; using System; using System.Collections.Generic; using System.Linq; @@ -48,6 +49,10 @@ public interface IRequestConfiguration /// IEnumerable AllowedStatusCodes { get; set; } - + /// + /// Basic access authorization credentials to specify with this request. + /// Overrides any credentials that are set at the global IConnectionSettings level. + /// + BasicAuthorizationCredentials BasicAuthorizationCredentials { get; set; } } } \ No newline at end of file diff --git a/src/Elasticsearch.Net/Connection/Configuration/RequestConfiguration.cs b/src/Elasticsearch.Net/Connection/Configuration/RequestConfiguration.cs index 52f541ca783..2609545e78c 100644 --- a/src/Elasticsearch.Net/Connection/Configuration/RequestConfiguration.cs +++ b/src/Elasticsearch.Net/Connection/Configuration/RequestConfiguration.cs @@ -1,3 +1,4 @@ +using Elasticsearch.Net.Connection.Security; using System; using System.Collections.Generic; @@ -13,5 +14,6 @@ public class RequestConfiguration : IRequestConfiguration public bool? DisableSniff { get; set; } public bool? DisablePing { get; set; } public IEnumerable AllowedStatusCodes { get; set; } + public BasicAuthorizationCredentials BasicAuthorizationCredentials { get; set; } } } \ No newline at end of file diff --git a/src/Elasticsearch.Net/Connection/Configuration/RequestConfigurationDescriptor.cs b/src/Elasticsearch.Net/Connection/Configuration/RequestConfigurationDescriptor.cs index 96f6081e887..e79ecf65edd 100644 --- a/src/Elasticsearch.Net/Connection/Configuration/RequestConfigurationDescriptor.cs +++ b/src/Elasticsearch.Net/Connection/Configuration/RequestConfigurationDescriptor.cs @@ -1,3 +1,4 @@ +using Elasticsearch.Net.Connection.Security; using System; using System.Collections.Generic; @@ -23,6 +24,8 @@ public class RequestConfigurationDescriptor : IRequestConfiguration IEnumerable IRequestConfiguration.AllowedStatusCodes { get; set; } + BasicAuthorizationCredentials IRequestConfiguration.BasicAuthorizationCredentials { get; set; } + public RequestConfigurationDescriptor RequestTimeout(int requestTimeoutInMilliseconds) { Self.RequestTimeout = requestTimeoutInMilliseconds; @@ -75,5 +78,14 @@ public RequestConfigurationDescriptor MaxRetries(int retry) Self.MaxRetries = retry; return this; } + + public RequestConfigurationDescriptor BasicAuthorization(string userName, string password) + { + if (Self.BasicAuthorizationCredentials == null) + Self.BasicAuthorizationCredentials = new BasicAuthorizationCredentials(); + Self.BasicAuthorizationCredentials.UserName = userName; + Self.BasicAuthorizationCredentials.Password = password; + return this; + } } } \ No newline at end of file diff --git a/src/Elasticsearch.Net/Connection/HttpConnection.cs b/src/Elasticsearch.Net/Connection/HttpConnection.cs index 69deadd3860..de440a06ed0 100644 --- a/src/Elasticsearch.Net/Connection/HttpConnection.cs +++ b/src/Elasticsearch.Net/Connection/HttpConnection.cs @@ -141,7 +141,7 @@ private static void ThreadTimeoutCallback(object state, bool timedOut) protected virtual HttpWebRequest CreateHttpWebRequest(Uri uri, string method, byte[] data, IRequestConfiguration requestSpecificConfig) { var myReq = this.CreateWebRequest(uri, method, data, requestSpecificConfig); - this.SetBasicAuthorizationIfNeeded(uri, myReq); + this.SetBasicAuthorizationIfNeeded(uri, myReq, requestSpecificConfig); this.SetProxyIfNeeded(myReq); return myReq; } @@ -164,12 +164,23 @@ private void SetProxyIfNeeded(HttpWebRequest myReq) } } - private void SetBasicAuthorizationIfNeeded(Uri uri, HttpWebRequest myReq) + private void SetBasicAuthorizationIfNeeded(Uri uri, HttpWebRequest request, IRequestConfiguration requestSpecificConfig) { - if (!uri.UserInfo.IsNullOrEmpty()) - { - myReq.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(myReq.RequestUri.UserInfo)); - } + // 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) + + var userInfo = Uri.UnescapeDataString(uri.UserInfo); + + if (this.ConnectionSettings.BasicAuthorizationCredentials != null) + userInfo = this.ConnectionSettings.BasicAuthorizationCredentials.ToString(); + + if (requestSpecificConfig != null && requestSpecificConfig.BasicAuthorizationCredentials != null) + userInfo = requestSpecificConfig.BasicAuthorizationCredentials.ToString(); + + if (!userInfo.IsNullOrEmpty()) + request.Headers["Authorization"] = "Basic " + Convert.ToBase64String(Encoding.UTF8.GetBytes(userInfo)); } protected virtual HttpWebRequest CreateWebRequest(Uri uri, string method, byte[] data, IRequestConfiguration requestSpecificConfig) diff --git a/src/Elasticsearch.Net/Connection/Security/BasicAuthorizationCredentials.cs b/src/Elasticsearch.Net/Connection/Security/BasicAuthorizationCredentials.cs new file mode 100644 index 00000000000..ad1c32f9b7b --- /dev/null +++ b/src/Elasticsearch.Net/Connection/Security/BasicAuthorizationCredentials.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Elasticsearch.Net.Connection.Security +{ + public class BasicAuthorizationCredentials + { + public string UserName { get; set; } + public string Password { get; set; } + + public override string ToString() + { + return this.UserName + ":" + this.Password; + } + } +} diff --git a/src/Elasticsearch.Net/Elasticsearch.Net.csproj b/src/Elasticsearch.Net/Elasticsearch.Net.csproj index e5b3e6aa6e6..a06bf749f87 100644 --- a/src/Elasticsearch.Net/Elasticsearch.Net.csproj +++ b/src/Elasticsearch.Net/Elasticsearch.Net.csproj @@ -57,6 +57,7 @@ + diff --git a/src/Tests/Nest.Tests.Integration/Connection/Security/BasicAuthorizationTests.cs b/src/Tests/Nest.Tests.Integration/Connection/Security/BasicAuthorizationTests.cs new file mode 100644 index 00000000000..941bb18300e --- /dev/null +++ b/src/Tests/Nest.Tests.Integration/Connection/Security/BasicAuthorizationTests.cs @@ -0,0 +1,96 @@ + +using NUnit.Framework; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FluentAssertions; +using Nest; + +// TODO: Need to figure out a better way to incorporate these tests into the normal test suite workflow. + +// Purposely outside of the Nest namespace in order to bypass test setup. +namespace Tests.Security.BasicAuthTests +{ + [TestFixture] + // Ignore these tests by default and run manually (for now) by commenting out the [Ignore] attribute. + // A considerable amount of setup is required to run them, which will also cause the rest of the test + // suite to fail. + [Ignore] + public class BasicAuthorizationTests + { + [Test] + public void No_Credentials_Result_In_401() + { + var settings = new ConnectionSettings(new Uri("http://localhost:9200")); + var client = new ElasticClient(settings); + var response = client.RootNodeInfo(); + response.IsValid.Should().BeFalse(); + response.ConnectionStatus.HttpStatusCode.Should().Be(401); + } + + [Test] + public void Invalid_Credentials_Result_In_401() + { + var settings = new ConnectionSettings(new Uri("http://localhost:9200")) + .SetBasicAuthorization("nestuser", "incorrectpassword"); + var client = new ElasticClient(settings); + var response = client.RootNodeInfo(); + response.IsValid.Should().BeFalse(); + response.ConnectionStatus.HttpStatusCode.Should().Be(401); + } + + [Test] + public void Valid_Credentials_Result_In_200() + { + var settings = new ConnectionSettings(new Uri("http://localhost:9200")) + .SetBasicAuthorization("nestuser", "elastic"); + var client = new ElasticClient(settings); + var response = client.RootNodeInfo(); + response.IsValid.Should().BeTrue(); + } + + [Test] + public void Credentials_On_URI_Result_In_200() + { + var settings = new ConnectionSettings(new Uri("http://nestuser:elastic@localhost:9200")); + var client = new ElasticClient(settings); + var response = client.RootNodeInfo(); + response.IsValid.Should().BeTrue(); + } + + [Test] + public void Escaped_Credentials_On_URI_Result_In_200() + { + var settings = new ConnectionSettings(new Uri("http://gmarz:p%40ssword@localhost:9200")); + var client = new ElasticClient(settings); + var response = client.RootNodeInfo(); + response.IsValid.Should().BeTrue(); + } + + [Test] + public void ConnectionSettings_Overrides_URI() + { + var settings = new ConnectionSettings(new Uri("http://invalid:user@localhost:9200")) + .SetBasicAuthorization("nestuser", "elastic"); + var client = new ElasticClient(settings); + var response = client.RootNodeInfo(); + response.IsValid.Should().BeTrue(); + } + + [Test] + public void RequestConfiguration_Overrides_ConnectionSettings() + { + var settings = new ConnectionSettings(new Uri("http://localhost:9200")) + .SetBasicAuthorization("invalid", "user"); + var client = new ElasticClient(settings); + var response = client.RootNodeInfo(c => c + .RequestConfiguration(rc => rc + .BasicAuthorization("nestuser", "elastic") + ) + ); + response.IsValid.Should().BeTrue(); + } + } +} diff --git a/src/Tests/Nest.Tests.Integration/IntegrationSetup.cs b/src/Tests/Nest.Tests.Integration/IntegrationSetup.cs index c386cd900a2..203e84de4cb 100644 --- a/src/Tests/Nest.Tests.Integration/IntegrationSetup.cs +++ b/src/Tests/Nest.Tests.Integration/IntegrationSetup.cs @@ -8,41 +8,41 @@ using Nest.Tests.MockData.Domain; using NUnit.Framework; -[SetUpFixture] -public class SetupAndTeardownForIntegrationTests +namespace Nest.Tests.Integration { - [SetUp] - public void Setup() + [SetUpFixture] + public class SetupAndTeardownForIntegrationTests { - var client = new ElasticClient( - //ElasticsearchConfiguration.Settings(hostOverride: new Uri("http://localhost:9200")) - ElasticsearchConfiguration.Settings() - ); - - try + [SetUp] + public void Setup() { - IntegrationSetup.CreateTestIndex(client, ElasticsearchConfiguration.DefaultIndex); - IntegrationSetup.CreateTestIndex(client, ElasticsearchConfiguration.DefaultIndex + "_clone"); + var client = new ElasticClient( + //ElasticsearchConfiguration.Settings(hostOverride: new Uri("http://localhost:9200")) + ElasticsearchConfiguration.Settings() + ); + + try + { + IntegrationSetup.CreateTestIndex(client, ElasticsearchConfiguration.DefaultIndex); + IntegrationSetup.CreateTestIndex(client, ElasticsearchConfiguration.DefaultIndex + "_clone"); + + IntegrationSetup.IndexDemoData(client); + } + catch (Exception) + { + + throw; + } - IntegrationSetup.IndexDemoData(client); } - catch (Exception) + [TearDown] + public void TearDown() { - - throw; + var client = ElasticsearchConfiguration.Client.Value; + client.DeleteIndex(di => di.Indices(ElasticsearchConfiguration.DefaultIndex, ElasticsearchConfiguration.DefaultIndex + "*")); } - - } - [TearDown] - public void TearDown() - { - var client = ElasticsearchConfiguration.Client.Value; - client.DeleteIndex(di => di.Indices(ElasticsearchConfiguration.DefaultIndex, ElasticsearchConfiguration.DefaultIndex + "*")); } -} -namespace Nest.Tests.Integration -{ public static class IntegrationSetup { public static void IndexDemoData(IElasticClient client, string index = null) diff --git a/src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj b/src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj index 1e9164293e4..d41c5c3113a 100644 --- a/src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj +++ b/src/Tests/Nest.Tests.Integration/Nest.Tests.Integration.csproj @@ -105,6 +105,7 @@ +