-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FPD Enrichment: support for Cookie Deprecation Label #10683
Conversation
@patmmccann @dgirardi, from #10516, it sounded like we wanted this change in enrichment.js. Let me know what you guys think. |
src/fpd/enrichment.js
Outdated
@@ -78,6 +95,34 @@ function removeUndef(obj) { | |||
return getDefinedParams(obj, Object.keys(obj)) | |||
} | |||
|
|||
export async function tryToGetCdepLabel(cb = getCookieDeprecationLabel) { | |||
let cdep; | |||
const consentData = gdprDataHandler.getConsentData(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should do a check on the accessDevice
activity instead (somewhat like this).
Normally that would be enough - the TCF logic (which is just one of the ways storage is gated) is attached to accessDevice
in its own module.
However for this case we decided that the TCF logic should be changed to check for publisher consent (publisher.consents[1]
in the parsed TCF data), apparently that's what we should have been doing from the beginning, but we need to wait for 9 to avoid breaking people. Paging also @patmmccann since I can't find a summary of what was decided, I think we may also want to add transmitUfpd
(purpose 4) checks.
For this PR that means:
- the TCF logic linked above should be updated to look for
publisher.*
. I would put in a special case for a new component name, e.g. if in here you checkisActivityAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(MODULE_TYPE_PREBID, 'cdep'))
, then here you can have a special case for whenparams[ACTIVITY_PARAM_COMPONENT_NAME] === 'cdep'
. That would then get conflated into the existing special case forMODULE_TYPE_PREBID
in prebid 9. - If we need purpose 4 checks it should be enough to add
device.ext.cdep
to this list
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update:
- The new TCF logic should (in version 9) replace this so that if gvlid is
VENDORLESS_GVLID
, instead of looking atvendorData.purpose
and switching off the check onvendorData.vendor
, we checkvendorData.publisher
instead. So my suggestion is to introduce a second, temporary gvlid placeholder (maybeFIRST_PARTY_GVLID
); use it to implement the newpublisher
logic, and update this to return it only for the cookie deprecation label for now - then in 9 we can get rid of the existingVENDORLESS_GVLID
logic. - We do want
transmitUfpd
/ purpose 4 checks, sodevice.ext.cdep
should be added to the ORTB fields that are redacted out when that is denied
Thanks @dgirardi, will look into the feedback items you mentioned above today 👍 |
Hey @dgirardi, could you take another look? Left a comment below about one question I had about the last commit i made. Also, I can address tests once the general flow of logic looks good for the rest of this PR. |
modules/gdprEnforcement.js
Outdated
if (shouldEnforce(consentData, purposeNo, modName)) { | ||
const gvlid = getGvlid(params[ACTIVITY_PARAM_COMPONENT_TYPE], modName, gvlidFallback(params)); | ||
let allow = !!checkConsent(consentData, modName, gvlid); | ||
let allow = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dgirardi had one question about the logic here. know we want to go with publisher.consent in this scenario.. but was wondering if i put the FIRST_PARTY_GVLID ternary in the right place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it should be inside validateRules
, so that you can keep it consistent with some of the other logic - for example if the publisher has disabled purpose enforcement it should allow regardless of consent data.
modules/gdprEnforcement.js
Outdated
let allow = !!checkConsent(consentData, modName, gvlid); | ||
let allow = | ||
gvlid === FIRST_PARTY_GVLID | ||
? !!deepAccess(consentData, `vendorData.publisher.consents.1`) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
vendorData.publisher.consents[${purposeNo}]
; currently this path will always have purpose 1 but I don't want us to forget about it in the future
src/activities/redactor.js
Outdated
@@ -17,7 +17,8 @@ export const ORTB_UFPD_PATHS = [ | |||
'kwarray', | |||
'id', | |||
'buyeruid', | |||
'customdata' | |||
'customdata', | |||
'device.ext.cdep' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this becomes user.device.ext.cdep
because of the .map
below, which is not correct. this PR adds the first field that's not under user
so it needs to be outside the map.
src/fpd/enrichment.js
Outdated
export async function tryToGetCdepLabel(cb = getCookieDeprecationLabel) { | ||
let cdep; | ||
if (isActivityAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(MODULE_TYPE_PREBID, 'cdep'))) { | ||
return GreedyPromise.resolve( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would do return GreedyPromise.resolve(isActivityAllowed(...) && cb())
. Your version wraps the promise two more times (with await
and the .then()
below), which seems unnecessary, plus if getCookieDeprecationLabel
rejects this promise never resolves.
modules/gdprEnforcement.js
Outdated
if (shouldEnforce(consentData, purposeNo, modName)) { | ||
const gvlid = getGvlid(params[ACTIVITY_PARAM_COMPONENT_TYPE], modName, gvlidFallback(params)); | ||
let allow = !!checkConsent(consentData, modName, gvlid); | ||
let allow = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO it should be inside validateRules
, so that you can keep it consistent with some of the other logic - for example if the publisher has disabled purpose enforcement it should allow regardless of consent data.
let validation = (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); | ||
|
||
if (gvlId === FIRST_PARTY_GVLID) { | ||
validation = (!rule.enforcePurpose || !!deepAccess(consentData, `vendorData.publisher.consents.${ruleOptions.id}`)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One question.. when FIRST_PARTY_GVLID is present I’m guessing we don’t need to worry about vendor for this validation? (looks to be the case with VENDORLESS_GVLID as well, so I was following down that road)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, in this case purposeConsent is required, vendorConsent is not, and the flag to check is publisher.consents
src/fpd/enrichment.js
Outdated
// } | ||
|
||
export async function tryToGetCdepLabel(cb = getCookieDeprecationLabel) { | ||
return GreedyPromise.resolve(isActivityAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(MODULE_TYPE_PREBID, 'cdep')) && await cb()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the getCookieDeprecationLabel func would require that we wait for it to resolve right? otherwise the label (if there is one) most likely wouldn’t get retrieved in time before the bid reqs go out.. (i'm not able to fetch it in time from my end at least). Was wondering what your thoughts on this use of await
here are? Or if you have any other suggestions?
One thing I was thinking.. maybe right here a setTimeout would be good? One that would only wait x amount of time for navigator.cookieDeprecationLabel.getValue()
to resolve and if it doesn’t we can resolve manually (and not block the auction)? I added in some arbitrary setTimeout code as an example, but of course that can be changed based on what you think.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getCookieDeprecationLabel
returns a promise; since this also returns a promise, await
is not necessary. Unless I'm mistaken await P; [...]
is just syntactic sugar for P.then(() => [...])
so in this case it does nothing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're correct! I think when I originally tried out your suggestion there was a bug on my end 😂 I've since tried it again and things are working as expected on my end 👍
hey @dgirardi, could you please take another look? |
added a quick "on hold" label to this PR temporarily, just so pubmatic can test for #10695 |
let validation = (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); | ||
|
||
if (gvlId === FIRST_PARTY_GVLID) { | ||
validation = (!rule.enforcePurpose || !!deepAccess(consentData, `vendorData.publisher.consents.${ruleOptions.id}`)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yep, in this case purposeConsent is required, vendorConsent is not, and the flag to check is publisher.consents
src/fpd/enrichment.js
Outdated
// } | ||
|
||
export async function tryToGetCdepLabel(cb = getCookieDeprecationLabel) { | ||
return GreedyPromise.resolve(isActivityAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(MODULE_TYPE_PREBID, 'cdep')) && await cb()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
getCookieDeprecationLabel
returns a promise; since this also returns a promise, await
is not necessary. Unless I'm mistaken await P; [...]
is just syntactic sugar for P.then(() => [...])
so in this case it does nothing.
src/fpd/enrichment.js
Outdated
}); | ||
setTimeout(() => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we have exising examples of FPD that waits for promises without timeouts (like user agent hints). I think it's OK because none of them actually need to wait for anything - there's no network calls or user prompts as far as I can tell, they should always resolve (or reject) very quickly. If we do introduce a timeout it should be configurable, and this should reject - not resolve to false.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, thanks for the input! Sounds like we are ok without the setTimeout in this case at this time then. I removed it for now.
#10695 is already merged and released |
is the failed test bc of Prebid.js/test/spec/modules/ixBidAdapter_spec.js Line 4564 in e3b99f8
|
@patmmccann removed the label I had added onto this ticket, good to go there! Also, interesting with the failed test.. I couldn't reproduce it locally. I also remember seeing that failed test in the circleci build for this PR too the other day but now it looks as though everything is green and passing? |
@dgirardi, attached a screenshot below of the failed test error that came up the other day when the build failed. Seems like an intermittent thing.. All checks look to have passed now. Is this something we should look into more or are we ok? |
Looks as though the same error crept up here as well fyi: #10635 |
test/spec/fpd/enrichment_spec.js
Outdated
@@ -310,6 +312,55 @@ describe('FPD enrichment', () => { | |||
}); | |||
}); | |||
|
|||
describe('privacy sandbox cookieDeprecationLabel', () => { | |||
it('attempts to set ext.cdep on device obj when the gdprEnforcement module is not active', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test does not match its description - and also mixes the behavior of enrichment and gdprEnforcement. IMO a better set of tests is:
- if
isActivityAllowed
is mocked totrue
, and the navigator API returns a promise to some label X, the enrichment puts X indevice.ext.cdep
(your tests only check that the navigator is invoked) - if
isActivityAllowed
is mocked tofalse
, the navigator API is not called and no enrichment happens - if the navigator API returns a promise that rejects, the enrichment does not halt forever (I expect your current version to fail this one)
- if
validateRules
is passedFIRST_PARTY_GVLID
, it will usepublisher.consents
- this should be part of the gdprEnforcement suite
src/fpd/enrichment.js
Outdated
return GreedyPromise.resolve(isActivityAllowed(ACTIVITY_ACCESS_DEVICE, activityParams(MODULE_TYPE_PREBID, 'cdep')) && cb()); | ||
} | ||
|
||
function getCookieDeprecationLabel() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll be more specific - this function, as far as I can tell, behaves the same as directly calling navigator.cookieDeprecationLabel.getValue()
except that you lose rejections / errors, the promise you return never completes in that case.
0eeb42c
to
f85c2b3
Compare
test/spec/fpd/enrichment_spec.js
Outdated
if (navigator.cookieDeprecationLabel) delete navigator.cookieDeprecationLabel; | ||
}); | ||
|
||
it('enrichment sets device.ext.cdep when navigator.getCookieDeprecationLabel exists and isActivityAllowed is mocked to true', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these are not actually mocking isActivityAllowed
, they are still relying on gdprEnforcement to actually set up the activity rule. If you try to run only this test suite (gulp serve-and-test --file test/spec/fpd/enrichment_spec.js
) it fails, since the gdpr module is not loaded.
test/spec/fpd/enrichment_spec.js
Outdated
|
||
it('if the navigator API returns a promise that rejects, the enrichment does not halt forever', () => { | ||
navigator.cookieDeprecationLabel = {}; | ||
sandbox.stub(dep, 'getCookieDeprecationLabel').returns(Promise.reject(new Error('oops, something went wrong'))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
More mismatch between intent and code - because you are not mocking the navigator API but instead the broken piece of logic, this test is not picking up the problem. It would pass no matter what you put in getCookieDeprecationLabel
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I pushed 83e3d3b to show what I meant with my last comments - consider it a suggestion, it it looks good to you then of course it also LGTM
With the caveat that my browser does not have 'navigator.cookieDeprecationLabel', so I did not test the actual integration - is there a flag to turn that on, or do we need to wait until it's too late?
Update: chrome 120 does have the API, but only on (I think) https sites. For me it returns an empty string which with this results in |
@dgirardi Try using chrome flags chrome://flags/#tpc-phase-out-facilitated-testing. This might help. |
@dgirardi saw your last commit, see what you mean about isActivityAllowed. also the other changes made sense and lgtm too! |
@patmmccann @dgirardi are we ok to merge this one? |
LGTM |
Awesome, thanks! |
Type of change
Description of change
Other information
#10516