From ae7cd86605e6d89137b5e4ecdfcaf1754e18b7d9 Mon Sep 17 00:00:00 2001 From: Matthew Fisher <matt.fisher@fermyon.com> Date: Tue, 7 Jun 2022 13:10:56 -0700 Subject: [PATCH 1/7] implement connection string support Signed-off-by: Matthew Fisher <matt.fisher@fermyon.com> --- src/Bindle/BindleClient.cs | 52 ++++++++--------- src/Bindle/ConnectionInfo.cs | 57 +++++++++++++++++++ src/Bindle/SslMode.cs | 17 ++++++ tests/Bindle.IntegrationTests/Integration.cs | 16 +++--- .../Bindle.UnitTests/TomlNamingHelperTests.cs | 1 - 5 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 src/Bindle/ConnectionInfo.cs create mode 100644 src/Bindle/SslMode.cs diff --git a/src/Bindle/BindleClient.cs b/src/Bindle/BindleClient.cs index 9929220..acce558 100644 --- a/src/Bindle/BindleClient.cs +++ b/src/Bindle/BindleClient.cs @@ -15,34 +15,38 @@ namespace Deislabs.Bindle; public class BindleClient { public BindleClient( - string baseUri + string connectionString ) { - _baseUri = new Uri(SlashSafe(baseUri)); - _httpClient = new HttpClient(); - } + ConnectionInfo connectionInfo = new ConnectionInfo(connectionString); - public BindleClient( - string baseUri, - HttpMessageHandler messageHandler - ) - { - _baseUri = new Uri(SlashSafe(baseUri)); - _httpClient = new HttpClient(messageHandler); + // TODO: do we want to assume the default listening address? + if (string.IsNullOrEmpty(connectionInfo.BaseUri)) + throw new ArgumentException("base URI cannot be empty"); + + var handler = new HttpClientHandler(); + + if (connectionInfo.SslMode == SslMode.Disable) + { + handler.ServerCertificateCustomValidationCallback = + (httpRequestMessage, cert, cetChain, policyErrors) => + { + return true; + }; + } + + _httpClient = new HttpClient(handler) { BaseAddress = new Uri(SlashSafe(connectionInfo.BaseUri)) }; } private const string INVOICE_PATH = "_i"; private const string QUERY_PATH = "_q"; private const string RELATIONSHIP_PATH = "_r"; - - private readonly Uri _baseUri; private readonly HttpClient _httpClient; public async Task<Invoice> GetInvoice(string invoiceId, GetInvoiceOptions options = GetInvoiceOptions.None) { var query = GetInvoiceQueryString(options); - var uri = new Uri(_baseUri, $"{INVOICE_PATH}/{invoiceId}{query}"); - var response = await _httpClient.GetAsync(uri); + var response = await _httpClient.GetAsync($"{INVOICE_PATH}/{invoiceId}{query}"); await ExpectResponseCode(response, HttpStatusCode.OK, HttpStatusCode.Forbidden); if (response.StatusCode == HttpStatusCode.Forbidden) @@ -74,8 +78,7 @@ public async Task<Matches> QueryInvoices(string? queryString = null, bool? yanked = null) { var query = GetDistinctInvoicesNamesQueryString(queryString, offset, limit, strict, semVer, yanked); - var uri = new Uri(_baseUri, $"{QUERY_PATH}?{query}"); - var response = await _httpClient.GetAsync(uri); + var response = await _httpClient.GetAsync($"{QUERY_PATH}?{query}"); await ExpectResponseCode(response, HttpStatusCode.OK, HttpStatusCode.Forbidden); if (response.StatusCode == HttpStatusCode.Forbidden) @@ -99,9 +102,8 @@ public async Task<CreateInvoiceResult> CreateInvoice(Invoice invoice) ConvertPropertyName = name => TomlNamingHelper.PascalToCamelCase(name) }); - var uri = new Uri(_baseUri, INVOICE_PATH); var requestContent = new StringContent(invoiceToml, null, "application/toml"); - var response = await _httpClient.PostAsync(uri, requestContent); + var response = await _httpClient.PostAsync(INVOICE_PATH, requestContent); await ExpectResponseCode(response, HttpStatusCode.Created, HttpStatusCode.Accepted); var content = await response.Content.ReadAsStringAsync(); @@ -115,15 +117,13 @@ public async Task<CreateInvoiceResult> CreateInvoice(Invoice invoice) public async Task YankInvoice(string invoiceId) { - var uri = new Uri(_baseUri, $"{INVOICE_PATH}/{invoiceId}"); - var response = await _httpClient.DeleteAsync(uri); + var response = await _httpClient.DeleteAsync($"{INVOICE_PATH}/{invoiceId}"); await ExpectResponseCode(response, HttpStatusCode.OK); } public async Task<HttpContent> GetParcel(string invoiceId, string parcelId) { - var uri = new Uri(_baseUri, $"{INVOICE_PATH}/{invoiceId}@{parcelId}"); - var response = await _httpClient.GetAsync(uri); + var response = await _httpClient.GetAsync($"{INVOICE_PATH}/{invoiceId}@{parcelId}"); await ExpectResponseCode(response, HttpStatusCode.OK); return response.Content; @@ -146,15 +146,13 @@ public async Task CreateParcel(string invoiceId, string parcelId, byte[] content public async Task CreateParcel(string invoiceId, string parcelId, HttpContent content) { - var uri = new Uri(_baseUri, $"{INVOICE_PATH}/{invoiceId}@{parcelId}"); - var response = await _httpClient.PostAsync(uri, content); + var response = await _httpClient.PostAsync($"{INVOICE_PATH}/{invoiceId}@{parcelId}", content); await ExpectResponseCode(response, HttpStatusCode.OK, HttpStatusCode.Created); } public async Task<MissingParcelsResponse> ListMissingParcels(string invoiceId) { - var uri = new Uri(_baseUri, $"{RELATIONSHIP_PATH}/missing/{invoiceId}"); - var response = await _httpClient.GetAsync(uri); + var response = await _httpClient.GetAsync($"{RELATIONSHIP_PATH}/missing/{invoiceId}"); await ExpectResponseCode(response, HttpStatusCode.OK); var content = await response.Content.ReadAsStringAsync(); diff --git a/src/Bindle/ConnectionInfo.cs b/src/Bindle/ConnectionInfo.cs new file mode 100644 index 0000000..5efecff --- /dev/null +++ b/src/Bindle/ConnectionInfo.cs @@ -0,0 +1,57 @@ +using System; +using System.Linq; + +namespace Deislabs.Bindle; + +public class ConnectionInfo +{ + static readonly string[] serverAliases = { + "server", + "host", + "data source", + "datasource", + "address", + "addr", + "network address", + }; + + static readonly string[] sslModeAliases = { + "sslmode", + "ssl mode" + }; + + public string? BaseUri; + + public SslMode? SslMode; + + public ConnectionInfo(string connectionString) + { + BaseUri = GetValue(connectionString, serverAliases); + + try + { + SslMode = Enum.Parse<SslMode>(GetValue(connectionString, sslModeAliases)); + } + catch (Exception) + { + SslMode = null; + } + } + + static string GetValue(string connectionString, params string[] keyAliases) + { + var keyValuePairs = connectionString.Split(';') + .Where(kvp => kvp.Contains('=')) + .Select(kvp => kvp.Split(new char[] { '=' }, 2)) + .ToDictionary(kvp => kvp[0].Trim(), + kvp => kvp[1].Trim(), + StringComparer.InvariantCultureIgnoreCase); + foreach (var alias in keyAliases) + { + string? value; + if (keyValuePairs.TryGetValue(alias, out value)) + return value; + } + return string.Empty; + } +} diff --git a/src/Bindle/SslMode.cs b/src/Bindle/SslMode.cs new file mode 100644 index 0000000..f86069a --- /dev/null +++ b/src/Bindle/SslMode.cs @@ -0,0 +1,17 @@ +namespace Deislabs.Bindle; + +public enum SslMode +{ + // I don't care about security, and I don't want to pay the overhead of encryption. + Disable, + // I don't care about security, but I will pay the overhead of encryption if the server insists on it. + Allow, + // I don't care about encryption, but I wish to pay the overhead of encryption if the server supports it. + Prefer, + // I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want. + Require, + // I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server that I trust. + VerifyCA, + // I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it's the one I specify. + VerifyFull, +} diff --git a/tests/Bindle.IntegrationTests/Integration.cs b/tests/Bindle.IntegrationTests/Integration.cs index 5b8d6d0..5d025fd 100644 --- a/tests/Bindle.IntegrationTests/Integration.cs +++ b/tests/Bindle.IntegrationTests/Integration.cs @@ -23,7 +23,7 @@ public class Integration : IClassFixture<IntegrationFixture> [Fact] public async Task CanFetchInvoice() { - var client = new BindleClient(DEMO_SERVER_URL); + var client = new BindleClient($"host=${DEMO_SERVER_URL}"); var invoice = await client.GetInvoice("your/fancy/bindle/0.3.0"); Assert.Equal("1.0.0", invoice.BindleVersion); Assert.Equal("your/fancy/bindle", invoice.Bindle.Name); @@ -37,7 +37,7 @@ public async Task CanFetchInvoice() [Fact] public async Task CanQueryInvoices() { - var client = new BindleClient(DEMO_SERVER_URL); + var client = new BindleClient($"host=${DEMO_SERVER_URL}"); var matches = await client.QueryInvoices(queryString: "my"); Assert.True(matches.Invoices.All(i => i.Bindle.Name.Contains("my"))); } @@ -45,7 +45,7 @@ public async Task CanQueryInvoices() [Fact] public async Task CanFetchYankedInvoices() { - var client = new BindleClient(DEMO_SERVER_URL); + var client = new BindleClient($"host=${DEMO_SERVER_URL}"); var invoice = await client.GetInvoice("yourbindle/0.1.1", IncludeYanked); Assert.Equal("1.0.0", invoice.BindleVersion); Assert.Equal("yourbindle", invoice.Bindle.Name); @@ -55,7 +55,7 @@ public async Task CanFetchYankedInvoices() [Fact] public async Task CanCreateInvoices() { - var client = new BindleClient(DEMO_SERVER_URL); + var client = new BindleClient($"host=${DEMO_SERVER_URL}"); var invoice = new Invoice { BindleVersion = "1.0.0", @@ -98,7 +98,7 @@ public async Task CanCreateInvoices() [TestPriority(10)] public async Task CanYankInvoice() { - var client = new BindleClient(DEMO_SERVER_URL); + var client = new BindleClient($"host=${DEMO_SERVER_URL}"); await client.YankInvoice("your/fancy/bindle/0.3.0"); await Assert.ThrowsAsync<BindleYankedException>(async () => { @@ -111,7 +111,7 @@ await Assert.ThrowsAsync<BindleYankedException>(async () => [Fact] public async Task CanFetchParcel() { - var client = new BindleClient(DEMO_SERVER_URL); + var client = new BindleClient($"host=${DEMO_SERVER_URL}"); var parcel = await client.GetParcel("mybindle/0.1.0", "f7f3b33707fb76d208f5839a40e770452dcf9f348bfd7faf2c524e0fa6710ed6"); Assert.Equal("Fie on you Gary", await parcel.ReadAsStringAsync()); } @@ -119,7 +119,7 @@ public async Task CanFetchParcel() [Fact] public async Task CanCreateParcel() { - var client = new BindleClient(DEMO_SERVER_URL); + var client = new BindleClient($"host=${DEMO_SERVER_URL}"); await client.CreateParcel("mybindle/0.1.0", "460d5965e4d1909e8c7a3748a414956b7038ab5fd79937c9fcb2b214e6b0160a", "The front fell off"); var fetched = await client.GetParcel("mybindle/0.1.0", "460d5965e4d1909e8c7a3748a414956b7038ab5fd79937c9fcb2b214e6b0160a"); Assert.Equal("The front fell off", await fetched.ReadAsStringAsync()); @@ -128,7 +128,7 @@ public async Task CanCreateParcel() [Fact] public async Task CanListMissingParcels() { - var client = new BindleClient(DEMO_SERVER_URL); + var client = new BindleClient($"host=${DEMO_SERVER_URL}"); var resp = await client.ListMissingParcels("mybindle/0.3.0"); Assert.Contains(resp.Missing, (label) => label.Sha256 == "e1706ab0a39ac88094b6d54a3f5cdba41fe5a901"); Assert.DoesNotContain(resp.Missing, (label) => label.Sha256 == "f7f3b33707fb76d208f5839a40e770452dcf9f348bfd7faf2c524e0fa6710ed6"); diff --git a/tests/Bindle.UnitTests/TomlNamingHelperTests.cs b/tests/Bindle.UnitTests/TomlNamingHelperTests.cs index 63f8288..2f66e2c 100644 --- a/tests/Bindle.UnitTests/TomlNamingHelperTests.cs +++ b/tests/Bindle.UnitTests/TomlNamingHelperTests.cs @@ -1,4 +1,3 @@ -using Deislabs.Bindle; using Xunit; namespace Deislabs.Bindle.UnitTests; From 8d8cab7a9448eb6c5222f47d51aec9d0a92cfd9f Mon Sep 17 00:00:00 2001 From: Matthew Fisher <matt.fisher@fermyon.com> Date: Tue, 7 Jun 2022 13:15:27 -0700 Subject: [PATCH 2/7] ignore ssl mode casing Signed-off-by: Matthew Fisher <matt.fisher@fermyon.com> --- src/Bindle/ConnectionInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Bindle/ConnectionInfo.cs b/src/Bindle/ConnectionInfo.cs index 5efecff..3348549 100644 --- a/src/Bindle/ConnectionInfo.cs +++ b/src/Bindle/ConnectionInfo.cs @@ -30,7 +30,7 @@ public ConnectionInfo(string connectionString) try { - SslMode = Enum.Parse<SslMode>(GetValue(connectionString, sslModeAliases)); + SslMode = Enum.Parse<SslMode>(GetValue(connectionString, sslModeAliases), true); } catch (Exception) { From 487f3ede4784c0785ff835daac96ec278ac6b23f Mon Sep 17 00:00:00 2001 From: Matthew Fisher <matt.fisher@fermyon.com> Date: Tue, 7 Jun 2022 13:38:54 -0700 Subject: [PATCH 3/7] add unit tests Signed-off-by: Matthew Fisher <matt.fisher@fermyon.com> --- src/Bindle/ConnectionInfo.cs | 2 +- tests/Bindle.UnitTests/ConnectionInfoTests.cs | 64 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 tests/Bindle.UnitTests/ConnectionInfoTests.cs diff --git a/src/Bindle/ConnectionInfo.cs b/src/Bindle/ConnectionInfo.cs index 3348549..aea5a5b 100644 --- a/src/Bindle/ConnectionInfo.cs +++ b/src/Bindle/ConnectionInfo.cs @@ -20,7 +20,7 @@ public class ConnectionInfo "ssl mode" }; - public string? BaseUri; + public string BaseUri; public SslMode? SslMode; diff --git a/tests/Bindle.UnitTests/ConnectionInfoTests.cs b/tests/Bindle.UnitTests/ConnectionInfoTests.cs new file mode 100644 index 0000000..9a41ffb --- /dev/null +++ b/tests/Bindle.UnitTests/ConnectionInfoTests.cs @@ -0,0 +1,64 @@ +using System; +using Xunit; + +namespace Deislabs.Bindle.UnitTests; + +public class ConnectionInfoTests +{ + [Fact] + public void ShouldAcceptServerAliases() + { + Assert.Equal("", (new ConnectionInfo("").BaseUri)); + Assert.Equal("", (new ConnectionInfo("server=").BaseUri)); + Assert.Equal("localhost", (new ConnectionInfo("server=localhost").BaseUri)); + Assert.Equal("localhost", (new ConnectionInfo("host=localhost").BaseUri)); + Assert.Equal("localhost", (new ConnectionInfo("data source=localhost").BaseUri)); + Assert.Equal("localhost", (new ConnectionInfo("datasource=localhost").BaseUri)); + Assert.Equal("localhost", (new ConnectionInfo("address=localhost").BaseUri)); + Assert.Equal("localhost", (new ConnectionInfo("addr=localhost").BaseUri)); + Assert.Equal("localhost", (new ConnectionInfo("network address=localhost").BaseUri)); + } + + [Fact] + public void ShouldAcceptSslModeAliases() + { + Assert.Null(new ConnectionInfo("").SslMode); + Assert.Null(new ConnectionInfo("sslmode=").SslMode); + Assert.Null(new ConnectionInfo("sslmode=doesnotexist").SslMode); + Assert.Equal(SslMode.Disable, (new ConnectionInfo("sslmode=disable").SslMode)); + Assert.Equal(SslMode.Disable, (new ConnectionInfo("sslmode=Disable").SslMode)); + Assert.Equal(SslMode.Disable, (new ConnectionInfo("ssl mode=disable").SslMode)); + Assert.Equal(SslMode.Allow, (new ConnectionInfo("sslmode=allow").SslMode)); + Assert.Equal(SslMode.Prefer, (new ConnectionInfo("sslmode=prefer").SslMode)); + Assert.Equal(SslMode.Require, (new ConnectionInfo("sslmode=require").SslMode)); + Assert.Equal(SslMode.VerifyCA, (new ConnectionInfo("sslmode=verifyca").SslMode)); + Assert.Equal(SslMode.VerifyFull, (new ConnectionInfo("sslmode=verifyfull").SslMode)); + } + + [Fact] + public void ShouldAcceptMultipleOptions() + { + var connectionInfos = new ConnectionInfo[] + { + new ConnectionInfo("server=localhost;sslmode=verifyfull"), + new ConnectionInfo("server=localhost; sslmode=verifyfull"), + new ConnectionInfo(" server=localhost;sslmode=verifyfull"), + new ConnectionInfo(" server=localhost; sslmode=verifyfull"), + new ConnectionInfo("server=localhost;sslmode=verifyfull "), + new ConnectionInfo(" server=localhost;sslmode=verifyfull "), + new ConnectionInfo("server=localhost;;sslmode=verifyfull"), + new ConnectionInfo("server=localhost; sslmode=verifyfull"), + }; + foreach (var connectionInfo in connectionInfos) + { + Assert.Equal("localhost", connectionInfo.BaseUri); + Assert.Equal(SslMode.VerifyFull, connectionInfo.SslMode); + } + } + + [Fact] + public void ShouldNotAcceptDuplicates() + { + Assert.Throws<ArgumentException>(() => new ConnectionInfo("sslmode=disable;sslmode=verifyfull")); + } +} From 80d593afa50b96d4504a22dc2f2e9abb41ca2a1d Mon Sep 17 00:00:00 2001 From: Matthew Fisher <matt.fisher@fermyon.com> Date: Tue, 7 Jun 2022 14:04:28 -0700 Subject: [PATCH 4/7] optimize parse loop Signed-off-by: Matthew Fisher <matt.fisher@fermyon.com> --- src/Bindle/BindleClient.cs | 1 - src/Bindle/ConnectionInfo.cs | 27 ++++++++++++++++++--------- src/Bindle/SslMode.cs | 5 +++++ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/Bindle/BindleClient.cs b/src/Bindle/BindleClient.cs index acce558..c600ba8 100644 --- a/src/Bindle/BindleClient.cs +++ b/src/Bindle/BindleClient.cs @@ -20,7 +20,6 @@ string connectionString { ConnectionInfo connectionInfo = new ConnectionInfo(connectionString); - // TODO: do we want to assume the default listening address? if (string.IsNullOrEmpty(connectionInfo.BaseUri)) throw new ArgumentException("base URI cannot be empty"); diff --git a/src/Bindle/ConnectionInfo.cs b/src/Bindle/ConnectionInfo.cs index aea5a5b..cdde5c9 100644 --- a/src/Bindle/ConnectionInfo.cs +++ b/src/Bindle/ConnectionInfo.cs @@ -1,10 +1,13 @@ using System; +using System.Collections.Generic; using System.Linq; namespace Deislabs.Bindle; public class ConnectionInfo { + private readonly Dictionary<string, string> keyValuePairs; + static readonly string[] serverAliases = { "server", "host", @@ -26,11 +29,13 @@ public class ConnectionInfo public ConnectionInfo(string connectionString) { - BaseUri = GetValue(connectionString, serverAliases); + keyValuePairs = GetKeyValuePairs(connectionString); + + BaseUri = GetValue(serverAliases); try { - SslMode = Enum.Parse<SslMode>(GetValue(connectionString, sslModeAliases), true); + SslMode = Enum.Parse<SslMode>(GetValue(sslModeAliases), true); } catch (Exception) { @@ -38,14 +43,18 @@ public ConnectionInfo(string connectionString) } } - static string GetValue(string connectionString, params string[] keyAliases) + private Dictionary<string, string> GetKeyValuePairs(string connectionString) + { + return connectionString.Split(';') + .Where(kvp => kvp.Contains('=')) + .Select(kvp => kvp.Split(new char[] { '=' }, 2)) + .ToDictionary(kvp => kvp[0].Trim(), + kvp => kvp[1].Trim(), + StringComparer.InvariantCultureIgnoreCase); + } + + private string GetValue(string[] keyAliases) { - var keyValuePairs = connectionString.Split(';') - .Where(kvp => kvp.Contains('=')) - .Select(kvp => kvp.Split(new char[] { '=' }, 2)) - .ToDictionary(kvp => kvp[0].Trim(), - kvp => kvp[1].Trim(), - StringComparer.InvariantCultureIgnoreCase); foreach (var alias in keyAliases) { string? value; diff --git a/src/Bindle/SslMode.cs b/src/Bindle/SslMode.cs index f86069a..00c7c48 100644 --- a/src/Bindle/SslMode.cs +++ b/src/Bindle/SslMode.cs @@ -5,13 +5,18 @@ public enum SslMode // I don't care about security, and I don't want to pay the overhead of encryption. Disable, // I don't care about security, but I will pay the overhead of encryption if the server insists on it. + // TODO(bacongobbler): not implemented Allow, // I don't care about encryption, but I wish to pay the overhead of encryption if the server supports it. + // TODO(bacongobbler): not implemented Prefer, // I want my data to be encrypted, and I accept the overhead. I trust that the network will make sure I always connect to the server I want. + // TODO(bacongobbler): not implemented Require, // I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server that I trust. + // TODO(bacongobbler): not implemented VerifyCA, // I want my data encrypted, and I accept the overhead. I want to be sure that I connect to a server I trust, and that it's the one I specify. + // TODO(bacongobbler): not implemented VerifyFull, } From 3df29732afddc7ca38e551b81305962cb897ffb5 Mon Sep 17 00:00:00 2001 From: Matthew Fisher <matt.fisher@fermyon.com> Date: Tue, 7 Jun 2022 14:14:14 -0700 Subject: [PATCH 5/7] implement ConnectionInfo ctor Signed-off-by: Matthew Fisher <matt.fisher@fermyon.com> --- src/Bindle/BindleClient.cs | 8 +++----- src/Bindle/ConnectionInfo.cs | 10 ++++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/Bindle/BindleClient.cs b/src/Bindle/BindleClient.cs index c600ba8..698850d 100644 --- a/src/Bindle/BindleClient.cs +++ b/src/Bindle/BindleClient.cs @@ -14,12 +14,10 @@ namespace Deislabs.Bindle; public class BindleClient { - public BindleClient( - string connectionString - ) - { - ConnectionInfo connectionInfo = new ConnectionInfo(connectionString); + public BindleClient(string connectionString) : this(new ConnectionInfo(connectionString)) { } + public BindleClient(ConnectionInfo connectionInfo) + { if (string.IsNullOrEmpty(connectionInfo.BaseUri)) throw new ArgumentException("base URI cannot be empty"); diff --git a/src/Bindle/ConnectionInfo.cs b/src/Bindle/ConnectionInfo.cs index cdde5c9..c59b6e9 100644 --- a/src/Bindle/ConnectionInfo.cs +++ b/src/Bindle/ConnectionInfo.cs @@ -6,8 +6,6 @@ namespace Deislabs.Bindle; public class ConnectionInfo { - private readonly Dictionary<string, string> keyValuePairs; - static readonly string[] serverAliases = { "server", "host", @@ -23,10 +21,18 @@ public class ConnectionInfo "ssl mode" }; + private readonly Dictionary<string, string> keyValuePairs; + public string BaseUri; public SslMode? SslMode; + public ConnectionInfo() + { + keyValuePairs = new Dictionary<string, string>(); + BaseUri = "http://localhost:8080/v1/"; + } + public ConnectionInfo(string connectionString) { keyValuePairs = GetKeyValuePairs(connectionString); From 3b56e2b90f6e7ab59094d981f83f8761e7b4d13b Mon Sep 17 00:00:00 2001 From: Matthew Fisher <matt.fisher@fermyon.com> Date: Tue, 7 Jun 2022 14:31:00 -0700 Subject: [PATCH 6/7] fix string interpolation Signed-off-by: Matthew Fisher <matt.fisher@fermyon.com> --- src/Bindle/BindleClient.cs | 2 -- tests/Bindle.IntegrationTests/Integration.cs | 16 ++++++++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Bindle/BindleClient.cs b/src/Bindle/BindleClient.cs index 698850d..c4b2b2f 100644 --- a/src/Bindle/BindleClient.cs +++ b/src/Bindle/BindleClient.cs @@ -1,11 +1,9 @@ using System; -using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Threading.Tasks; using Tomlyn; using Tomlyn.Syntax; diff --git a/tests/Bindle.IntegrationTests/Integration.cs b/tests/Bindle.IntegrationTests/Integration.cs index 5d025fd..7a663fa 100644 --- a/tests/Bindle.IntegrationTests/Integration.cs +++ b/tests/Bindle.IntegrationTests/Integration.cs @@ -23,7 +23,7 @@ public class Integration : IClassFixture<IntegrationFixture> [Fact] public async Task CanFetchInvoice() { - var client = new BindleClient($"host=${DEMO_SERVER_URL}"); + var client = new BindleClient($"host={DEMO_SERVER_URL}"); var invoice = await client.GetInvoice("your/fancy/bindle/0.3.0"); Assert.Equal("1.0.0", invoice.BindleVersion); Assert.Equal("your/fancy/bindle", invoice.Bindle.Name); @@ -37,7 +37,7 @@ public async Task CanFetchInvoice() [Fact] public async Task CanQueryInvoices() { - var client = new BindleClient($"host=${DEMO_SERVER_URL}"); + var client = new BindleClient($"host={DEMO_SERVER_URL}"); var matches = await client.QueryInvoices(queryString: "my"); Assert.True(matches.Invoices.All(i => i.Bindle.Name.Contains("my"))); } @@ -45,7 +45,7 @@ public async Task CanQueryInvoices() [Fact] public async Task CanFetchYankedInvoices() { - var client = new BindleClient($"host=${DEMO_SERVER_URL}"); + var client = new BindleClient($"host={DEMO_SERVER_URL}"); var invoice = await client.GetInvoice("yourbindle/0.1.1", IncludeYanked); Assert.Equal("1.0.0", invoice.BindleVersion); Assert.Equal("yourbindle", invoice.Bindle.Name); @@ -55,7 +55,7 @@ public async Task CanFetchYankedInvoices() [Fact] public async Task CanCreateInvoices() { - var client = new BindleClient($"host=${DEMO_SERVER_URL}"); + var client = new BindleClient($"host={DEMO_SERVER_URL}"); var invoice = new Invoice { BindleVersion = "1.0.0", @@ -98,7 +98,7 @@ public async Task CanCreateInvoices() [TestPriority(10)] public async Task CanYankInvoice() { - var client = new BindleClient($"host=${DEMO_SERVER_URL}"); + var client = new BindleClient($"host={DEMO_SERVER_URL}"); await client.YankInvoice("your/fancy/bindle/0.3.0"); await Assert.ThrowsAsync<BindleYankedException>(async () => { @@ -111,7 +111,7 @@ await Assert.ThrowsAsync<BindleYankedException>(async () => [Fact] public async Task CanFetchParcel() { - var client = new BindleClient($"host=${DEMO_SERVER_URL}"); + var client = new BindleClient($"host={DEMO_SERVER_URL}"); var parcel = await client.GetParcel("mybindle/0.1.0", "f7f3b33707fb76d208f5839a40e770452dcf9f348bfd7faf2c524e0fa6710ed6"); Assert.Equal("Fie on you Gary", await parcel.ReadAsStringAsync()); } @@ -119,7 +119,7 @@ public async Task CanFetchParcel() [Fact] public async Task CanCreateParcel() { - var client = new BindleClient($"host=${DEMO_SERVER_URL}"); + var client = new BindleClient($"host={DEMO_SERVER_URL}"); await client.CreateParcel("mybindle/0.1.0", "460d5965e4d1909e8c7a3748a414956b7038ab5fd79937c9fcb2b214e6b0160a", "The front fell off"); var fetched = await client.GetParcel("mybindle/0.1.0", "460d5965e4d1909e8c7a3748a414956b7038ab5fd79937c9fcb2b214e6b0160a"); Assert.Equal("The front fell off", await fetched.ReadAsStringAsync()); @@ -128,7 +128,7 @@ public async Task CanCreateParcel() [Fact] public async Task CanListMissingParcels() { - var client = new BindleClient($"host=${DEMO_SERVER_URL}"); + var client = new BindleClient($"host={DEMO_SERVER_URL}"); var resp = await client.ListMissingParcels("mybindle/0.3.0"); Assert.Contains(resp.Missing, (label) => label.Sha256 == "e1706ab0a39ac88094b6d54a3f5cdba41fe5a901"); Assert.DoesNotContain(resp.Missing, (label) => label.Sha256 == "f7f3b33707fb76d208f5839a40e770452dcf9f348bfd7faf2c524e0fa6710ed6"); From 45e087c4fad23e7120092c0f8bfec2a44ba2d47b Mon Sep 17 00:00:00 2001 From: Matthew Fisher <matt.fisher@fermyon.com> Date: Tue, 7 Jun 2022 15:27:01 -0700 Subject: [PATCH 7/7] basic auth support Signed-off-by: Matthew Fisher <matt.fisher@fermyon.com> --- src/Bindle/BindleClient.cs | 5 +++++ src/Bindle/ConnectionInfo.cs | 21 +++++++++++++++++++ tests/Bindle.UnitTests/ConnectionInfoTests.cs | 19 +++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/Bindle/BindleClient.cs b/src/Bindle/BindleClient.cs index c4b2b2f..8bc74c3 100644 --- a/src/Bindle/BindleClient.cs +++ b/src/Bindle/BindleClient.cs @@ -31,6 +31,11 @@ public BindleClient(ConnectionInfo connectionInfo) } _httpClient = new HttpClient(handler) { BaseAddress = new Uri(SlashSafe(connectionInfo.BaseUri)) }; + + if (!string.IsNullOrEmpty(connectionInfo.UserName) && !string.IsNullOrEmpty(connectionInfo.Password)) + { + _httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", $"{connectionInfo.UserName}:{connectionInfo.Password}"); + } } private const string INVOICE_PATH = "_i"; diff --git a/src/Bindle/ConnectionInfo.cs b/src/Bindle/ConnectionInfo.cs index c59b6e9..b8d6334 100644 --- a/src/Bindle/ConnectionInfo.cs +++ b/src/Bindle/ConnectionInfo.cs @@ -21,16 +21,33 @@ public class ConnectionInfo "ssl mode" }; + static readonly string[] usernameAliases = { + "user", + "username" + }; + + static readonly string[] passwordAliases = { + "pass", + "passwd", + "password" + }; + private readonly Dictionary<string, string> keyValuePairs; public string BaseUri; + public string UserName; + + public string Password; + public SslMode? SslMode; public ConnectionInfo() { keyValuePairs = new Dictionary<string, string>(); BaseUri = "http://localhost:8080/v1/"; + UserName = String.Empty; + Password = String.Empty; } public ConnectionInfo(string connectionString) @@ -39,6 +56,10 @@ public ConnectionInfo(string connectionString) BaseUri = GetValue(serverAliases); + UserName = GetValue(usernameAliases); + + Password = GetValue(passwordAliases); + try { SslMode = Enum.Parse<SslMode>(GetValue(sslModeAliases), true); diff --git a/tests/Bindle.UnitTests/ConnectionInfoTests.cs b/tests/Bindle.UnitTests/ConnectionInfoTests.cs index 9a41ffb..d2b44aa 100644 --- a/tests/Bindle.UnitTests/ConnectionInfoTests.cs +++ b/tests/Bindle.UnitTests/ConnectionInfoTests.cs @@ -35,6 +35,25 @@ public void ShouldAcceptSslModeAliases() Assert.Equal(SslMode.VerifyFull, (new ConnectionInfo("sslmode=verifyfull").SslMode)); } + [Fact] + public void ShouldAcceptUserNameAliases() + { + Assert.Equal("", (new ConnectionInfo("").UserName)); + Assert.Equal("", (new ConnectionInfo("username=").UserName)); + Assert.Equal("spongebob", (new ConnectionInfo("username=spongebob").UserName)); + Assert.Equal("patrick", (new ConnectionInfo("user=patrick").UserName)); + } + + [Fact] + public void ShouldAcceptPasswordAliases() + { + Assert.Equal("", (new ConnectionInfo("").Password)); + Assert.Equal("", (new ConnectionInfo("password=").Password)); + Assert.Equal("imagoofygooberyeah", (new ConnectionInfo("password=imagoofygooberyeah").Password)); + Assert.Equal("uragoofygooberyeah", (new ConnectionInfo("pass=uragoofygooberyeah").Password)); + Assert.Equal("wereallgoofygoobersyeah", (new ConnectionInfo("passwd=wereallgoofygoobersyeah").Password)); + } + [Fact] public void ShouldAcceptMultipleOptions() {