Skip to content

Commit

Permalink
AdagioBidAdapter: external script compliance
Browse files Browse the repository at this point in the history
- lock version
- remove loading from localStorage (eval() fn)
  • Loading branch information
osazos committed Apr 17, 2024
1 parent 524617d commit 7efe65c
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 137 deletions.
84 changes: 33 additions & 51 deletions modules/adagioBidAdapter.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
import {config} from '../src/config.js';
import {registerBidder} from '../src/adapters/bidderFactory.js';
import {loadExternalScript} from '../src/adloader.js';
import {verify} from 'criteo-direct-rsa-validate/build/verify.js';
import {getStorageManager} from '../src/storageManager.js';
import {getRefererInfo, parseDomain} from '../src/refererDetection.js';
import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js';
Expand All @@ -33,14 +32,20 @@ import { getGlobal } from '../src/prebidGlobal.js';
import { convertOrtbRequestToProprietaryNative } from '../src/native.js';
import { userSync } from '../src/userSync.js';
import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js';
import {bidderSettings} from '../src/bidderSettings.js';

const BIDDER_CODE = 'adagio';
const LOG_PREFIX = 'Adagio:';
const FEATURES_VERSION = '1';
export const ENDPOINT = 'https://mp.4dex.io/prebid';
const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO];
const ADAGIO_TAG_URL = 'https://script.4dex.io/localstore.js';
const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript';

const ADAGIOJS_VERSION_LOCK = '2.0.1';
const ADAGIOJS_VERSION_LATEST = 'latest';
const ADAGIOJS_VERSION_NONE = 'none';
const ADAGIOJS_VERSION_PLACEHOLDER = '%ADAGIOJS_VERSION%';
const PUBLISHER_TAG_URL_TEMPLATE = 'https://script.4dex.io/a' + ADAGIOJS_VERSION_PLACEHOLDER + '/adagio.js';

const GVLID = 617;
export const storage = getStorageManager({bidderCode: BIDDER_CODE});

Expand All @@ -49,8 +54,6 @@ const BB_RENDERER_DEFAULT = 'renderer';
export const BB_RENDERER_URL = `https://${BB_PUBLICATION}.bbvms.com/r/$RENDERER.js`;

const MAX_SESS_DURATION = 30 * 60 * 1000;
const ADAGIO_PUBKEY = 'AL16XT44Sfp+8SHVF1UdC7hydPSMVLMhsYknKDdwqq+0ToDSJrP0+Qh0ki9JJI2uYm/6VEYo8TJED9WfMkiJ4vf02CW3RvSWwc35bif2SK1L8Nn/GfFYr/2/GG/Rm0vUsv+vBHky6nuuYls20Og0HDhMgaOlXoQ/cxMuiy5QSktp';
const ADAGIO_PUBKEY_E = 65537;
const CURRENCY = 'USD';

// This provide a whitelist and a basic validation of OpenRTB 2.5 options used by the Adagio SSP.
Expand Down Expand Up @@ -145,58 +148,38 @@ export const GlobalExchange = (function() {
};
})();

export function adagioScriptFromLocalStorageCb(ls) {
try {
if (!ls) {
logWarn(`${LOG_PREFIX} script not found.`);
return;
}

const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/;

if (!hashRgx.test(ls)) {
logWarn(`${LOG_PREFIX} no hash found.`);
storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY);
} else {
const r = ls.match(hashRgx);
const hash = r[2];
const content = r[3];
/**
* @param {string|number} adagiojsVersion The external adagio.js script version to use
* @returns {string} https://script.4dex.io/a[/VERSION]/adagio.js
*/
export function computeAdagioScriptUrl(adagiojsVersion) {
let versionDir = `/${ADAGIOJS_VERSION_LOCK}`;
const semverRgx = /^\d+\.\d+\.\d+$/;

if (verify(content, hash, ADAGIO_PUBKEY, ADAGIO_PUBKEY_E)) {
logInfo(`${LOG_PREFIX} start script.`);
Function(ls)(); // eslint-disable-line no-new-func
} else {
logWarn(`${LOG_PREFIX} invalid script found.`);
storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY);
}
}
} catch (err) {
logError(LOG_PREFIX, err);
if (adagiojsVersion === ADAGIOJS_VERSION_LATEST || semverRgx.test(adagiojsVersion)) {
versionDir = `/${adagiojsVersion}`;
} else {
logInfo(`${LOG_PREFIX} Invalid adagio.js version. Using the default version.`);
}

return PUBLISHER_TAG_URL_TEMPLATE.replace(ADAGIOJS_VERSION_PLACEHOLDER, versionDir);
}

export function getAdagioScript() {
storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (ls) => {
internal.adagioScriptFromLocalStorageCb(ls);
});

storage.localStorageIsEnabled(isValid => {
if (isValid) {
loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE);
} else {
// Try-catch to avoid error when 3rd party cookies is disabled (e.g. in privacy mode)
try {
// ensure adagio removing for next time.
// It's an antipattern regarding the TCF2 enforcement logic
// but it's the only way to respect the user choice update.
window.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY);
// Extra data from external script.
// This key is removed only if localStorage is not accessible.
window.localStorage.removeItem('adagio');
} catch (e) {
logInfo(`${LOG_PREFIX} unable to clear Adagio scripts from localstorage.`);
}
if (!isValid) {
return
}

const adagiojsVersion = bidderSettings.get('adagio', 'scriptVersion') || config.getConfig('adagio.scriptVersion');

// If the publisher explicitly set the version to 'none', we don't load anything.
if (adagiojsVersion === ADAGIOJS_VERSION_NONE) {
return;
}

const url = computeAdagioScriptUrl(adagiojsVersion);
loadExternalScript(url, BIDDER_CODE, undefined, undefined, { id: `adagiojs-${getUniqueIdentifierStr()}`, version: adagiojsVersion });
});
}

Expand Down Expand Up @@ -362,7 +345,6 @@ export const internal = {
getSite,
getElementFromTopWindow,
getRefererInfo,
adagioScriptFromLocalStorageCb,
getCurrentWindow,
canAccessTopWindow,
isRendererPreferredFromPublisher,
Expand Down
8 changes: 6 additions & 2 deletions modules/adagioBidAdapter.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ pbjs.setConfig({

### Bidder Settings

The Adagio bid adapter uses browser local storage. Since Prebid.js 7.x, the access to it must be explicitly set.
- `storageAllowed`: Adagio uses browser local storage, explicit access to it must be configured _(since Prebid.js 7.x)_
- `scriptVersion`: Adagio loads an additional script used for measurement. By default the adapter loads a specific version of this script, defined in the adapter itself. Two values can override this behavior:
- `latest`: allow to always get the more recent version of the script
- `none`: deactivates the feature, no script is loaded

```js
// https://docs.prebid.org/dev-docs/publisher-api-reference/bidderSettings.html
pbjs.bidderSettings = {
adagio: {
storageAllowed: true
storageAllowed: true,
scriptVersion: 'latest'
}
}
```
Expand Down
124 changes: 40 additions & 84 deletions test/spec/modules/adagioBidAdapter_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1760,29 +1760,8 @@ describe('Adagio bid adapter', () => {
});
});

describe('adagioScriptFromLocalStorageCb()', function() {
const VALID_HASH = 'Lddcw3AADdQDrPtbRJkKxvA+o1CtScGDIMNRpHB3NnlC/FYmy/9RKXelKrYj/sjuWusl5YcOpo+lbGSkk655i8EKuDiOvK6ae/imxSrmdziIp+S/TA6hTFJXcB8k1Q9OIp4CMCT52jjXgHwX6G0rp+uYoCR25B1jHaHnpH26A6I=';
const INVALID_HASH = 'invalid';
const VALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){};(_ADAGIO)();\n';
const INVALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){//corrupted};(_ADAGIO)();\n';
const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript';

beforeEach(function() {
localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY);
});

describe('getAdagioScript', function() {
it('should run storage.getDataFromLocalStorage callback and call adagioScriptFromLocalStorageCb() ', function() {
sandbox.spy(adagio, 'adagioScriptFromLocalStorageCb');
const getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage').callsArg(1);
localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT);

getAdagioScript();

sinon.assert.callCount(getDataFromLocalStorageStub, 1);
sinon.assert.callCount(adagio.adagioScriptFromLocalStorageCb, 1);
});

describe('adagio external script loading', function() {
describe('external script loading regarding the user consent storageAllowed flag', function() {
it('should load external script if the user consent', function() {
sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true);
getAdagioScript();
Expand All @@ -1796,75 +1775,52 @@ describe('Adagio bid adapter', () => {

expect(loadExternalScript.called).to.be.false;
});
})

it('should remove the localStorage key if exists and the user does not consent', function() {
sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false);
localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, 'the script');

getAdagioScript();

expect(loadExternalScript.called).to.be.false;
expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null;
describe('getAdagioScript with expected version accordingly to bidderSettings config', function() {
beforeEach(function() {
sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true);
});
});

it('should verify valid hash with valid script', function () {
localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT);

utilsMock.expects('logInfo').withExactArgs('Adagio: start script.').once();
utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never();
utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never();

adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY));

expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.equals('// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT);
utilsMock.verify();
});

it('should verify valid hash with invalid script', function () {
localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + INVALID_SCRIPT_CONTENT);

utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never();
utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never();
utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once();

adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY));

expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null;
utilsMock.verify();
});
const lockedVersion = '2.0.1';

it('should verify invalid hash with valid script', function () {
localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + INVALID_HASH + '\n' + VALID_SCRIPT_CONTENT);

utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never();
utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').never();
utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').once();

adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY));

expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null;
utilsMock.verify();
});

it('should verify missing hash', function () {
localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, VALID_SCRIPT_CONTENT);
it('loads a determined version', function() {
const version = '2.0.5';
$$PREBID_GLOBAL$$.bidderSettings.adagio.scriptVersion = version;
getAdagioScript();
expect(loadExternalScript.called).to.be.true;
expect(loadExternalScript.args[0][0]).to.deep.equal(`https://script.4dex.io/a/${version}/adagio.js`);
});

utilsMock.expects('logInfo').withExactArgs('Adagio: start script').never();
utilsMock.expects('logWarn').withExactArgs('Adagio: no hash found.').once();
utilsMock.expects('logWarn').withExactArgs('Adagio: invalid script found.').never();
it('loads the latest external script, bypass the lock version', function() {
const version = 'latest';
$$PREBID_GLOBAL$$.bidderSettings.adagio.scriptVersion = version;
getAdagioScript();
expect(loadExternalScript.called).to.be.true;
expect(loadExternalScript.args[0][0]).to.deep.equal(`https://script.4dex.io/a/${version}/adagio.js`);
});

adagioScriptFromLocalStorageCb(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY));
it('loads the locked version, bad version provided', function() {
const version = 'false';
$$PREBID_GLOBAL$$.bidderSettings.adagio.scriptVersion = version;
getAdagioScript();
expect(loadExternalScript.called).to.be.true;
expect(loadExternalScript.args[0][0]).to.deep.equal(`https://script.4dex.io/a/${lockedVersion}/adagio.js`);
});

expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null;
utilsMock.verify();
});
it('loads nothing', function() {
const version = 'none';
$$PREBID_GLOBAL$$.bidderSettings.adagio.scriptVersion = version;
getAdagioScript();
expect(loadExternalScript.called).to.be.false;
});

it('should return false if content script does not exist in localStorage', function() {
sandbox.spy(utils, 'logWarn');
expect(adagioScriptFromLocalStorageCb(null)).to.be.undefined;
sinon.assert.callCount(utils.logWarn, 1);
sinon.assert.calledWith(utils.logWarn, 'Adagio: script not found.');
it('loads the locked version - default behavior', function() {
delete $$PREBID_GLOBAL$$.bidderSettings.adagio.scriptVersion;
getAdagioScript();
expect(loadExternalScript.called).to.be.true;
expect(loadExternalScript.args[0][0]).to.deep.equal(`https://script.4dex.io/a/${lockedVersion}/adagio.js`);
});
});
});
});

0 comments on commit 7efe65c

Please sign in to comment.