summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorNicolas Martinelli <nim@odoo.com>2016-04-27 10:54:55 +0200
committerNicolas Martinelli <nim@odoo.com>2016-04-27 11:29:51 +0200
commit7d25dd56180ec60d9167f0c975d95b261a08223d (patch)
tree2a823f760b285cbbccb7eb3a4762456d304c3c5c /addons
parent4606250d914bb18517d6f5713fcd33348553dadc (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.py113
-rw-r--r--addons/payment_adyen/views/adyen.xml4
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">