11import base64
22import logging
3+ import re
34import warnings
45from builtins import bytes
5-
6- from django_ses .deprecation import RemovedInDjangoSES20Warning
7-
6+ from urllib .error import URLError
87from urllib .parse import urlparse
98from urllib .request import urlopen
10- from urllib .error import URLError
119
1210from django .core .exceptions import ImproperlyConfigured
11+
1312from django_ses import settings
13+ from django_ses .deprecation import RemovedInDjangoSES20Warning
1414
1515logger = logging .getLogger (__name__ )
1616
1717_CERT_CACHE = {}
1818
19+ SES_REGEX_CERT_URL = re .compile (
20+ "(?i)^https://sns\.[a-z0-9\-]+\.amazonaws\.com(\.cn)?/SimpleNotificationService\-[a-z0-9]+\.pem$"
21+ )
22+
1923
2024def clear_cert_cache ():
2125 """Clear the certificate cache.
@@ -183,6 +187,17 @@ def _get_cert_url(self):
183187 url_obj = urlparse (cert_url )
184188 for trusted_domain in settings .EVENT_CERT_DOMAINS :
185189 parts = trusted_domain .split ("." )
190+ if "amazonaws.com" in trusted_domain :
191+ if not SES_REGEX_CERT_URL .match (cert_url ):
192+ if len (parts ) < 4 :
193+ return None
194+ else :
195+ logger .warning ('Possible security risk for: "%s"' , cert_url )
196+ logger .warning (
197+ "It is strongly recommended to configure the full domain in EVENT_CERT_DOMAINS. "
198+ "See v3.5.0 release notes for more details."
199+ )
200+
186201 if url_obj .netloc .split ("." )[- len (parts ) :] == parts :
187202 return cert_url
188203
@@ -196,26 +211,28 @@ def _get_bytes_to_sign(self):
196211
197212 # Depending on the message type the fields to add to the message
198213 # differ so we handle that here.
199- msg_type = self ._data .get (' Type' )
200- if msg_type == ' Notification' :
214+ msg_type = self ._data .get (" Type" )
215+ if msg_type == " Notification" :
201216 fields_to_sign = [
202- ' Message' ,
203- ' MessageId' ,
204- ' Subject' ,
205- ' Timestamp' ,
206- ' TopicArn' ,
207- ' Type' ,
217+ " Message" ,
218+ " MessageId" ,
219+ " Subject" ,
220+ " Timestamp" ,
221+ " TopicArn" ,
222+ " Type" ,
208223 ]
209- elif (msg_type == 'SubscriptionConfirmation' or
210- msg_type == 'UnsubscribeConfirmation' ):
224+ elif (
225+ msg_type == "SubscriptionConfirmation"
226+ or msg_type == "UnsubscribeConfirmation"
227+ ):
211228 fields_to_sign = [
212- ' Message' ,
213- ' MessageId' ,
214- ' SubscribeURL' ,
215- ' Timestamp' ,
216- ' Token' ,
217- ' TopicArn' ,
218- ' Type' ,
229+ " Message" ,
230+ " MessageId" ,
231+ " SubscribeURL" ,
232+ " Timestamp" ,
233+ " Token" ,
234+ " TopicArn" ,
235+ " Type" ,
219236 ]
220237 else :
221238 # Unrecognized type
@@ -237,14 +254,14 @@ def _get_bytes_to_sign(self):
237254
238255def BounceMessageVerifier (* args , ** kwargs ):
239256 warnings .warn (
240- ' utils.BounceMessageVerifier is deprecated. It is renamed to EventMessageVerifier.' ,
257+ " utils.BounceMessageVerifier is deprecated. It is renamed to EventMessageVerifier." ,
241258 RemovedInDjangoSES20Warning ,
242259 )
243260
244261 # parameter name is renamed from bounce_dict to notification.
245- if ' bounce_dict' in kwargs :
246- kwargs [' notification' ] = kwargs [' bounce_dict' ]
247- del kwargs [' bounce_dict' ]
262+ if " bounce_dict" in kwargs :
263+ kwargs [" notification" ] = kwargs [" bounce_dict" ]
264+ del kwargs [" bounce_dict" ]
248265
249266 return EventMessageVerifier (* args , ** kwargs )
250267
@@ -262,31 +279,32 @@ def verify_bounce_message(msg):
262279 Verify an SES/SNS bounce(event) notification message.
263280 """
264281 warnings .warn (
265- ' utils.verify_bounce_message is deprecated. It is renamed to verify_event_message.' ,
282+ " utils.verify_bounce_message is deprecated. It is renamed to verify_event_message." ,
266283 RemovedInDjangoSES20Warning ,
267284 )
268285 return verify_event_message (msg )
269286
270287
271288def confirm_sns_subscription (notification ):
272289 logger .info (
273- ' Received subscription confirmation: TopicArn: %s' ,
274- notification .get (' TopicArn' ),
290+ " Received subscription confirmation: TopicArn: %s" ,
291+ notification .get (" TopicArn" ),
275292 extra = {
276- ' notification' : notification ,
293+ " notification" : notification ,
277294 },
278295 )
279296
280297 # Get the subscribe url and hit the url to confirm the subscription.
281- subscribe_url = notification .get (' SubscribeURL' )
298+ subscribe_url = notification .get (" SubscribeURL" )
282299 try :
283300 urlopen (subscribe_url ).read ()
284301 except URLError as e :
285302 # Some kind of error occurred when confirming the request.
286303 logger .error (
287- 'Could not confirm subscription: "%s"' , e ,
304+ 'Could not confirm subscription: "%s"' ,
305+ e ,
288306 extra = {
289- ' notification' : notification ,
307+ " notification" : notification ,
290308 },
291309 exc_info = True ,
292310 )
0 commit comments