From e3f0c182ca1f0b548f269d5b35f8328354dfa1fd Mon Sep 17 00:00:00 2001 From: Cole Krumbholz Date: Wed, 31 Aug 2011 14:12:54 -0700 Subject: [PATCH 1/4] fixing issue: PayPalPDT._verify_postback() nukes PDT object on bad postback --- paypal/standard/pdt/models.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/paypal/standard/pdt/models.py b/paypal/standard/pdt/models.py index 72e7d7d..307c038 100644 --- a/paypal/standard/pdt/models.py +++ b/paypal/standard/pdt/models.py @@ -76,11 +76,12 @@ def _verify_postback(self): except ValueError, e: pass - qd = QueryDict('', mutable=True) - qd.update(response_dict) - qd.update(dict(ipaddress=self.ipaddress, st=self.st, flag_info=self.flag_info)) - pdt_form = PayPalPDTForm(qd, instance=self) - pdt_form.save(commit=False) + if not self.flag: + qd = QueryDict('', mutable=True) + qd.update(response_dict) + qd.update(dict(ipaddress=self.ipaddress, st=self.st, flag_info=self.flag_info)) + pdt_form = PayPalPDTForm(qd, instance=self) + pdt_form.save(commit=False) def send_signals(self): # Send the PDT signals... From 877a53e2e30092212dbe5f20c886e716f17fe754 Mon Sep 17 00:00:00 2001 From: Cole Krumbholz Date: Wed, 31 Aug 2011 18:52:32 -0700 Subject: [PATCH 2/4] a few tweeks to pdt error handling. 1) added settings.IGNORE_INVALID_PDT which won't store new pdt objects to the DB if they have invalid forms/postback responses, and 2) copy the txn_id over from the pdt GET method's tx parameter. This just makes invalid objects look better in the admin interface. (formerly they showed up as PDT: recurring since they didn't have a txn_id) --- paypal/standard/models.py | 13 +++++++++++-- paypal/standard/pdt/views.py | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/paypal/standard/models.py b/paypal/standard/models.py index 7a2cb4d..80a98aa 100644 --- a/paypal/standard/models.py +++ b/paypal/standard/models.py @@ -240,7 +240,13 @@ def verify(self, item_check_callable=None): """ self.response = self._postback() self._verify_postback() - if not self.flag: + + # self.flag is set if the paypal object is malformed. A non-validating + # PayPalPDTForm (see pdt.views.pdt()) or postbacks that don't verify + # will cause self.flag to be set. + invalid_paypal_obj = self.flag + + if not invalid_paypal_obj: if self.is_transaction(): if self.payment_status not in self.PAYMENT_STATUS_CHOICES: self.set_flag("Invalid payment_status. (%s)" % self.payment_status) @@ -256,7 +262,10 @@ def verify(self, item_check_callable=None): # @@@ Run a different series of checks on recurring payments. pass - self.save() + if not (invalid_paypal_obj and settings.IGNORE_INVALID_PDT): + # IPN objects get saved anyway, see ipn.views.ipn() + self.save() + self.send_signals() def verify_secret(self, form_instance, secret): diff --git a/paypal/standard/pdt/views.py b/paypal/standard/pdt/views.py index 0993411..d2308cd 100644 --- a/paypal/standard/pdt/views.py +++ b/paypal/standard/pdt/views.py @@ -41,6 +41,7 @@ def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None pdt_obj.initialize(request) if not failed: + pdt_obj.txn_id = txn_id # The PDT object gets saved during verify pdt_obj.verify(item_check_callable) else: From 3d9f3847f03c7a41eb6145834d2757ec62e34a83 Mon Sep 17 00:00:00 2001 From: Cole Krumbholz Date: Fri, 23 Sep 2011 03:40:26 -0500 Subject: [PATCH 3/4] fixes to the IGNORE_INVALID_PDT setting, plus a line in the README to explain the setting --- README.md | 3 +++ paypal/standard/models.py | 15 ++++++++++----- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1a0b063..fc1d75f 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,9 @@ Paypal Payment Data Transfer (PDT) allows you to display transaction details to ... ) +1. You can set settings.IGNORE_INVALID_PDT to ignore invalid PDT requests and keep them from filling up your db, a potential attack. + + Using PayPal Payments Standard with Subscriptions: -------------------------------------------------- diff --git a/paypal/standard/models.py b/paypal/standard/models.py index 80a98aa..7d63ebd 100644 --- a/paypal/standard/models.py +++ b/paypal/standard/models.py @@ -241,9 +241,6 @@ def verify(self, item_check_callable=None): self.response = self._postback() self._verify_postback() - # self.flag is set if the paypal object is malformed. A non-validating - # PayPalPDTForm (see pdt.views.pdt()) or postbacks that don't verify - # will cause self.flag to be set. invalid_paypal_obj = self.flag if not invalid_paypal_obj: @@ -262,8 +259,16 @@ def verify(self, item_check_callable=None): # @@@ Run a different series of checks on recurring payments. pass - if not (invalid_paypal_obj and settings.IGNORE_INVALID_PDT): - # IPN objects get saved anyway, see ipn.views.ipn() + # If settings.IGNORE_INVALID_PDT is set, don't save an invalid paypal + # object to the db. Invalid paypal objects include non-validating + # PayPalPDTForms (see pdt.views.pdt()) or postbacks that don't verify + # Keeps bad PDT requests from filling up your db, a potential attack. + # Note this only effects PDT, since IPN objects get saved during + # ipn.views.ipn(). + + if not invalid_paypal_obj or \ + not hasattr(settings, 'IGNORE_INVALID_PDT') or \ + not settings.IGNORE_INVALID_PDT: self.save() self.send_signals() From ba1cb4923c920b572c18a96064340d9cf625d027 Mon Sep 17 00:00:00 2001 From: Cole Krumbholz Date: Fri, 23 Sep 2011 03:49:04 -0500 Subject: [PATCH 4/4] Fixes required to get PDT working: made pdt() @csrf_exempt and removed @require_GET decorator, since paypal is POSTing PDT data as of 09/23/2011 --- paypal/standard/pdt/views.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/paypal/standard/pdt/views.py b/paypal/standard/pdt/views.py index d2308cd..c8eb47a 100644 --- a/paypal/standard/pdt/views.py +++ b/paypal/standard/pdt/views.py @@ -5,9 +5,19 @@ from django.views.decorators.http import require_GET from paypal.standard.pdt.models import PayPalPDT from paypal.standard.pdt.forms import PayPalPDTForm +from django.views.decorators.csrf import csrf_exempt - -@require_GET +# 09/23/2011 - Cole Krumbholz +# Paypal has started POSTing to the return_url +# GET QueryDict still contains expected parameters +# POST QueryDict now includes: +# +# Removing the require_GET decorator keeps a 405 error from hitting the user +# The view still operates as expected, albeit after limited testing +# +# @require_GET + +@csrf_exempt def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None): """Payment data transfer implementation: http://tinyurl.com/c9jjmw""" context = context or {} @@ -48,4 +58,4 @@ def pdt(request, item_check_callable=None, template="pdt/pdt.html", context=None pass # we ignore any PDT requests that don't have a transaction id context.update({"failed":failed, "pdt_obj":pdt_obj}) - return render_to_response(template, context, RequestContext(request)) \ No newline at end of file + return render_to_response(template, context, RequestContext(request))