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 7a2cb4d..7d63ebd 100644 --- a/paypal/standard/models.py +++ b/paypal/standard/models.py @@ -240,7 +240,10 @@ def verify(self, item_check_callable=None): """ self.response = self._postback() self._verify_postback() - if not self.flag: + + 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 +259,18 @@ def verify(self, item_check_callable=None): # @@@ Run a different series of checks on recurring payments. pass - self.save() + # 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() def verify_secret(self, form_instance, secret): 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... diff --git a/paypal/standard/pdt/views.py b/paypal/standard/pdt/views.py index 0993411..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 {} @@ -41,10 +51,11 @@ 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: 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))