From 12506e3e56a275ef288bf5a501004eaf2168cad4 Mon Sep 17 00:00:00 2001 From: Olivier Bellone Date: Sun, 17 Feb 2019 04:59:10 +0100 Subject: [PATCH] API key validation --- .../Public/StripeConfiguration.cs | 10 ++---- src/Stripe.net/Infrastructure/StringUtils.cs | 23 +++++++++++++ src/Stripe.net/Services/_base/Service.cs | 2 +- .../Services/_common/RequestOptions.cs | 10 +++++- .../Public/StripeConfigurationTest.cs | 14 ++++++++ .../Infrastructure/StringUtilsTest.cs | 32 +++++++++++++++++++ src/StripeTests/Services/_base/ServiceTest.cs | 16 ++++++++++ .../Services/_common/RequestOptionsTest.cs | 14 ++++++++ 8 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 src/StripeTests/Infrastructure/Public/StripeConfigurationTest.cs create mode 100644 src/StripeTests/Services/_common/RequestOptionsTest.cs diff --git a/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs b/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs index 5bc68d1827..1202954e6b 100644 --- a/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs +++ b/src/Stripe.net/Infrastructure/Public/StripeConfiguration.cs @@ -70,10 +70,7 @@ public static string ApiKey return apiKey; } - set - { - apiKey = value; - } + set => apiKey = StringUtils.ValidateApiKey(value); } /// Gets or sets the base URL for Stripe's OAuth API. @@ -126,10 +123,7 @@ public static IStripeClient StripeClient return stripeClient; } - set - { - stripeClient = value; - } + set => stripeClient = value; } /// Gets the version of the Stripe.net client library. diff --git a/src/Stripe.net/Infrastructure/StringUtils.cs b/src/Stripe.net/Infrastructure/StringUtils.cs index 9c13f29196..4e9731c959 100644 --- a/src/Stripe.net/Infrastructure/StringUtils.cs +++ b/src/Stripe.net/Infrastructure/StringUtils.cs @@ -6,6 +6,9 @@ namespace Stripe.Infrastructure internal static class StringUtils { + private static Regex apiKeyRegex + = new Regex(@"\A\w*\z", RegexOptions.Singleline | RegexOptions.CultureInvariant); + /// Converts the string to snake case. /// The string to convert. /// A string with the contents of the input string converted to snake_case. @@ -52,5 +55,25 @@ public static bool SecureEquals(string a, string b) return result == 0; } + + /// + /// Validates that the API key only contains valid characters (alphanumeric or underscores). + /// + /// The API key. + /// The API key, if valid. + /// + /// Thrown if the API key contains invalid characters. + /// + public static string ValidateApiKey(string apiKey) + { + if (!apiKeyRegex.IsMatch(apiKey ?? string.Empty)) + { + var message = $"Invalid API key: \"{apiKey}\". API keys may only contain alphanumeric " + + "characters and underscores."; + throw new StripeException(message); + } + + return apiKey; + } } } diff --git a/src/Stripe.net/Services/_base/Service.cs b/src/Stripe.net/Services/_base/Service.cs index 5337387c5c..68bd98f3af 100644 --- a/src/Stripe.net/Services/_base/Service.cs +++ b/src/Stripe.net/Services/_base/Service.cs @@ -18,7 +18,7 @@ protected Service() protected Service(string apiKey) { - this.ApiKey = apiKey; + this.ApiKey = StringUtils.ValidateApiKey(apiKey); } public string ApiKey { get; set; } diff --git a/src/Stripe.net/Services/_common/RequestOptions.cs b/src/Stripe.net/Services/_common/RequestOptions.cs index c9c0f00e2c..b403184129 100644 --- a/src/Stripe.net/Services/_common/RequestOptions.cs +++ b/src/Stripe.net/Services/_common/RequestOptions.cs @@ -1,9 +1,17 @@ namespace Stripe { + using Stripe.Infrastructure; + public class RequestOptions { + private string apiKey; + /// The API key to use for the request. - public string ApiKey { get; set; } + public string ApiKey + { + get => this.apiKey; + set => this.apiKey = StringUtils.ValidateApiKey(value); + } /// Idempotency key for safely retrying requests. public string IdempotencyKey { get; set; } diff --git a/src/StripeTests/Infrastructure/Public/StripeConfigurationTest.cs b/src/StripeTests/Infrastructure/Public/StripeConfigurationTest.cs new file mode 100644 index 0000000000..dd36a84427 --- /dev/null +++ b/src/StripeTests/Infrastructure/Public/StripeConfigurationTest.cs @@ -0,0 +1,14 @@ +namespace StripeTests +{ + using Stripe; + using Xunit; + + public class StripeConfigurationTest : BaseStripeTest + { + [Fact] + public void ApiKey_Set_ThrowsOnInvalidKey() + { + Assert.Throws(() => StripeConfiguration.ApiKey = "sk_test_123\n"); + } + } +} diff --git a/src/StripeTests/Infrastructure/StringUtilsTest.cs b/src/StripeTests/Infrastructure/StringUtilsTest.cs index 40f0973bfd..573eadb3aa 100644 --- a/src/StripeTests/Infrastructure/StringUtilsTest.cs +++ b/src/StripeTests/Infrastructure/StringUtilsTest.cs @@ -1,5 +1,6 @@ namespace StripeTests { + using Stripe; using Stripe.Infrastructure; using Xunit; @@ -48,5 +49,36 @@ public void SecureEquals() StringUtils.SecureEquals(testCase.data.a, testCase.data.b)); } } + + [Fact] + public void ValidateApiKey() + { + var testCases = new[] + { + new { data = "sk_test_123", throws = false }, + new { data = "sk_test_4eC39HqLyjWDarjtT1zdp7dc", throws = false }, + new { data = "abc", throws = false }, + new { data = string.Empty, throws = false }, + new { data = (string)null, throws = false }, + new { data = "sk_test_123\n", throws = true }, + new { data = "\nsk_test_123", throws = true }, + new { data = "sk_test_\n123", throws = true }, + new { data = "sk_test_123 ", throws = true }, + new { data = " sk_test_123", throws = true }, + new { data = "sk_test_ 123", throws = true }, + }; + + foreach (var testCase in testCases) + { + if (testCase.throws) + { + Assert.Throws(() => StringUtils.ValidateApiKey(testCase.data)); + } + else + { + Assert.Equal(testCase.data, StringUtils.ValidateApiKey(testCase.data)); + } + } + } } } diff --git a/src/StripeTests/Services/_base/ServiceTest.cs b/src/StripeTests/Services/_base/ServiceTest.cs index 9325652b7f..c8b5123bf2 100644 --- a/src/StripeTests/Services/_base/ServiceTest.cs +++ b/src/StripeTests/Services/_base/ServiceTest.cs @@ -10,6 +10,12 @@ namespace StripeTests public class ServiceTest : BaseStripeTest { + [Fact] + public void Ctor_ThrowsOnInvalidApiKey() + { + Assert.Throws(() => new TestService("sk_test_123\n")); + } + [Fact] public void Get_ExpandProperties() { @@ -65,6 +71,16 @@ private class TestService : Service, IListable, IRetrievable { + public TestService() + : base(null) + { + } + + public TestService(string apiKey) + : base(apiKey) + { + } + public override string BasePath => "/v1/test_entities"; public bool ExpandSimple { get; set; } diff --git a/src/StripeTests/Services/_common/RequestOptionsTest.cs b/src/StripeTests/Services/_common/RequestOptionsTest.cs new file mode 100644 index 0000000000..d92b427997 --- /dev/null +++ b/src/StripeTests/Services/_common/RequestOptionsTest.cs @@ -0,0 +1,14 @@ +namespace StripeTests +{ + using Stripe; + using Xunit; + + public class RequestOptionsTest : BaseStripeTest + { + [Fact] + public void ApiKey_Set_ThrowsOnInvalidKey() + { + Assert.Throws(() => new RequestOptions { ApiKey = "sk_test_123\n" }); + } + } +}