[FIX] payment_authorize: md5 to sha512 compat

This commit extends the changes introduced by 88de93114 to adapt
Odoo payment flows to the switch in transaction signature done
by Authorize.net.

The initial fix was not sufficient for flows that mixed redirection
payment flows and server-to-server flows (e.g. paying a quote with a
card that gets saved then using the token to pay for a subscription).

The problem comes from the fact that the server-to-server API uses
the API Transaction Key and API Login ID as credentials to authenticate
requests; there is no need for a signature since this data is never
publicly exposed on the website and a MITM is mitigated by the fact
that it would need to be done between the Odoo server and the
Authorize.net servers (both of which use https in a normal deployment)
which is admitedly more complex than doing a MITM on a Starbucks wifi.

On the other hand, the 'redirection' flow will include all transaction
parameters as inputs in an html form, therefore the signature is
required to ensure that the values have not been modified by a website
user or a mitm.

Since both flows can coexist on the same configuration, we cannot use
the same field depending on the payment flow configuration - we need
both fields to be stored for the provider.

This commit therefore has to introduce new fields on payment.acquirer
record that can store the signature key for authorize in addition to the
usual authorize fields. Instead of adding a new module, this commit uses
non-stored computed fields that will generate System Parameters entries
for any acquirer of the 'authorize' kind when set through the interface.

closes odoo/odoo#34670

Signed-off-by: Damien Bouvy (dbo) <dbo@odoo.com>
This commit is contained in:
Damien Bouvy
2019-07-16 05:25:09 +00:00
parent 565cb99ec0
commit f7b6d3b0b8
2 changed files with 27 additions and 3 deletions
+26 -3
View File
@@ -24,6 +24,7 @@ class PaymentAcquirerAuthorize(models.Model):
provider = fields.Selection(selection_add=[('authorize', 'Authorize.Net')])
authorize_login = fields.Char(string='API Login Id', required_if_provider='authorize', groups='base.group_user')
authorize_transaction_key = fields.Char(string='API Transaction Key', required_if_provider='authorize', groups='base.group_user')
authorize_signature_key = fields.Char(string='API Signature Key', groups='base.group_user', compute="_compute_auth_signature_key", inverse="_inverse_auth_signature_key")
def _get_feature_support(self):
"""Get advanced feature support by provider.
@@ -41,6 +42,16 @@ class PaymentAcquirerAuthorize(models.Model):
res['tokenize'].append('authorize')
return res
def _compute_auth_signature_key(self):
ICP = self.env['ir.config_parameter'].sudo()
for acquirer in self.filtered(lambda a: a.provider == 'authorize'):
acquirer.authorize_signature_key = ICP.get_param('payment_authorize.signature_key_%s' % acquirer.id)
def _inverse_auth_signature_key(self):
ICP = self.env['ir.config_parameter'].sudo()
for acquirer in self.filtered(lambda a: a.provider == 'authorize'):
ICP.set_param('payment_authorize.signature_key_%s' % acquirer.id, acquirer.authorize_signature_key)
def _get_authorize_urls(self, environment):
""" Authorize URLs """
if environment == 'prod':
@@ -56,12 +67,24 @@ class PaymentAcquirerAuthorize(models.Model):
values['x_amount'],
values['x_currency_code']])
# [BACKWARD COMPATIBILITY] Check that the merchant did update his transaction
# key to signature key (end of MD5 support from Authorize.net)
# [BACKWARD COMPATIBILITY, 2nd edition]
# The signature key is now '128-character hexadecimal format', while the
# transaction key was only 16-character.
if len(values['x_trans_key']) == 128:
# One of 2 things should have happened:
# 1/ the Transaction Key has been replaced with the Signature Key value (patch from March 2019)
# => Use that to sign, but server-to-server won't work since it uses transaction key
# as its credentials
# 2/ the Signature key is a new field (patch from July 2019)
# => Use that field for the signature
# FORWARD-PORT NOTE: be careful, hexadecimal decoding in python 3 is done by using bytes.fromhex(str)
# (P2) self.authorize_signature_key.decode("hex") ==> (P3) bytes.fromhex(self.authorize_signature_key)
# FORWARD-PORT NOTE NUMERO DOS: forward part to saas-12.4 but no further
if len(values['x_trans_key']) == 128 and not self.authorize_signature_key:
self.authorize_signature_key = values['x_trans_key'] # store in the correct field
return hmac.new(values['x_trans_key'].decode("hex"), data, hashlib.sha512).hexdigest().upper()
elif self.authorize_signature_key:
return hmac.new(self.authorize_signature_key.decode("hex"), data, hashlib.sha512).hexdigest().upper()
else:
return hmac.new(str(values['x_trans_key']), data, hashlib.md5).hexdigest()
@@ -9,6 +9,7 @@
<group attrs="{'invisible': [('provider', '!=', 'authorize')]}">
<field name="authorize_login"/>
<field name="authorize_transaction_key" password="True"/>
<field name="authorize_signature_key" password="True"/>
<a colspan="2" href="https://www.odoo.com/documentation/user/online/ecommerce/shopper_experience/authorize.html" target="_blank">How to get paid with Authorize.Net</a>
</group>
</xpath>