Files
odoo_source/addons/account_payment/controllers/payment.py
T
Antoine Vandevenne (anv) 3ef9310ec3 [FIX] (account_)payment, sale: check document access in payment routes
Commit 4d1a1f1c introduced a systematic check on the kwargs passed to
transaction routes of modules integrating with online payments, but
failed to check the access token of documents whose ID is passed to
payment routes. This allowed retrieving the access token of such
documents by visiting a route that did not check the document access
(e.g., /my/payment_method) and passing an arbitrary document ID
(e.g, sale_order_id=123). The route's controller would reroute the
payment flow to the document's portal page and render the landing route
of the flow on the payment form, with the access token included.

This commit makes sure that we always check the access token of a
document before reading rerouting a payment flow.

closes odoo/odoo#138238

Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
2023-10-12 13:16:46 +00:00

125 lines
6.0 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import _
from odoo.exceptions import AccessError, MissingError, ValidationError
from odoo.fields import Command
from odoo.http import request, route
from odoo.addons.payment import utils as payment_utils
from odoo.addons.payment.controllers import portal as payment_portal
class PaymentPortal(payment_portal.PaymentPortal):
@route('/invoice/transaction/<int:invoice_id>', type='json', auth='public')
def invoice_transaction(self, invoice_id, access_token, **kwargs):
""" Create a draft transaction and return its processing values.
:param int invoice_id: The invoice to pay, as an `account.move` id
:param str access_token: The access token used to authenticate the request
:param dict kwargs: Locally unused data passed to `_create_transaction`
:return: The mandatory values for the processing of the transaction
:rtype: dict
:raise: ValidationError if the invoice id or the access token is invalid
"""
# Check the invoice id and the access token
try:
invoice_sudo = self._document_check_access('account.move', invoice_id, access_token)
except MissingError as error:
raise error
except AccessError:
raise ValidationError(_("The access token is invalid."))
logged_in = not request.env.user._is_public()
partner_sudo = request.env.user.partner_id if logged_in else invoice_sudo.partner_id
self._validate_transaction_kwargs(kwargs)
kwargs.update({
'currency_id': invoice_sudo.currency_id.id,
'partner_id': partner_sudo.id,
}) # Inject the create values taken from the invoice into the kwargs.
tx_sudo = self._create_transaction(
custom_create_values={'invoice_ids': [Command.set([invoice_id])]}, **kwargs,
)
return tx_sudo._get_processing_values()
# Payment overrides
@route()
def payment_pay(self, *args, amount=None, invoice_id=None, access_token=None, **kwargs):
""" Override of `payment` to replace the missing transaction values by that of the invoice.
:param str amount: The (possibly partial) amount to pay used to check the access token.
:param str invoice_id: The invoice for which a payment id made, as an `account.move` id.
:param str access_token: The access token used to authenticate the partner.
:return: The result of the parent method.
:rtype: str
:raise ValidationError: If the invoice id is invalid.
"""
# Cast numeric parameters as int or float and void them if their str value is malformed.
amount = self._cast_as_float(amount)
invoice_id = self._cast_as_int(invoice_id)
if invoice_id:
invoice_sudo = request.env['account.move'].sudo().browse(invoice_id).exists()
if not invoice_sudo:
raise ValidationError(_("The provided parameters are invalid."))
# Check the access token against the invoice values. Done after fetching the invoice
# as we need the invoice fields to check the access token.
if not payment_utils.check_access_token(
access_token, invoice_sudo.partner_id.id, amount, invoice_sudo.currency_id.id
):
raise ValidationError(_("The provided parameters are invalid."))
kwargs.update({
# To display on the payment form; will be later overwritten when creating the tx.
'reference': invoice_sudo.name,
# To fix the currency if incorrect and avoid mismatches when creating the tx.
'currency_id': invoice_sudo.currency_id.id,
# To fix the partner if incorrect and avoid mismatches when creating the tx.
'partner_id': invoice_sudo.partner_id.id,
'company_id': invoice_sudo.company_id.id,
'invoice_id': invoice_id,
})
return super().payment_pay(*args, amount=amount, access_token=access_token, **kwargs)
def _get_extra_payment_form_values(self, invoice_id=None, access_token=None, **kwargs):
""" Override of `payment` to reroute the payment flow to the portal view of the invoice.
:param str invoice_id: The invoice for which a payment id made, as an `account.move` id.
:param str access_token: The portal or payment access token, respectively if we are in a
portal or payment link flow.
:return: The extended rendering context values.
:rtype: dict
"""
form_values = super()._get_extra_payment_form_values(
invoice_id=invoice_id, access_token=access_token, **kwargs
)
if invoice_id:
invoice_id = self._cast_as_int(invoice_id)
try: # Check document access against what could be a portal access token.
invoice_sudo = self._document_check_access('account.move', invoice_id, access_token)
except AccessError: # It is a payment access token computed on the payment context.
if not payment_utils.check_access_token(
access_token,
kwargs.get('partner_id'),
kwargs.get('amount'),
kwargs.get('currency_id'),
):
raise
invoice_sudo = request.env['account.move'].sudo().browse(invoice_id)
# Interrupt the payment flow if the invoice has been canceled.
if invoice_sudo.state == 'cancel':
form_values['amount'] = 0.0
# Reroute the next steps of the payment flow to the portal view of the invoice.
form_values.update({
'transaction_route': f'/invoice/transaction/{invoice_id}',
'landing_route': f'{invoice_sudo.access_url}'
f'?access_token={invoice_sudo._portal_ensure_token()}',
'access_token': invoice_sudo.access_token,
})
return form_values