Skip to content

Commit 1ef2d8b

Browse files
dgirardiSantiago Carabone
authored and
Santiago Carabone
committed
gppControl_usnat: update default rules for usnat consent interpretation (prebid#10241)
1 parent 921acc1 commit 1ef2d8b

File tree

2 files changed

+196
-244
lines changed

2 files changed

+196
-244
lines changed

libraries/mspa/activityControls.js

Lines changed: 51 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,13 @@ import {
88
import {gppDataHandler} from '../../src/adapterManager.js';
99

1010
// default interpretation for MSPA consent(s):
11-
// https://docs.google.com/document/d/1yPEVx3bBdSkIw-QNkQR5qi1ztmn9zoXk-LaGQB6iw7Q
11+
// https://docs.prebid.org/features/mspa-usnat.html
12+
13+
const SENSITIVE_DATA_GEO = 7;
14+
15+
function isApplicable(val) {
16+
return val != null && val !== 0
17+
}
1218

1319
export function isBasicConsentDenied(cd) {
1420
// service provider mode is always consent denied
@@ -18,47 +24,60 @@ export function isBasicConsentDenied(cd) {
1824
// minors 13+ who have not given consent
1925
cd.KnownChildSensitiveDataConsents[0] === 1 ||
2026
// minors under 13 cannot consent
21-
cd.KnownChildSensitiveDataConsents[1] !== 0 ||
22-
// sensitive category consent impossible without notice
23-
(cd.SensitiveDataProcessing.some(element => element === 2) && (cd.SensitiveDataLimitUseNotice !== 1 || cd.SensitiveDataProcessingOptOutNotice !== 1));
27+
isApplicable(cd.KnownChildSensitiveDataConsents[1]);
2428
}
2529

26-
export function isSensitiveNoticeMissing(cd) {
27-
return ['SensitiveDataProcessingOptOutNotice', 'SensitiveDataLimitUseNotice'].some(prop => cd[prop] === 2)
30+
export function sensitiveNoticeIs(cd, value) {
31+
return ['SensitiveDataProcessingOptOutNotice', 'SensitiveDataLimitUseNotice'].some(prop => cd[prop] === value)
2832
}
2933

3034
export function isConsentDenied(cd) {
3135
return isBasicConsentDenied(cd) ||
32-
// user opts out
33-
(['SaleOptOut', 'SharingOptOut', 'TargetedAdvertisingOptOut'].some(prop => cd[prop] === 1)) ||
34-
// notice not given
35-
(['SaleOptOutNotice', 'SharingNotice', 'SharingOptOutNotice', 'TargetedAdvertisingOptOutNotice'].some(prop => cd[prop] === 2)) ||
36-
// sale opted in but notice does not apply
37-
((cd.SaleOptOut === 2 && cd.SaleOptOutNotice === 0)) ||
38-
// targeted opted in but notice does not apply
39-
((cd.TargetedAdvertisingOptOut === 2 && cd.TargetedAdvertisingOptOutNotice === 0)) ||
40-
// sharing opted in but notices do not apply
41-
((cd.SharingOptOut === 2 && (cd.SharingNotice === 0 || cd.SharingOptOutNotice === 0)));
36+
['Sale', 'Sharing', 'TargetedAdvertising'].some(scope => {
37+
const oo = cd[`${scope}OptOut`];
38+
const notice = cd[`${scope}OptOutNotice`];
39+
// user opted out
40+
return oo === 1 ||
41+
// opt-out notice was not given
42+
notice === 2 ||
43+
// do not trust CMP if it signals opt-in but no opt-out notice was given
44+
(oo === 2 && notice === 0);
45+
}) ||
46+
// no sharing notice was given ...
47+
cd.SharingNotice === 2 ||
48+
// ... or the CMP says it was not applicable, while also claiming it got consent
49+
(cd.SharingOptOut === 2 && cd.SharingNotice === 0);
4250
}
4351

44-
export function isTransmitUfpdConsentDenied(cd) {
45-
// SensitiveDataProcessing[1-5,11]=1 OR SensitiveDataProcessing[6,7,9,10,12]<>0 OR
46-
const mustBeZero = [6, 7, 9, 10, 12];
47-
const mustBeZeroSubtractedVector = mustBeZero.map((number) => number - 1);
48-
const SensitiveDataProcessingMustBeZero = cd.SensitiveDataProcessing.filter(index => mustBeZeroSubtractedVector.includes(index));
49-
const cannotBeOne = [1, 2, 3, 4, 5, 11];
50-
const cannotBeOneSubtractedVector = cannotBeOne.map((number) => number - 1);
51-
const SensitiveDataProcessingCannotBeOne = cd.SensitiveDataProcessing.filter(index => cannotBeOneSubtractedVector.includes(index));
52-
return isConsentDenied(cd) ||
53-
isSensitiveNoticeMissing(cd) ||
54-
SensitiveDataProcessingCannotBeOne.some(val => val === 1) ||
55-
SensitiveDataProcessingMustBeZero.some(val => val !== 0);
56-
}
52+
export const isTransmitUfpdConsentDenied = (() => {
53+
// deny anything that smells like: genetic, biometric, state/national ID, financial, union membership,
54+
// or personal communication data
55+
const cannotBeInScope = [6, 7, 9, 10, 12].map(el => --el);
56+
// require consent for everything else (except geo, which is treated separately)
57+
const allExceptGeo = Array.from(Array(12).keys()).filter((el) => el !== SENSITIVE_DATA_GEO)
58+
const mustHaveConsent = allExceptGeo.filter(el => !cannotBeInScope.includes(el));
59+
60+
return function (cd) {
61+
return isConsentDenied(cd) ||
62+
// no notice about sensitive data was given
63+
sensitiveNoticeIs(cd, 2) ||
64+
// extra-sensitive data is applicable
65+
cannotBeInScope.some(i => isApplicable(cd.SensitiveDataProcessing[i])) ||
66+
// user opted out for not-as-sensitive data
67+
mustHaveConsent.some(i => cd.SensitiveDataProcessing[i] === 1) ||
68+
// CMP says it has consent, but did not give notice about it
69+
(sensitiveNoticeIs(cd, 0) && allExceptGeo.some(i => cd.SensitiveDataProcessing[i] === 2))
70+
}
71+
})();
5772

5873
export function isTransmitGeoConsentDenied(cd) {
59-
return isBasicConsentDenied(cd) ||
60-
isSensitiveNoticeMissing(cd) ||
61-
cd.SensitiveDataProcessing[7] === 1
74+
const geoConsent = cd.SensitiveDataProcessing[SENSITIVE_DATA_GEO];
75+
return geoConsent === 1 ||
76+
isBasicConsentDenied(cd) ||
77+
// no sensitive data notice was given
78+
sensitiveNoticeIs(cd, 2) ||
79+
// do not trust CMP if it says it has consent for geo but didn't show a sensitive data notice
80+
(sensitiveNoticeIs(cd, 0) && geoConsent === 2)
6281
}
6382

6483
const CONSENT_RULES = {

0 commit comments

Comments
 (0)