diff --git a/src/pretix/control/forms/global_settings.py b/src/pretix/control/forms/global_settings.py index 24687e7d9..1049ac287 100644 --- a/src/pretix/control/forms/global_settings.py +++ b/src/pretix/control/forms/global_settings.py @@ -1,4 +1,5 @@ from collections import OrderedDict +from typing import List, Union from django import forms from django.conf import settings @@ -142,10 +143,53 @@ def __init__(self, *args, **kwargs): self.fields['banner_message'].widget.attrs['rows'] = '2' self.fields['banner_message_detail'].widget.attrs['rows'] = '3' self.fields = OrderedDict(list(self.fields.items()) + [ - ('stripe_webhook_secret_key', SecretKeySettingsField( - label=_('Stripe Webhook: Signing secret'), - required=False, - )), + ( + 'payment_stripe_connect_secret_key', + SecretKeySettingsField( + label=_('Stripe Connect: Secret key'), + required=False, + validators=( + StripeKeyValidator(['sk_live_', 'rk_live_']), + ), + ) + ), + ( + 'payment_stripe_connect_publishable_key', + forms.CharField( + label=_('Stripe Connect: Publishable key'), + required=False, + validators=( + StripeKeyValidator('pk_live_'), + ), + ) + ), + ( + 'payment_stripe_connect_test_secret_key', + SecretKeySettingsField( + label=_('Stripe Connect: Secret key (test)'), + required=False, + validators=( + StripeKeyValidator(['sk_test_', 'rk_test_']), + ), + ) + ), + ( + 'payment_stripe_connect_test_publishable_key', + forms.CharField( + label=_('Stripe Connect: Publishable key (test)'), + required=False, + validators=( + StripeKeyValidator('pk_test_'), + ), + ) + ), + ( + 'stripe_webhook_secret_key', + SecretKeySettingsField( + label=_('Stripe Webhook: Secret key'), + required=False, + ) + ), ( "ticket_fee_percentage", forms.DecimalField( @@ -195,3 +239,46 @@ class SSOConfigForm(SettingsForm): def __init__(self, *args, **kwargs): self.obj = GlobalSettingsObject() super().__init__(*args, obj=self.obj, **kwargs) + + +class StripeKeyValidator: + """ + Validates that a given Stripe key starts with the expected prefix(es). + + This validator ensures that Stripe API keys conform to the expected format + by checking their prefixes. It supports both single prefix validation and + multiple prefix validation. + """ + def __init__(self, prefix: Union[str, List[str]]) -> None: + if not prefix: + raise ValueError("Prefix cannot be empty") + + if isinstance(prefix, list): + if not all(isinstance(p, str) and p for p in prefix): + raise ValueError("All prefixes must be non-empty strings") + self._prefixes = prefix + elif isinstance(prefix, str): + if not prefix.strip(): + raise ValueError("Prefix cannot be whitespace") + self._prefixes = [prefix] + + def __call__(self, value: str) -> None: + if not value: + raise forms.ValidationError( + _("The Stripe key cannot be empty."), + code='invalid-stripe-key' + ) + + if not any(value.startswith(p) for p in self._prefixes): + if len(self._prefixes) == 1: + message = _('The provided key does not look valid. It should start with "%(prefix)s".') + params = {'value': value, 'prefix': self._prefixes[0]} + else: + message = _('The provided key does not look valid. It should start with one of: %(prefixes)s') + params = {'value': value, 'prefixes': ', '.join(f'"{p}"' for p in self._prefixes)} + + raise forms.ValidationError( + message, + code='invalid-stripe-key', + params=params + )