diff --git a/cartridges/int_alma/cartridge/controllers/Alma.js b/cartridges/int_alma/cartridge/controllers/Alma.js index b8a2d243..e821bb72 100644 --- a/cartridges/int_alma/cartridge/controllers/Alma.js +++ b/cartridges/int_alma/cartridge/controllers/Alma.js @@ -198,10 +198,24 @@ server.get( server.get('IPN', function (req, res, next) { var paymentHelper = require('*/cartridge/scripts/helpers/almaPaymentHelper'); var orderHelper = require('*/cartridge/scripts/helpers/almaOrderHelper'); + var almaSecurityHelper = require('*/cartridge/scripts/helpers/almaSecurityHelper'); + var almaHelpers = require('*/cartridge/scripts/helpers/almaHelpers'); var paymentObj = null; + var paymentId = req.querystring.pid; + var signature = req.httpHeaders.get('X-Alma-Signature'); try { - paymentObj = buildPaymentObj(req.querystring.pid); + almaSecurityHelper.checkIpnSignature(signature, paymentId, almaHelpers.getApiKey()); + } catch (e) { + res.setStatusCode(500); + res.render('error', { + message: e.message + }); + return next(); + } + + try { + paymentObj = buildPaymentObj(paymentId); } catch (e) { res.setStatusCode(500); res.render('error', { @@ -209,12 +223,12 @@ server.get('IPN', function (req, res, next) { }); return next(); } - var order = getOrderByAlmaPaymentId(req.querystring.pid); + var order = getOrderByAlmaPaymentId(paymentId); if (!order) { var basketUuid = paymentObj.custom_data.basket_id; order = paymentHelper.createOrderFromBasketUUID(basketUuid); - orderHelper.addPidToOrder(order, req.querystring.pid); + orderHelper.addPidToOrder(order, paymentId); } if (!order) { diff --git a/cartridges/int_alma/cartridge/scripts/helpers/almaHelpers.js b/cartridges/int_alma/cartridge/scripts/helpers/almaHelpers.js index 9e0fc6ee..6ac3a463 100644 --- a/cartridges/int_alma/cartridge/scripts/helpers/almaHelpers.js +++ b/cartridges/int_alma/cartridge/scripts/helpers/almaHelpers.js @@ -16,13 +16,22 @@ function getSfccVersion() { return sfccMajor + '.' + sfccMinor; } + +/** + * Return current Api key + * @returns {string} current api key + */ +function getApiKey() { + return Site.getCurrent().getCustomPreferenceValue('ALMA_APIKey'); +} + /** * Adds common headers to request * @param {dw.svc.Service} service - current service instance * @returns {dw.svc.Service} service */ function addHeaders(service) { - var apiKey = Site.getCurrent().getCustomPreferenceValue('ALMA_APIKey'); + var apiKey = getApiKey(); if (!apiKey) { logger.error('Alma api key is not configured'); return service; @@ -223,5 +232,6 @@ module.exports = { isAlmaOnShipment: isAlmaOnShipment, getSfccVersion: getSfccVersion, haveExcludedCategory: haveExcludedCategory, - formatItem: formatItem + formatItem: formatItem, + getApiKey: getApiKey }; diff --git a/cartridges/int_alma/cartridge/scripts/helpers/almaSecurityHelper.js b/cartridges/int_alma/cartridge/scripts/helpers/almaSecurityHelper.js new file mode 100644 index 00000000..21561ff4 --- /dev/null +++ b/cartridges/int_alma/cartridge/scripts/helpers/almaSecurityHelper.js @@ -0,0 +1,30 @@ +'use strict'; + +var Mac = require('dw/crypto/Mac'); +var Encoding = require('dw/crypto/Encoding'); + + +/** +* Check is the IPN signature is valid +* @param {string|null} almaSignature signature to check +* @param {string} paymentId Paymewnt id +* @param {string} key key to check the signature +* @throws Error +*/ +function checkIpnSignature(almaSignature, paymentId, key) { + if (!almaSignature) { + throw new Error('There is no signature in header'); + } + + var mac = new Mac(Mac.HMAC_SHA_256); + var hmac = mac.digest(paymentId, key); + var hmacHex = Encoding.toHex(hmac); + + if (hmacHex !== almaSignature) { + throw new Error('Signature is not valid'); + } +} + +module.exports = { + checkIpnSignature: checkIpnSignature +}; diff --git a/test/mocks/helpers/almaSecurityHelper.js b/test/mocks/helpers/almaSecurityHelper.js new file mode 100644 index 00000000..31b8fd0f --- /dev/null +++ b/test/mocks/helpers/almaSecurityHelper.js @@ -0,0 +1,30 @@ +'use strict'; + +var proxyquire = require('proxyquire') + .noCallThru() + .noPreserveCache(); + +function mac() { + return { + digest: function () { + return 'good_byte_signature'; + } + }; +} + +var encoding = { + toHex: function () { + return '4545854d3b8704d4b21cf88bc8b5da5680c46b2ab9d45c8cffe6278d8a8b1860'; + } +}; + +function proxyModel() { + return proxyquire('../../../cartridges/int_alma/cartridge/scripts/helpers/almaSecurityHelper', { + 'dw/crypto/Mac': mac, + 'dw/crypto/Encoding': encoding + }); +} + +module.exports = { + almaSecurityHelper: proxyModel() +}; diff --git a/test/unit/int_alma/scripts/helpers/almaSecurityHelperTest.js b/test/unit/int_alma/scripts/helpers/almaSecurityHelperTest.js new file mode 100644 index 00000000..c40899ae --- /dev/null +++ b/test/unit/int_alma/scripts/helpers/almaSecurityHelperTest.js @@ -0,0 +1,30 @@ +'use strict'; + +// almaSecurityHelper.js unit tests + +var assert = require('chai').assert; +var almaSecurityHelper = require('../../../../mocks/helpers/almaSecurityHelper').almaSecurityHelper; +var PAYMENT_ID = 'payment_id_test'; +var API_KEY = 'api_key_test'; +var BAD_SIGNATURE = 'bad_signature'; +var GOOD_SIGNATURE = '4545854d3b8704d4b21cf88bc8b5da5680c46b2ab9d45c8cffe6278d8a8b1860'; + +describe('Alma security helper', function () { + it('checkIpnSignature throw error without signature in header', function () { + assert.throw(function () { + almaSecurityHelper.checkIpnSignature(null, PAYMENT_ID, API_KEY); + }, 'There is no signature in header'); + }); + + it('checkIpnSignature throw error with bad signature', function () { + assert.throw(function () { + almaSecurityHelper.checkIpnSignature(BAD_SIGNATURE, PAYMENT_ID, ''); + }, 'Signature is not valid'); + }); + + it('checkIpnSignature not throw error with good signature', function () { + assert.doesNotThrow(function () { + almaSecurityHelper.checkIpnSignature(GOOD_SIGNATURE, PAYMENT_ID, API_KEY); + }); + }); +});