Files
odoo_source/addons/payment_sips/controllers/main.py
T
Antoine Vandevenne (anv)andLucie Van Nieuwenhuyze 00259dc44a [IMP] payment_*: improve handling of webhook notifications
Notification handling in some acquirers presents a subset of the
following issues:
1. The signature of synchronous notifications (redirect payloads) is not
   checked. (Alipay, Authorize, Buckaroo, Mollie, PayU money, PayULatam)
2. When the signature check fails, we raise a ValidationError which
   counts as an HTTP 200 for some providers (it's not the case if they
   expect a specific string). (Adyen, Paypal,  Sips, Stripe)
3. If a ValidationError is raised when processing the feedback data, it
   is allowed to bubble up to the provider. (Alipay, Ogone)

The issues are respectively addressed as follows:
1. If the acquirer implements payments with redirection, make sure that
   if either makes a request to the provider to validate the data or
   that it verifies the signature. Verifying the origin of the request
   is not enough: the payload must be checked too.
2. Instead of raising ValidationError's, raise an HTTP 403 FORBIDDEN
   error if the signature check fails.
3. Wrap the call to `_handle_feedback_data` of the webhook method inside
   a try/except clause to catch any ValidationError, log a warning, and
   acknowledge the notification to avoid having the provider disable the
   webhook because of too many failures.

task-2688139
task-2693293

closes odoo/odoo#81607

Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
Co-authored-by: Lucie Van Nieuwenhuyze <luvn@odoo.com>
2022-01-27 17:11:52 +00:00

92 lines
4.0 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
# Original Copyright 2015 Eezee-It, modified and maintained by Odoo.
import hmac
import logging
import pprint
from werkzeug.exceptions import Forbidden
from odoo import http
from odoo.exceptions import ValidationError
from odoo.http import request
_logger = logging.getLogger(__name__)
class SipsController(http.Controller):
_return_url = '/payment/sips/return/'
_webhook_url = '/payment/sips/webhook/'
@http.route(
_return_url, type='http', auth='public', methods=['POST'], csrf=False, save_session=False
)
def sips_return_from_checkout(self, **data):
""" Process the notification data sent by SIPS after redirection from checkout.
The route is flagged with `save_session=False` to prevent Odoo from assigning a new session
to the user if they are redirected to this route with a POST request. Indeed, as the session
cookie is created without a `SameSite` attribute, some browsers that don't implement the
recommended default `SameSite=Lax` behavior will not include the cookie in the redirection
request from the payment provider to Odoo. As the redirection to the '/payment/status' page
will satisfy any specification of the `SameSite` attribute, the session of the user will be
retrieved and with it the transaction which will be immediately post-processed.
:param dict data: The notification data
"""
_logger.info("handling redirection from SIPS with data:\n%s", pprint.pformat(data))
# Check the integrity of the notification
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_feedback_data(
'sips', data
)
self._verify_notification_signature(data, tx_sudo)
# Handle the notification data
request.env['payment.transaction'].sudo()._handle_feedback_data('sips', data)
return request.redirect('/payment/status')
@http.route(_webhook_url, type='http', auth='public', methods=['POST'], csrf=False)
def sips_webhook(self, **data):
""" Process the notification data sent by SIPS to the webhook.
:param dict data: The notification data
:return: An empty string to acknowledge the notification
:rtype: str
"""
_logger.info("notification received from SIPS with data:\n%s", pprint.pformat(data))
try:
# Check the integrity of the notification
tx_sudo = request.env['payment.transaction'].sudo()._get_tx_from_feedback_data(
'sips', data
)
self._verify_notification_signature(data, tx_sudo)
# Handle the notification data
request.env['payment.transaction'].sudo()._handle_feedback_data('sips', data)
except ValidationError:
_logger.exception("unable to handle the notification data; skipping to acknowledge")
return ''
@staticmethod
def _verify_notification_signature(notification_data, tx_sudo):
""" Check that the received signature matches the expected one.
:param dict notification_data: The notification data
:param recordset tx_sudo: The sudoed transaction referenced by the notification data, as a
`payment.transaction` record
:return: None
:raise: :class:`werkzeug.exceptions.Forbidden` if the signatures don't match
"""
# Retrieve the received signature from the data
received_signature = notification_data.get('Seal')
if not received_signature:
_logger.warning("received notification with missing signature")
raise Forbidden()
# Compare the received signature with the expected signature computed from the data
expected_signature = tx_sudo.acquirer_id._sips_generate_shasign(notification_data['Data'])
if not hmac.compare_digest(received_signature, expected_signature):
_logger.warning("received notification with invalid signature")
raise Forbidden()