forked from dj-stripe/dj-stripe
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchecks.py
263 lines (211 loc) · 8.7 KB
/
checks.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
"""
dj-stripe System Checks
"""
import re
from django.core import checks
from django.db.utils import DatabaseError
STRIPE_API_VERSION_PATTERN = re.compile(
r"(?P<year>\d{4})-(?P<month>\d{1,2})-(?P<day>\d{1,2})(; [\w=]*)?$"
)
# 4 possibilities:
# Keys in admin and in settings
# Keys in admin and not in settings
# Keys not in admin but in settings
# Keys not in admin and not in settings
@checks.register("djstripe")
def check_stripe_api_key(app_configs=None, **kwargs):
"""Check the user has configured API live/test keys correctly."""
def _check_stripe_api_in_settings(messages):
if djstripe_settings.STRIPE_LIVE_MODE:
if not djstripe_settings.LIVE_API_KEY.startswith(("sk_live_", "rk_live_")):
msg = "Bad Stripe live API key."
hint = 'STRIPE_LIVE_SECRET_KEY should start with "sk_live_"'
messages.append(checks.Info(msg, hint=hint, id="djstripe.I003"))
elif not djstripe_settings.TEST_API_KEY.startswith(("sk_test_", "rk_test_")):
msg = "Bad Stripe test API key."
hint = 'STRIPE_TEST_SECRET_KEY should start with "sk_test_"'
messages.append(checks.Info(msg, hint=hint, id="djstripe.I004"))
from djstripe.models import APIKey
from .settings import djstripe_settings
messages = []
try:
# get all APIKey objects in the db
api_qs = APIKey.objects.all()
if not api_qs.exists():
msg = (
"You don't have any API Keys in the database. Did you forget to add"
" them?"
)
hint = (
"Add STRIPE_TEST_SECRET_KEY and STRIPE_LIVE_SECRET_KEY directly from"
" the Django Admin."
)
messages.append(checks.Info(msg, hint=hint, id="djstripe.I001"))
# Keys not in admin but in settings
if djstripe_settings.STRIPE_SECRET_KEY:
msg = (
"Your keys are defined in the settings files. You can now add and"
" manage them directly from the django admin."
)
hint = (
"Add STRIPE_TEST_SECRET_KEY and STRIPE_LIVE_SECRET_KEY directly"
" from the Django Admin."
)
messages.append(checks.Info(msg, hint=hint, id="djstripe.I002"))
# Ensure keys defined in settings files are valid
_check_stripe_api_in_settings(messages)
# Keys in admin and in settings
elif djstripe_settings.STRIPE_SECRET_KEY:
msg = (
"Your keys are defined in the settings files and are also in the admin."
" You can now add and manage them directly from the django admin."
)
hint = (
"We suggest adding STRIPE_TEST_SECRET_KEY and STRIPE_LIVE_SECRET_KEY"
" directly from the Django Admin. And removing them from the settings"
" files."
)
messages.append(checks.Info(msg, hint=hint, id="djstripe.I002"))
# Ensure keys defined in settings files are valid
_check_stripe_api_in_settings(messages)
except DatabaseError:
# Skip the check - Database most likely not migrated yet
return []
return messages
def validate_stripe_api_version(version):
"""
Check the API version is formatted correctly for Stripe.
The expected format is `YYYY-MM-DD` (an iso8601 date) or
for access to alpha or beta releases the expected format is: `YYYY-MM-DD; modelname_version=version_number`.
Ex "2020-08-27; orders_beta=v3"
:param version: The version to set for the Stripe API.
:type version: ``str``
:returns bool: Whether the version is formatted correctly.
"""
return re.match(STRIPE_API_VERSION_PATTERN, version)
@checks.register("djstripe")
def check_stripe_api_version(app_configs=None, **kwargs):
"""Check the user has configured API version correctly."""
from .settings import djstripe_settings
messages = []
default_version = djstripe_settings.DEFAULT_STRIPE_API_VERSION
version = djstripe_settings.STRIPE_API_VERSION
if not validate_stripe_api_version(version):
msg = f"Invalid Stripe API version: {version!r}"
hint = "STRIPE_API_VERSION should be formatted as: YYYY-MM-DD"
messages.append(checks.Critical(msg, hint=hint, id="djstripe.C004"))
return messages
@checks.register("djstripe")
def check_stripe_api_host(app_configs=None, **kwargs):
"""
Check that STRIPE_API_HOST is not being used in production.
"""
from django.conf import settings
messages = []
if not settings.DEBUG and hasattr(settings, "STRIPE_API_HOST"):
messages.append(
checks.Warning(
(
"STRIPE_API_HOST should not be set in production! "
"This is most likely unintended."
),
hint="Remove STRIPE_API_HOST from your Django settings.",
id="djstripe.W002",
)
)
return messages
@checks.register("djstripe")
def check_webhook_endpoint_has_secret(app_configs=None, **kwargs):
"""Checks if all Webhook Endpoints have not empty secrets."""
from djstripe.models import WebhookEndpoint
messages = []
try:
qs = list(WebhookEndpoint.objects.filter(secret="").all())
except DatabaseError:
# Skip the check - Database most likely not migrated yet
return []
for webhook in qs:
webhook_url = webhook.get_stripe_dashboard_url()
messages.append(
checks.Warning(
(
f"The secret of Webhook Endpoint: {webhook} is not populated "
"in the db. Events sent to it will not work properly."
),
hint=(
"This can happen if it was deleted and resynced as Stripe "
"sends the webhook secret ONLY on the creation call. "
"Please use the django shell and update the secret with "
f"the value from {webhook_url}"
),
id="djstripe.W005",
)
)
return messages
@checks.register("djstripe")
def check_subscriber_key_length(app_configs=None, **kwargs):
"""
Check that DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY fits in metadata.
Docs: https://stripe.com/docs/api#metadata
"""
from .settings import djstripe_settings
messages = []
key = djstripe_settings.SUBSCRIBER_CUSTOMER_KEY
key_max_length = 40
if key and len(key) > key_max_length:
messages.append(
checks.Error(
(
"DJSTRIPE_SUBSCRIBER_CUSTOMER_KEY must be no more than "
f"{key_max_length} characters long"
),
hint=f"Current value: {key!r}",
id="djstripe.E001",
)
)
return messages
@checks.register("djstripe")
def check_webhook_event_callback_accepts_api_key(app_configs=None, **kwargs):
"""
Checks if the custom callback accepts atleast 2 mandatory positional arguments
"""
from inspect import signature
from .settings import djstripe_settings
messages = []
# callable can have exactly 2 arguments or
# if more than two, the rest need to be optional.
callable = djstripe_settings.WEBHOOK_EVENT_CALLBACK
if callable:
# Deprecated in 2.8.0. Raise a warning.
messages.append(
checks.Warning(
(
"DJSTRIPE_WEBHOOK_EVENT_CALLBACK is deprecated. See release notes"
" for details."
),
hint=(
"If you need to trigger a function during webhook processing, "
"you can use djstripe.signals instead.\n"
"Available signals:\n"
"- djstripe.signals.webhook_pre_validate\n"
"- djstripe.signals.webhook_post_validate\n"
"- djstripe.signals.webhook_pre_process\n"
"- djstripe.signals.webhook_post_process\n"
"- djstripe.signals.webhook_processing_error"
),
)
)
sig = signature(callable)
signature_sz = len(sig.parameters.keys())
if signature_sz < 2:
messages.append(
checks.Error(
f"{callable} accepts {signature_sz} arguments.",
hint=(
"You may have forgotten to add api_key parameter to your custom"
" callback."
),
id="djstripe.E004",
)
)
return messages