From 3685bd978e7c91b754c8f19486f1132b0e8796a3 Mon Sep 17 00:00:00 2001 From: dpapworth-qc Date: Fri, 27 Mar 2020 15:40:41 +0000 Subject: [PATCH 1/8] Add TCF version to bid request. --- modules/quantcastBidAdapter.js | 1 + test/spec/modules/quantcastBidAdapter_spec.js | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 4ffb84b8934..a990e0b6361 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -139,6 +139,7 @@ export const spec = { bidId: bid.bidId, gdprSignal: gdprConsent.gdprApplies ? 1 : 0, gdprConsent: gdprConsent.consentString, + gdprVersion: gdprConsent.apiVersion, uspSignal: uspConsent ? 1 : 0, uspConsent, coppa: config.getConfig('coppa') === true ? 1 : 0, diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 351cea0b086..334ec784aeb 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -347,12 +347,21 @@ describe('Quantcast adapter', function () { }); }); - it('propagates GDPR consent string and signal', function () { - const bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentString' } } + it('propagates GDPR consent string, signal and version', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + apiVersion: 1 + } + }; + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); const parsed = JSON.parse(requests[0].data); + expect(parsed.gdprSignal).to.equal(1); expect(parsed.gdprConsent).to.equal('consentString'); + expect(parsed.gdprVersion).to.equal(1); }); it('propagates US Privacy/CCPA consent information', function () { From 9108384effa1541d11212e0306d0867020c26cf1 Mon Sep 17 00:00:00 2001 From: dpapworth-qc Date: Fri, 27 Mar 2020 16:34:38 +0000 Subject: [PATCH 2/8] Block requests where GDPR consent has not been given. --- modules/quantcastBidAdapter.js | 11 +++++ test/spec/modules/quantcastBidAdapter_spec.js | 41 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index a990e0b6361..f3cf804236d 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -4,6 +4,7 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'quantcast'; +const QUANTCAST_VENDOR_ID = 11; const DEFAULT_BID_FLOOR = 0.0000000001; export const QUANTCAST_DOMAIN = 'qcx.quantserve.com'; @@ -107,6 +108,16 @@ export const spec = { const page = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); const domain = getDomain(page); + // Check for GDPR consent, and drop request if consent has not been given + if (gdprConsent.gdprApplies) { + if (gdprConsent.vendorData && gdprConsent.vendorData.vendorConsents && + typeof gdprConsent.vendorData.vendorConsents[QUANTCAST_VENDOR_ID.toString(10)] !== 'undefined') { + if (!(bidderRequest.gdprConsent.vendorData.vendorConsents[QUANTCAST_VENDOR_ID.toString(10)])) { + return; + } + } + } + let bidRequestsList = []; bids.forEach(bid => { diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 334ec784aeb..7631ecc90af 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -352,6 +352,47 @@ describe('Quantcast adapter', function () { gdprConsent: { gdprApplies: true, consentString: 'consentString', + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + + expect(parsed.gdprSignal).to.equal(1); + expect(parsed.gdprConsent).to.equal('consentString'); + expect(parsed.gdprVersion).to.equal(2); + }); + + it('blocks request without GDPR consent', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendorConsents: { + '11': 0 + } + }, + apiVersion: 1 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('allows request with GDPR consent', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendorConsents: { + '11': 1 + } + }, apiVersion: 1 } }; From 1ce5fd640ddbf05ceee7ddfb990aa3f7ff18f683 Mon Sep 17 00:00:00 2001 From: dpapworth-qc Date: Fri, 27 Mar 2020 17:02:35 +0000 Subject: [PATCH 3/8] Removed GDPR apiVersion from bid request. --- modules/quantcastBidAdapter.js | 1 - test/spec/modules/quantcastBidAdapter_spec.js | 13 ++++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index f3cf804236d..04b59bf854b 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -150,7 +150,6 @@ export const spec = { bidId: bid.bidId, gdprSignal: gdprConsent.gdprApplies ? 1 : 0, gdprConsent: gdprConsent.consentString, - gdprVersion: gdprConsent.apiVersion, uspSignal: uspConsent ? 1 : 0, uspConsent, coppa: config.getConfig('coppa') === true ? 1 : 0, diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 7631ecc90af..caf0259f446 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -347,12 +347,11 @@ describe('Quantcast adapter', function () { }); }); - it('propagates GDPR consent string, signal and version', function () { + it('propagates GDPR consent string and signal', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, - consentString: 'consentString', - apiVersion: 2 + consentString: 'consentString' } }; @@ -361,7 +360,6 @@ describe('Quantcast adapter', function () { expect(parsed.gdprSignal).to.equal(1); expect(parsed.gdprConsent).to.equal('consentString'); - expect(parsed.gdprVersion).to.equal(2); }); it('blocks request without GDPR consent', function () { @@ -373,8 +371,7 @@ describe('Quantcast adapter', function () { vendorConsents: { '11': 0 } - }, - apiVersion: 1 + } } }; @@ -392,8 +389,7 @@ describe('Quantcast adapter', function () { vendorConsents: { '11': 1 } - }, - apiVersion: 1 + } } }; @@ -402,7 +398,6 @@ describe('Quantcast adapter', function () { expect(parsed.gdprSignal).to.equal(1); expect(parsed.gdprConsent).to.equal('consentString'); - expect(parsed.gdprVersion).to.equal(1); }); it('propagates US Privacy/CCPA consent information', function () { From fb5f49c73c4f5cdbb104937fcede7c392f7062ba Mon Sep 17 00:00:00 2001 From: dpapworth-qc Date: Mon, 30 Mar 2020 11:27:57 +0100 Subject: [PATCH 4/8] Added purpose consent check. --- modules/quantcastBidAdapter.js | 31 +++++++++++-- test/spec/modules/quantcastBidAdapter_spec.js | 46 +++++++++++++++++-- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 04b59bf854b..4ebca11323f 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -4,9 +4,12 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; const BIDDER_CODE = 'quantcast'; -const QUANTCAST_VENDOR_ID = 11; const DEFAULT_BID_FLOOR = 0.0000000001; +const QUANTCAST_VENDOR_ID = '11'; +// Check other required purposes on server +const PURPOSE_DATA_COLLECT = '1'; + export const QUANTCAST_DOMAIN = 'qcx.quantserve.com'; export const QUANTCAST_TEST_DOMAIN = 's2s-canary.quantserve.com'; export const QUANTCAST_NET_REVENUE = true; @@ -73,6 +76,24 @@ function getDomain(url) { return url.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#]/)[0]; } +function checkPurposeConsent(gdprConsent) { + if (gdprConsent.vendorData && typeof gdprConsent.vendorData.purposeConsents !== 'undefined' && + typeof gdprConsent.vendorData.purposeConsents[PURPOSE_DATA_COLLECT] !== 'undefined') { + return !!(gdprConsent.vendorData.purposeConsents[PURPOSE_DATA_COLLECT]); + } + // Defer purpose consent check to server + return true; +} + +function checkVendorConsent(gdprConsent) { + if (gdprConsent.vendorData && typeof gdprConsent.vendorData.vendorConsents !== 'undefined' && + typeof gdprConsent.vendorData.vendorConsents[QUANTCAST_VENDOR_ID] !== 'undefined') { + return !!(gdprConsent.vendorData.vendorConsents[QUANTCAST_VENDOR_ID]) + } + // Defer vendor consent check to server + return true; +} + /** * The documentation for Prebid.js Adapter 1.0 can be found at link below, * http://prebid.org/dev-docs/bidder-adapter-1.html @@ -110,9 +131,11 @@ export const spec = { // Check for GDPR consent, and drop request if consent has not been given if (gdprConsent.gdprApplies) { - if (gdprConsent.vendorData && gdprConsent.vendorData.vendorConsents && - typeof gdprConsent.vendorData.vendorConsents[QUANTCAST_VENDOR_ID.toString(10)] !== 'undefined') { - if (!(bidderRequest.gdprConsent.vendorData.vendorConsents[QUANTCAST_VENDOR_ID.toString(10)])) { + if (gdprConsent.vendorData) { + if (!checkVendorConsent(gdprConsent)) { + return; + } + if (!checkPurposeConsent(gdprConsent)) { return; } } diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index caf0259f446..a7bb48bb9cf 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -362,14 +362,14 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); - it('blocks request without GDPR consent', function () { + it('blocks request without GDPR vendor consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentString', vendorData: { vendorConsents: { - '11': 0 + '11': false } } } @@ -380,14 +380,52 @@ describe('Quantcast adapter', function () { expect(requests).to.equal(undefined); }); - it('allows request with GDPR consent', function () { + it('allows request with GDPR vendor consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentString', vendorData: { vendorConsents: { - '11': 1 + '11': true + } + } + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + + expect(parsed.gdprSignal).to.equal(1); + expect(parsed.gdprConsent).to.equal('consentString'); + }); + + it('blocks request without GDPR purpose consent', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + purposeConsents: { + '1': false + } + } + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('allows request with GDPR purpose consent', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + purposeConsents: { + '1': true } } } From 07b843b068cf275259a1fdfaad2fc3089886972d Mon Sep 17 00:00:00 2001 From: dpapworth-qc Date: Mon, 30 Mar 2020 12:39:29 +0100 Subject: [PATCH 5/8] Added separate functions for TCF v1 and TCF v2 consent checks. --- modules/quantcastBidAdapter.js | 34 +++++++++++-------- test/spec/modules/quantcastBidAdapter_spec.js | 20 ++++++----- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 4ebca11323f..6f1f7171253 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -76,21 +76,25 @@ function getDomain(url) { return url.replace('http://', '').replace('https://', '').replace('www.', '').split(/[/?#]/)[0]; } -function checkPurposeConsent(gdprConsent) { - if (gdprConsent.vendorData && typeof gdprConsent.vendorData.purposeConsents !== 'undefined' && - typeof gdprConsent.vendorData.purposeConsents[PURPOSE_DATA_COLLECT] !== 'undefined') { - return !!(gdprConsent.vendorData.purposeConsents[PURPOSE_DATA_COLLECT]); +function checkTCFv1(vendorData) { + // Defer consent check to server if no consent fields in vendor data + let purposeConsent = true; + let vendorConsent = true; + + if (typeof vendorData.purposeConsents !== 'undefined' && + typeof vendorData.purposeConsents[PURPOSE_DATA_COLLECT] !== 'undefined') { + vendorConsent = !!(vendorData.purposeConsents[PURPOSE_DATA_COLLECT]); } - // Defer purpose consent check to server - return true; -} -function checkVendorConsent(gdprConsent) { - if (gdprConsent.vendorData && typeof gdprConsent.vendorData.vendorConsents !== 'undefined' && - typeof gdprConsent.vendorData.vendorConsents[QUANTCAST_VENDOR_ID] !== 'undefined') { - return !!(gdprConsent.vendorData.vendorConsents[QUANTCAST_VENDOR_ID]) + if (typeof vendorData.vendorConsents !== 'undefined' && + typeof vendorData.vendorConsents[QUANTCAST_VENDOR_ID] !== 'undefined') { + purposeConsent = !!(vendorData.vendorConsents[QUANTCAST_VENDOR_ID]) } - // Defer vendor consent check to server + + return vendorConsent && purposeConsent; +} + +function checkTCFv2(vendorData) { return true; } @@ -132,10 +136,12 @@ export const spec = { // Check for GDPR consent, and drop request if consent has not been given if (gdprConsent.gdprApplies) { if (gdprConsent.vendorData) { - if (!checkVendorConsent(gdprConsent)) { + if (gdprConsent.apiVersion === 1 && !checkTCFv1(gdprConsent.vendorData)) { + utils.logInfo(`${BIDDER_CODE}: No consent for TCF v1`); return; } - if (!checkPurposeConsent(gdprConsent)) { + if (gdprConsent.apiVersion === 2 && !checkTCFv2(gdprConsent.vendorData)) { + utils.logInfo(`${BIDDER_CODE}: No consent for TCF v2`); return; } } diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index a7bb48bb9cf..76e002e53bd 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -362,7 +362,7 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); - it('blocks request without GDPR vendor consent', function () { + it('blocks request without TCF v1 vendor consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -371,7 +371,8 @@ describe('Quantcast adapter', function () { vendorConsents: { '11': false } - } + }, + apiVersion: 1 } }; @@ -380,7 +381,7 @@ describe('Quantcast adapter', function () { expect(requests).to.equal(undefined); }); - it('allows request with GDPR vendor consent', function () { + it('allows request with TCF v1 vendor consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -389,7 +390,8 @@ describe('Quantcast adapter', function () { vendorConsents: { '11': true } - } + }, + apiVersion: 1 } }; @@ -400,7 +402,7 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); - it('blocks request without GDPR purpose consent', function () { + it('blocks request without TCF v1 purpose consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -409,7 +411,8 @@ describe('Quantcast adapter', function () { purposeConsents: { '1': false } - } + }, + apiVersion: 1 } }; @@ -418,7 +421,7 @@ describe('Quantcast adapter', function () { expect(requests).to.equal(undefined); }); - it('allows request with GDPR purpose consent', function () { + it('allows request with TCF v1 purpose consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -427,7 +430,8 @@ describe('Quantcast adapter', function () { purposeConsents: { '1': true } - } + }, + apiVersion: 1 } }; From d612bcd0fffd961dfc804bc5ff30a2275d208983 Mon Sep 17 00:00:00 2001 From: dpapworth-qc Date: Mon, 30 Mar 2020 15:37:34 +0100 Subject: [PATCH 6/8] Check TCF v2 consent before sending bid. --- modules/quantcastBidAdapter.js | 22 +++- test/spec/modules/quantcastBidAdapter_spec.js | 104 ++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 6f1f7171253..4b6e1c5f66a 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -94,8 +94,26 @@ function checkTCFv1(vendorData) { return vendorConsent && purposeConsent; } -function checkTCFv2(vendorData) { - return true; +function checkTCFv2(tcData) { + if (tcData.purposeOneTreatment && tcData.publisherCC === 'DE') { + // special purpose 1 treatment for Germany + return true; + } + + let restrictions = tcData.publisher ? tcData.publisher.restrictions : {}; + let qcRestriction = restrictions && restrictions[PURPOSE_DATA_COLLECT] + ? restrictions[PURPOSE_DATA_COLLECT][QUANTCAST_VENDOR_ID] + : null; + + let vendorConsent = tcData.vendor && tcData.vendor.consents && tcData.vendor.consents[QUANTCAST_VENDOR_ID]; + let purposeConsent = tcData.purpose && tcData.purpose.consents && tcData.purpose.consents[PURPOSE_DATA_COLLECT]; + if (vendorConsent && purposeConsent && qcRestriction !== 2) { + // Consent established + return true; + } + + // No consent established + return false; } /** diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 76e002e53bd..768badfa920 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -442,6 +442,110 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); + it('allows TCF v2 request from Germany', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + publisherCC: 'DE', + purposeOneTreatment: true + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + + expect(parsed.gdprSignal).to.equal(1); + expect(parsed.gdprConsent).to.equal('consentString'); + }); + + it('allows TCF v2 request when Quantcast has consent', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': true + } + }, + purpose: { + consents: { + '1': true + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + + expect(parsed.gdprSignal).to.equal(1); + expect(parsed.gdprConsent).to.equal('consentString'); + }); + + it('blocks TCF v2 request when Quantcast not allowed by publisher', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + publisher: { + restrictions: { + '1': { + '11': 0 + } + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('blocks TCF v2 request when legitimate interest required', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': true + } + }, + purpose: { + consents: { + '1': true + } + }, + publisher: { + restrictions: { + '1': { + '11': 2 + } + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + it('propagates US Privacy/CCPA consent information', function () { const bidderRequest = { uspConsent: 'consentString' } const requests = qcSpec.buildRequests([bidRequest], bidderRequest); From 002b54f63e62aebbdf687551ebbdf44f88cc052b Mon Sep 17 00:00:00 2001 From: dpapworth-qc Date: Mon, 30 Mar 2020 16:01:12 +0100 Subject: [PATCH 7/8] Make TCF v1 consent check strict. --- modules/quantcastBidAdapter.js | 22 +++----- test/spec/modules/quantcastBidAdapter_spec.js | 52 +++++++++---------- 2 files changed, 33 insertions(+), 41 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 4b6e1c5f66a..146a4a9881c 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -77,21 +77,10 @@ function getDomain(url) { } function checkTCFv1(vendorData) { - // Defer consent check to server if no consent fields in vendor data - let purposeConsent = true; - let vendorConsent = true; + let vendorConsent = vendorData.vendorConsents && vendorData.vendorConsents[QUANTCAST_VENDOR_ID]; + let purposeConsent = vendorData.purposeConsents && vendorData.purposeConsents[PURPOSE_DATA_COLLECT]; - if (typeof vendorData.purposeConsents !== 'undefined' && - typeof vendorData.purposeConsents[PURPOSE_DATA_COLLECT] !== 'undefined') { - vendorConsent = !!(vendorData.purposeConsents[PURPOSE_DATA_COLLECT]); - } - - if (typeof vendorData.vendorConsents !== 'undefined' && - typeof vendorData.vendorConsents[QUANTCAST_VENDOR_ID] !== 'undefined') { - purposeConsent = !!(vendorData.vendorConsents[QUANTCAST_VENDOR_ID]) - } - - return vendorConsent && purposeConsent; + return !!(vendorConsent && purposeConsent); } function checkTCFv2(tcData) { @@ -105,6 +94,11 @@ function checkTCFv2(tcData) { ? restrictions[PURPOSE_DATA_COLLECT][QUANTCAST_VENDOR_ID] : null; + if (qcRestriction === 0) { + // Not allowed by publisher + return false; + } + let vendorConsent = tcData.vendor && tcData.vendor.consents && tcData.vendor.consents[QUANTCAST_VENDOR_ID]; let purposeConsent = tcData.purpose && tcData.purpose.consents && tcData.purpose.consents[PURPOSE_DATA_COLLECT]; if (vendorConsent && purposeConsent && qcRestriction !== 2) { diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 768badfa920..2fa8e383e89 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -362,26 +362,7 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); - it('blocks request without TCF v1 vendor consent', function () { - const bidderRequest = { - gdprConsent: { - gdprApplies: true, - consentString: 'consentString', - vendorData: { - vendorConsents: { - '11': false - } - }, - apiVersion: 1 - } - }; - - const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - - expect(requests).to.equal(undefined); - }); - - it('allows request with TCF v1 vendor consent', function () { + it('allows request with TCF v1 consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -389,6 +370,9 @@ describe('Quantcast adapter', function () { vendorData: { vendorConsents: { '11': true + }, + purposeConsents: { + '1': true } }, apiVersion: 1 @@ -402,14 +386,17 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); - it('blocks request without TCF v1 purpose consent', function () { + it('blocks request without TCF v1 vendor consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentString', vendorData: { + vendorConsents: { + '11': false + }, purposeConsents: { - '1': false + '1': true } }, apiVersion: 1 @@ -421,14 +408,17 @@ describe('Quantcast adapter', function () { expect(requests).to.equal(undefined); }); - it('allows request with TCF v1 purpose consent', function () { + it('blocks request without TCF v1 purpose consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentString', vendorData: { + vendorConsents: { + '11': true + }, purposeConsents: { - '1': true + '1': false } }, apiVersion: 1 @@ -436,10 +426,8 @@ describe('Quantcast adapter', function () { }; const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const parsed = JSON.parse(requests[0].data); - expect(parsed.gdprSignal).to.equal(1); - expect(parsed.gdprConsent).to.equal('consentString'); + expect(requests).to.equal(undefined); }); it('allows TCF v2 request from Germany', function () { @@ -496,6 +484,16 @@ describe('Quantcast adapter', function () { gdprApplies: true, consentString: 'consentString', vendorData: { + vendor: { + consents: { + '11': true + } + }, + purpose: { + consents: { + '1': true + } + }, publisher: { restrictions: { '1': { From 73fc58b44e6e4dae16ea961c08f99424b11c22ff Mon Sep 17 00:00:00 2001 From: dpapworth-qc Date: Mon, 30 Mar 2020 16:29:27 +0100 Subject: [PATCH 8/8] Improved comments and tests. --- modules/quantcastBidAdapter.js | 18 +++--- test/spec/modules/quantcastBidAdapter_spec.js | 62 +++++++++++++++++-- 2 files changed, 64 insertions(+), 16 deletions(-) diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 146a4a9881c..fbf3396a40b 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -94,20 +94,15 @@ function checkTCFv2(tcData) { ? restrictions[PURPOSE_DATA_COLLECT][QUANTCAST_VENDOR_ID] : null; - if (qcRestriction === 0) { - // Not allowed by publisher + if (qcRestriction === 0 || qcRestriction === 2) { + // Not allowed by publisher, or requires legitimate interest return false; } let vendorConsent = tcData.vendor && tcData.vendor.consents && tcData.vendor.consents[QUANTCAST_VENDOR_ID]; let purposeConsent = tcData.purpose && tcData.purpose.consents && tcData.purpose.consents[PURPOSE_DATA_COLLECT]; - if (vendorConsent && purposeConsent && qcRestriction !== 2) { - // Consent established - return true; - } - // No consent established - return false; + return !!(vendorConsent && purposeConsent); } /** @@ -145,15 +140,16 @@ export const spec = { const page = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); const domain = getDomain(page); - // Check for GDPR consent, and drop request if consent has not been given + // Check for GDPR consent for purpose 1, and drop request if consent has not been given + // Remaining consent checks are performed server-side. if (gdprConsent.gdprApplies) { if (gdprConsent.vendorData) { if (gdprConsent.apiVersion === 1 && !checkTCFv1(gdprConsent.vendorData)) { - utils.logInfo(`${BIDDER_CODE}: No consent for TCF v1`); + utils.logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v1`); return; } if (gdprConsent.apiVersion === 2 && !checkTCFv2(gdprConsent.vendorData)) { - utils.logInfo(`${BIDDER_CODE}: No consent for TCF v2`); + utils.logInfo(`${BIDDER_CODE}: No purpose 1 consent for TCF v2`); return; } } diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index 2fa8e383e89..96e69664859 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -362,7 +362,7 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); - it('allows request with TCF v1 consent', function () { + it('allows TCF v1 request with consent for purpose 1', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -386,7 +386,7 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); - it('blocks request without TCF v1 vendor consent', function () { + it('blocks TCF v1 request without vendor consent', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -408,7 +408,7 @@ describe('Quantcast adapter', function () { expect(requests).to.equal(undefined); }); - it('blocks request without TCF v1 purpose consent', function () { + it('blocks TCF v1 request without consent for purpose 1', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -430,7 +430,7 @@ describe('Quantcast adapter', function () { expect(requests).to.equal(undefined); }); - it('allows TCF v2 request from Germany', function () { + it('allows TCF v2 request from Germany for purpose 1', function () { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -450,7 +450,7 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); - it('allows TCF v2 request when Quantcast has consent', function() { + it('allows TCF v2 request when Quantcast has consent for purpose 1', function() { const bidderRequest = { gdprConsent: { gdprApplies: true, @@ -478,6 +478,58 @@ describe('Quantcast adapter', function () { expect(parsed.gdprConsent).to.equal('consentString'); }); + it('blocks TCF v2 request when no consent for Quantcast', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': false + } + }, + purpose: { + consents: { + '1': true + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + + it('blocks TCF v2 request when no consent for purpose 1', function() { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'consentString', + vendorData: { + vendor: { + consents: { + '11': true + } + }, + purpose: { + consents: { + '1': false + } + } + }, + apiVersion: 2 + } + }; + + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + + expect(requests).to.equal(undefined); + }); + it('blocks TCF v2 request when Quantcast not allowed by publisher', function () { const bidderRequest = { gdprConsent: {