Repository: pretix/pretix

Issue:
stripe: check format of API keys in settings
The current settings form allows the stripe secret key to be submitted as the publishable keys. This can easily happen by accident (publishable key starts with `pk_` => "private key"). Since publishable keys always start with `pk_` and secret keys always start with `sk_`, a simple validator should do the trick.
stripe: check format of API keys in settings
The current settings form allows the stripe secret key to be submitted as the publishable keys. This can easily happen by accident (publishable key starts with `pk_` => "private key"). Since publishable keys always start with `pk_` and secret keys always start with `sk_`, a simple validator should do the trick.


Discussion:

Resolving patch:
diff --git a/src/pretix/plugins/stripe/payment.py b/src/pretix/plugins/stripe/payment.py
--- a/src/pretix/plugins/stripe/payment.py
+++ b/src/pretix/plugins/stripe/payment.py
@@ -33,6 +33,24 @@ class RefundForm(forms.Form):
     )
 
 
+class StripeKeyValidator():
+    def __init__(self, prefix):
+        assert isinstance(prefix, str)
+        assert len(prefix) > 0
+        self._prefix = prefix
+
+    def __call__(self, value):
+        if not value.startswith(self._prefix):
+            raise forms.ValidationError(
+                _('The provided key "%(value)s" does not look valid. It should start with "%(prefix)s".'),
+                code='invalid-stripe-secret-key',
+                params={
+                    'value': value,
+                    'prefix': self._prefix,
+                },
+            )
+
+
 class StripeSettingsHolder(BasePaymentProvider):
     identifier = 'stripe_settings'
     verbose_name = _('Stripe')
@@ -60,11 +78,17 @@ def settings_form_fields(self):
                      help_text=_('<a target="_blank" href="{docs_url}">{text}</a>').format(
                          text=_('Click here for a tutorial on how to obtain the required keys'),
                          docs_url='https://docs.pretix.eu/en/latest/user/payments/stripe.html'
-                     )
+                     ),
+                     validators=(
+                         StripeKeyValidator('sk_'),
+                     ),
                  )),
                 ('publishable_key',
                  forms.CharField(
                      label=_('Publishable key'),
+                     validators=(
+                         StripeKeyValidator('pk_'),
+                     ),
                  )),
                 ('ui',
                  forms.ChoiceField(


Test patch:
diff --git a/src/tests/plugins/stripe/test_settings.py b/src/tests/plugins/stripe/test_settings.py
--- a/src/tests/plugins/stripe/test_settings.py
+++ b/src/tests/plugins/stripe/test_settings.py
@@ -4,6 +4,28 @@
 
 from pretix.base.models import Event, Organizer, Team, User
 
+valid_secret_key_values = [
+    'sk_',
+    'sk_foo',
+]
+
+valid_publishable_key_values = [
+    'pk_',
+    'pk_foo',
+]
+
+invalid_secret_key_values = [
+    'skihaspartialprefix',
+    'ihasnoprefix',
+    'ihaspostfixsk_',
+]
+
+invalid_publishable_key_values = [
+    'pkihaspartialprefix',
+    'ihasnoprefix',
+    'ihaspostfixpk_',
+]
+
 
 @pytest.fixture
 def env(client):
@@ -21,12 +43,48 @@ def env(client):
     t.members.add(user)
     t.limit_events.add(event)
     client.force_login(user)
-    return client, event
+    url = '/control/event/%s/%s/settings/payment' % (event.organizer.slug, event.slug)
+    return client, event, url
 
 
 @pytest.mark.django_db
 def test_settings(env):
-    client, event = env
-    response = client.get('/control/event/%s/%s/settings/payment' % (event.organizer.slug, event.slug), follow=True)
+    client, event, url = env
+    response = client.get(url, follow=True)
     assert response.status_code == 200
     assert 'stripe__enabled' in response.rendered_content
+
+
+def _stripe_key_test(env, field, value, is_valid):
+    client, event, url = env
+    data = {'payment_stripe_' + field: value}
+    response = client.post(url, data)
+
+    if not is_valid:
+        assert 'does not look valid' in response.rendered_content
+    else:
+        assert 'does not look valid' not in response.rendered_content
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("value", invalid_secret_key_values)
+def test_settings_secret_key_invalid(env, value):
+    _stripe_key_test(env, 'secret_key', value, False)
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("value", invalid_publishable_key_values)
+def test_settings_publishable_key_invalid(env, value):
+    _stripe_key_test(env, 'publishable_key', value, False)
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("value", valid_secret_key_values)
+def test_settings_secret_key_valid(env, value):
+    _stripe_key_test(env, 'secret_key', value, True)
+
+
+@pytest.mark.django_db
+@pytest.mark.parametrize("value", valid_publishable_key_values)
+def test_settings_publishable_key_valid(env, value):
+    _stripe_key_test(env, 'publishable_key', value, True)