diff options
| author | Nicolas Martinelli <nim@odoo.com> | 2016-04-27 10:54:55 +0200 |
|---|---|---|
| committer | Nicolas Martinelli <nim@odoo.com> | 2016-04-27 11:29:51 +0200 |
| commit | 7d25dd56180ec60d9167f0c975d95b261a08223d (patch) | |
| tree | 2a823f760b285cbbccb7eb3a4762456d304c3c5c /addons | |
| parent | 4606250d914bb18517d6f5713fcd33348553dadc (diff) | |
[IMP] payment_adyen: support autogenerated HMAC SHA-256 key
From v4.1.0 of the documentation (2015-06-29), the HMAC SHA-1 key is
deprecated. Indeed, it should be considered as a legacy option to
guarantee compatibility with older skins.
The HMAC SHA-256 key should be used instead, and moreover, it seems that
any new skin created does not support SHA-1 at all, even if the option
is available.
The merchant signature code is inspired from the documentation example.
Source: https://docs.adyen.com/developers/hpp-manual
Fixes #9786
opw-676081
Diffstat (limited to 'addons')
| -rw-r--r-- | addons/payment_adyen/models/adyen.py | 113 | ||||
| -rw-r--r-- | addons/payment_adyen/views/adyen.xml | 4 |
2 files changed, 98 insertions, 19 deletions
diff --git a/addons/payment_adyen/models/adyen.py b/addons/payment_adyen/models/adyen.py index 4d28bbb4d03..d8d06042365 100644 --- a/addons/payment_adyen/models/adyen.py +++ b/addons/payment_adyen/models/adyen.py @@ -2,7 +2,9 @@ import base64 import json -from hashlib import sha1 +import binascii +from collections import OrderedDict +import hashlib import hmac import logging import urlparse @@ -39,8 +41,57 @@ class AcquirerAdyen(osv.Model): 'adyen_skin_hmac_key': fields.char('Skin HMAC Key', required_if_provider='adyen'), } + def _adyen_generate_merchant_sig_sha256(self, acquirer, inout, values): + """ Generate the shasign for incoming or outgoing communications., when using the SHA-256 + signature. + + :param browse acquirer: the payment.acquirer browse record. It should + have a shakey in shaky out + :param string inout: 'in' (openerp contacting ogone) or 'out' (adyen + contacting openerp). In this last case only some + fields should be contained (see e-Commerce basic) + :param dict values: transaction values + :return string: shasign + """ + def escapeVal(val): + return val.replace('\\', '\\\\').replace(':', '\\:') + + def signParams(parms): + signing_string = ':'.join(map(escapeVal, parms.keys() + parms.values())) + hm = hmac.new(hmac_key, signing_string, hashlib.sha256) + return base64.b64encode(hm.digest()) + + assert inout in ('in', 'out') + assert acquirer.provider == 'adyen' + + if inout == 'in': + # All the fields sent to Adyen must be included in the signature. ALL the fucking + # fields, despite what is claimed in the documentation. For example, in + # https://docs.adyen.com/developers/hpp-manual, it is stated: "The resURL parameter does + # not need to be included in the signature." It's a trap, it must be included as well! + keys = [ + 'merchantReference', 'paymentAmount', 'currencyCode', 'shipBeforeDate', 'skinCode', + 'merchantAccount', 'sessionValidity', 'merchantReturnData', 'shopperEmail', + 'shopperReference', 'allowedMethods', 'blockedMethods', 'offset', + 'shopperStatement', 'recurringContract', 'billingAddressType', + 'deliveryAddressType', 'brandCode', 'countryCode', 'shopperLocale', 'orderData', + 'offerEmail', 'resURL', + ] + else: + keys = [ + 'authResult', 'merchantReference', 'merchantReturnData', 'paymentMethod', + 'pspReference', 'shopperLocale', 'skinCode', + ] + + hmac_key = binascii.a2b_hex(acquirer.adyen_skin_hmac_key.encode('ascii')) + raw_values = {k: values.get(k.encode('ascii'), '') for k in keys if k in values} + raw_values_ordered = OrderedDict(sorted(raw_values.items(), key=lambda t: t[0])) + + return signParams(raw_values_ordered) + def _adyen_generate_merchant_sig(self, acquirer, inout, values): - """ Generate the shasign for incoming or outgoing communications. + """ Generate the shasign for incoming or outgoing communications, when using the SHA-1 + signature (deprecated by Adyen). :param browse acquirer: the payment.acquirer browse record. It should have a shakey in shaky out @@ -66,7 +117,7 @@ class AcquirerAdyen(osv.Model): sign = ''.join('%s' % get_value(k) for k in keys).encode('ascii') key = acquirer.adyen_skin_hmac_key.encode('ascii') - return base64.b64encode(hmac.new(key, sign, sha1).digest()) + return base64.b64encode(hmac.new(key, sign, hashlib.sha1).digest()) def adyen_form_generate_values(self, cr, uid, id, values, context=None): base_url = self.pool['ir.config_parameter'].get_param(cr, uid, 'web.base.url') @@ -74,21 +125,42 @@ class AcquirerAdyen(osv.Model): # tmp import datetime from dateutil import relativedelta - tmp_date = datetime.date.today() + relativedelta.relativedelta(days=1) - - values.update({ - 'merchantReference': values['reference'], - 'paymentAmount': '%d' % int(float_round(values['amount'], 2) * 100), - 'currencyCode': values['currency'] and values['currency'].name or '', - 'shipBeforeDate': tmp_date, - 'skinCode': acquirer.adyen_skin_code, - 'merchantAccount': acquirer.adyen_merchant_account, - 'shopperLocale': values.get('partner_lang'), - 'sessionValidity': tmp_date, - 'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url), - 'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url') else False, - 'merchantSig': self._adyen_generate_merchant_sig(acquirer, 'in', values), - }) + + if acquirer.provider == 'adyen' and len(acquirer.adyen_skin_hmac_key) == 64: + tmp_date = datetime.datetime.today() + relativedelta.relativedelta(days=1) + + values.update({ + 'merchantReference': values['reference'], + 'paymentAmount': '%d' % int(float_round(values['amount'], 2) * 100), + 'currencyCode': values['currency'] and values['currency'].name or '', + 'shipBeforeDate': tmp_date.strftime('%Y-%m-%d'), + 'skinCode': acquirer.adyen_skin_code, + 'merchantAccount': acquirer.adyen_merchant_account, + 'shopperLocale': values.get('partner_lang', ''), + 'sessionValidity': tmp_date.isoformat('T')[:19] + "Z", + 'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url), + 'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url', '') else False, + 'shopperEmail': values.get('partner_email', ''), + }) + values['merchantSig'] = self._adyen_generate_merchant_sig_sha256(acquirer, 'in', values) + + else: + tmp_date = datetime.date.today() + relativedelta.relativedelta(days=1) + + values.update({ + 'merchantReference': values['reference'], + 'paymentAmount': '%d' % int(float_round(values['amount'], 2) * 100), + 'currencyCode': values['currency'] and values['currency'].name or '', + 'shipBeforeDate': tmp_date, + 'skinCode': acquirer.adyen_skin_code, + 'merchantAccount': acquirer.adyen_merchant_account, + 'shopperLocale': values.get('partner_lang'), + 'sessionValidity': tmp_date, + 'resURL': '%s' % urlparse.urljoin(base_url, AdyenController._return_url), + 'merchantReturnData': json.dumps({'return_url': '%s' % values.pop('return_url')}) if values.get('return_url') else False, + 'merchantSig': self._adyen_generate_merchant_sig(acquirer, 'in', values), + }) + return values def adyen_get_form_action_url(self, cr, uid, id, context=None): @@ -123,7 +195,10 @@ class TxAdyen(osv.Model): tx = self.pool['payment.transaction'].browse(cr, uid, tx_ids[0], context=context) # verify shasign - shasign_check = self.pool['payment.acquirer']._adyen_generate_merchant_sig(tx.acquirer_id, 'out', data) + if len(tx.acquirer_id.adyen_skin_hmac_key) == 64: + shasign_check = self.pool['payment.acquirer']._adyen_generate_merchant_sig_sha256(tx.acquirer_id, 'out', data) + else: + shasign_check = self.pool['payment.acquirer']._adyen_generate_merchant_sig(tx.acquirer_id, 'out', data) if shasign_check != data.get('merchantSig'): error_msg = _('Adyen: invalid merchantSig, received %s, computed %s') % (data.get('merchantSig'), shasign_check) _logger.warning(error_msg) diff --git a/addons/payment_adyen/views/adyen.xml b/addons/payment_adyen/views/adyen.xml index 76856158030..ff92d21dce2 100644 --- a/addons/payment_adyen/views/adyen.xml +++ b/addons/payment_adyen/views/adyen.xml @@ -19,6 +19,10 @@ <!-- custom --> <input t-if="merchantReturnData" type='hidden' name='merchantReturnData' t-att-value="merchantReturnData"/> + <!-- shopperEmail is not included for SHA-1. To avoid breaking compatibility, + include only if filled in --> + <input t-if="shopperEmail" + type='hidden' name='shopperEmail' t-att-value="shopperEmail"/> <!-- submit --> <button type="submit" width="100px" t-att-class="submit_class"> |
