From 0cb8ccf98d3d8860a885b747ac434128aca6f0d2 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Wed, 18 Jun 2025 20:10:48 +0300 Subject: [PATCH 1/4] Tests: Bidder Usersync Skipwhen Config --- .../tests/privacy/GppCookieSyncSpec.groovy | 184 +++++++++++++++++- 1 file changed, 182 insertions(+), 2 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy index a462e91a1ba..f4bc89aa367 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy @@ -1,6 +1,8 @@ package org.prebid.server.functional.tests.privacy import io.netty.handler.codec.http.HttpResponseStatus +import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.request.GppSectionId import org.prebid.server.functional.model.request.cookiesync.CookieSyncRequest import org.prebid.server.functional.model.response.cookiesync.UserSyncInfo import org.prebid.server.functional.service.PrebidServerException @@ -28,15 +30,26 @@ class GppCookieSyncSpec extends BaseSpec { private static final UserSyncInfo.Type USER_SYNC_TYPE = REDIRECT private static final boolean CORS_SUPPORT = false private static final String USER_SYNC_URL = "$networkServiceContainer.rootUri/generic-usersync" + private static final GppSectionId FIRST_GPP_SECTION = PBSUtils.getRandomEnum(GppSectionId.class) + private static final GppSectionId SECOND_GPP_SECTION = PBSUtils.getRandomEnum(GppSectionId.class, [FIRST_GPP_SECTION]) + private static final Map GENERIC_CONFIG = [ "adapters.${GENERIC.value}.meta-info.vendor-id" : GENERIC_VENDOR_ID as String, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] + private static final Map GENERIC_WITH_SKIP_CONFIG = [ + "adapters.${GENERIC.value}.meta-info.vendor-id" : GENERIC_VENDOR_ID as String, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : "$networkServiceContainer.rootUri/generic-usersync&redir={{redirect_url}}".toString(), + "adapters.${GENERIC.value}.usersync.skipwhen.gdpr" : 'true', + "adapters.${GENERIC.value}.usersync.skipwhen.gpp_sid" : "${FIRST_GPP_SECTION.value}, ${SECOND_GPP_SECTION.value}".toString(), + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] private static PrebidServerService prebidServerService = pbsServiceFactory.getService(GENERIC_CONFIG) + private static PrebidServerService prebidServerServiceWithSkipConfig = pbsServiceFactory.getService(GENERIC_WITH_SKIP_CONFIG + GENERIC_ALIAS_CONFIG) def cleanupSpec() { pbsServiceFactory.removeContainer(GENERIC_CONFIG) + pbsServiceFactory.removeContainer(GENERIC_WITH_SKIP_CONFIG + GENERIC_ALIAS_CONFIG) } def "PBS cookie sync request should set GDPR to 1 when gpp_sid contains 2"() { @@ -171,8 +184,8 @@ class GppCookieSyncSpec extends BaseSpec { it.gpp = new TcfEuV2Consent.Builder().build() it.gdpr = null it.gdprConsent = new TcfConsent.Builder().setPurposesLITransparency(DEVICE_ACCESS) - .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) - .build() + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() } when: "PBS processes cookie sync request" @@ -261,4 +274,171 @@ class GppCookieSyncSpec extends BaseSpec { where: userSyncFormat << [REDIRECT, IFRAME] } + + def "PBS should emit proper error message when request contain gdpr config and global skip gdpr config for adapter"() { + given: "Default CookieSyncRequest with gdpr config" + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = TCF_EU_V2.intValue + it.gpp = null + it.gdpr = 1 + it.gdprConsent = new TcfConsent.Builder().build() + } + + when: "PBS processes cookie sync request" + def response = prebidServerServiceWithSkipConfig.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response userSync url shouldn't contain cookies and userSync" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert !bidderStatus.userSync + assert !bidderStatus.noCookie + + and: "Response should contain proper error message" + assert bidderStatus.error == "Rejected by regulation scope" + } + + def "PBS should emit proper error message when alias request contain gdpr config and global skip gdpr config for adapter"() { + given: "Default CookieSyncRequest with gdpr config" + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.bidders = [BidderName.ALIAS] + it.gppSid = TCF_EU_V2.intValue + it.gpp = null + it.gdpr = 1 + it.gdprConsent = new TcfConsent.Builder().build() + } + + when: "PBS processes cookie sync request" + def response = prebidServerServiceWithSkipConfig.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response userSync url shouldn't contain cookies and userSync" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert !bidderStatus.userSync + assert !bidderStatus.noCookie + + and: "Response should contain proper error message" + assert bidderStatus.error == "Rejected by regulation scope" + } + + def "PBS should emit proper error message when request contain gpp config and specific global skip gpp config for adapter"() { + given: "Default CookieSyncRequest with gpp and gppSid" + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = TCF_EU_V2.intValue + it.gpp = new UsV1Consent.Builder().build() + it.gppSid = gppSid + } + + when: "PBS processes cookie sync request" + def response = prebidServerServiceWithSkipConfig.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response userSync url shouldn't contain cookies and userSync" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert !bidderStatus.userSync + assert !bidderStatus.noCookie + + and: "Response should contain proper error message" + assert bidderStatus.error == "Rejected by regulation scope" + + where: + gppSid << ["${FIRST_GPP_SECTION.value}", + "${SECOND_GPP_SECTION.value}", + "${FIRST_GPP_SECTION.value}, ${SECOND_GPP_SECTION.value}", + "${SECOND_GPP_SECTION.value}, ${FIRST_GPP_SECTION.value}", + "${SECOND_GPP_SECTION.value}, ${FIRST_GPP_SECTION.value}", + "${PBSUtils.getRandomEnum(GppSectionId.class, [FIRST_GPP_SECTION, SECOND_GPP_SECTION]).value}, ${SECOND_GPP_SECTION.value}", + "${FIRST_GPP_SECTION.value}, ${PBSUtils.getRandomEnum(GppSectionId.class, [FIRST_GPP_SECTION, SECOND_GPP_SECTION]).value}" + ] + } + + def "PBS should emit proper error message when alias request contain gpp config and specific global skip gpp config for adapter"() { + given: "Default CookieSyncRequest with gpp and gppSid" + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = TCF_EU_V2.intValue + it.bidders = [BidderName.ALIAS] + it.gpp = new UsV1Consent.Builder().build() + it.gppSid = gppSid + } + + when: "PBS processes cookie sync request" + def response = prebidServerServiceWithSkipConfig.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response userSync url shouldn't contain cookies and userSync" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert !bidderStatus.userSync + assert !bidderStatus.noCookie + + and: "Response should contain proper error message" + assert bidderStatus.error == "Rejected by regulation scope" + + where: + gppSid << ["${FIRST_GPP_SECTION.value}", + "${SECOND_GPP_SECTION.value}", + "${FIRST_GPP_SECTION.value}, ${SECOND_GPP_SECTION.value}", + "${SECOND_GPP_SECTION.value}, ${FIRST_GPP_SECTION.value}", + "${SECOND_GPP_SECTION.value}, ${FIRST_GPP_SECTION.value}", + "${PBSUtils.getRandomEnum(GppSectionId.class, [FIRST_GPP_SECTION, SECOND_GPP_SECTION]).value}, ${SECOND_GPP_SECTION.value}", + "${FIRST_GPP_SECTION.value}, ${PBSUtils.getRandomEnum(GppSectionId.class, [FIRST_GPP_SECTION, SECOND_GPP_SECTION]).value}" + ] + } + + def "PBS shouldn't emit error message when request doesn't contain gdpr config and global skip gdpr config for adapter"() { + given: "Default CookieSyncRequest with gdpr config" + def gppSid = TCF_EU_V2 + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = gppSid.intValue + it.gpp = null + it.gdpr = 0 + it.gdprConsent = new TcfConsent.Builder().build() + } + + when: "PBS processes cookie sync request" + def response = prebidServerServiceWithSkipConfig.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response userSync url should contain cookies and userSync" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert HttpUtil.findUrlParameterValue(bidderStatus.userSync?.url, "gpp") == "" + assert HttpUtil.findUrlParameterValue(bidderStatus.userSync?.url, "gpp_sid") == gppSid.value + + and: "Shouldn't contains any error" + assert !bidderStatus.error + } + + def "PBS shouldn't emit error message when request doesn't contain matched gpp config and specific global skip gpp config for adapter"() { + given: "Default CookieSyncRequest with gpp and gppSid" + def gpp = new UsV1Consent.Builder().build() + def gppSid = "${PBSUtils.getRandomEnum(GppSectionId.class, [FIRST_GPP_SECTION, SECOND_GPP_SECTION]).value}" + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = TCF_EU_V2.intValue + it.gpp = gpp + it.gppSid = gppSid + } + + when: "PBS processes cookie sync request" + def response = prebidServerServiceWithSkipConfig.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response userSync url should contain gpp and gppSid" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert HttpUtil.findUrlParameterValue(bidderStatus.userSync?.url, "gpp") == gpp.toString() + assert HttpUtil.findUrlParameterValue(bidderStatus.userSync?.url, "gpp_sid") == gppSid + + and: "Shouldn't contains any error" + assert !bidderStatus.error + } + + def "PBS should also include validation warning when request matches skip config and has validation issue at same time"() { + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = PBSUtils.getRandomNumberWithExclusion(TCF_EU_V2.intValue) + it.gpp = null + it.gdpr = 1 + it.gdprConsent = new TcfConsent.Builder().build() + } + + when: "PBS processes cookie sync request" + def response = prebidServerServiceWithSkipConfig.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain a warning" + assert response.warnings == ["GPP scope does not match TCF2 scope"] + + then: "Privacy for bidder should be enforced" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert bidderStatus.error == "Rejected by regulation scope" + } } From 2794aeea3a3455585e0f0a0df7d309f1d37e9c67 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 20 Jun 2025 13:27:03 +0300 Subject: [PATCH 2/4] update after review --- .../tests/privacy/GppCookieSyncSpec.groovy | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy index f4bc89aa367..2db1ad4e04f 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy @@ -15,6 +15,7 @@ import org.prebid.server.functional.util.privacy.TcfConsent import org.prebid.server.functional.util.privacy.gpp.TcfEuV2Consent import org.prebid.server.functional.util.privacy.gpp.UsV1Consent +import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.GENERIC import static org.prebid.server.functional.model.request.GppSectionId.TCF_EU_V2 import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 @@ -279,7 +280,6 @@ class GppCookieSyncSpec extends BaseSpec { given: "Default CookieSyncRequest with gdpr config" def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { it.gppSid = TCF_EU_V2.intValue - it.gpp = null it.gdpr = 1 it.gdprConsent = new TcfConsent.Builder().build() } @@ -299,9 +299,8 @@ class GppCookieSyncSpec extends BaseSpec { def "PBS should emit proper error message when alias request contain gdpr config and global skip gdpr config for adapter"() { given: "Default CookieSyncRequest with gdpr config" def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { - it.bidders = [BidderName.ALIAS] + it.bidders = [ALIAS] it.gppSid = TCF_EU_V2.intValue - it.gpp = null it.gdpr = 1 it.gdprConsent = new TcfConsent.Builder().build() } @@ -352,7 +351,7 @@ class GppCookieSyncSpec extends BaseSpec { given: "Default CookieSyncRequest with gpp and gppSid" def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { it.gppSid = TCF_EU_V2.intValue - it.bidders = [BidderName.ALIAS] + it.bidders = [ALIAS] it.gpp = new UsV1Consent.Builder().build() it.gppSid = gppSid } @@ -384,7 +383,6 @@ class GppCookieSyncSpec extends BaseSpec { def gppSid = TCF_EU_V2 def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { it.gppSid = gppSid.intValue - it.gpp = null it.gdpr = 0 it.gdprConsent = new TcfConsent.Builder().build() } @@ -397,7 +395,7 @@ class GppCookieSyncSpec extends BaseSpec { assert HttpUtil.findUrlParameterValue(bidderStatus.userSync?.url, "gpp") == "" assert HttpUtil.findUrlParameterValue(bidderStatus.userSync?.url, "gpp_sid") == gppSid.value - and: "Shouldn't contains any error" + and: "Response shouldn't contains any error" assert !bidderStatus.error } @@ -419,14 +417,13 @@ class GppCookieSyncSpec extends BaseSpec { assert HttpUtil.findUrlParameterValue(bidderStatus.userSync?.url, "gpp") == gpp.toString() assert HttpUtil.findUrlParameterValue(bidderStatus.userSync?.url, "gpp_sid") == gppSid - and: "Shouldn't contains any error" + and: "Response shouldn't contains any error" assert !bidderStatus.error } def "PBS should also include validation warning when request matches skip config and has validation issue at same time"() { def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { it.gppSid = PBSUtils.getRandomNumberWithExclusion(TCF_EU_V2.intValue) - it.gpp = null it.gdpr = 1 it.gdprConsent = new TcfConsent.Builder().build() } From e28d7f7ea730b25887246ebb0f5f001f8218af1e Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 20 Jun 2025 15:02:39 +0300 Subject: [PATCH 3/4] update after review --- .../tests/privacy/GppCookieSyncSpec.groovy | 79 ++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy index 2db1ad4e04f..efc79d1113e 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy @@ -1,7 +1,11 @@ package org.prebid.server.functional.tests.privacy import io.netty.handler.codec.http.HttpResponseStatus -import org.prebid.server.functional.model.bidder.BidderName +import org.prebid.server.functional.model.config.AccountConfig +import org.prebid.server.functional.model.config.AccountGdprConfig +import org.prebid.server.functional.model.config.AccountPrivacyConfig +import org.prebid.server.functional.model.config.PurposeConfig +import org.prebid.server.functional.model.db.Account import org.prebid.server.functional.model.request.GppSectionId import org.prebid.server.functional.model.request.cookiesync.CookieSyncRequest import org.prebid.server.functional.model.response.cookiesync.UserSyncInfo @@ -17,6 +21,8 @@ import org.prebid.server.functional.util.privacy.gpp.UsV1Consent import static org.prebid.server.functional.model.bidder.BidderName.ALIAS import static org.prebid.server.functional.model.bidder.BidderName.GENERIC +import static org.prebid.server.functional.model.config.Purpose.P1 +import static org.prebid.server.functional.model.config.PurposeEnforcement.NO import static org.prebid.server.functional.model.request.GppSectionId.TCF_EU_V2 import static org.prebid.server.functional.model.request.GppSectionId.USP_V1 import static org.prebid.server.functional.model.response.cookiesync.UserSyncInfo.Type.IFRAME @@ -399,6 +405,77 @@ class GppCookieSyncSpec extends BaseSpec { assert !bidderStatus.error } + def "PBS shouldn't emit error message when request does contain gdpr config and global skip gdpr config disabled for adapter"() { + given: "Pbs config with usersync.#userSyncFormat.url" + def pbsConfig = [ + "adapters.${GENERIC.value}.meta-info.vendor-id" : GENERIC_VENDOR_ID as String, + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL, + "adapters.${GENERIC.value}.usersync.skipwhen.gdpr" : 'false', + "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()] + def prebidServerService = pbsServiceFactory.getService(pbsConfig) + + and: "Default CookieSyncRequest with gdpr config" + def gppSid = TCF_EU_V2 + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = gppSid.intValue + it.gdpr = 1 + it.gdprConsent = new TcfConsent.Builder().setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + it.account = PBSUtils.randomNumber + } + + and: "Save account config with requireConsent into DB" + def purposes = [(P1): new PurposeConfig(enforcePurpose: NO, enforceVendors: false)] + def accountGdprConfig = new AccountGdprConfig(purposes: purposes) + def privacyConfig = new AccountPrivacyConfig(gdpr: accountGdprConfig) + def account = new Account(uuid: cookieSyncRequest.account, config: new AccountConfig(privacy: privacyConfig)) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = prebidServerService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain proper userSync url" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert bidderStatus.userSync?.url == USER_SYNC_URL + + and: "Response shouldn't contains any error" + assert !bidderStatus.error + + cleanup: "Stop and remove pbs container" + pbsServiceFactory.removeContainer(pbsConfig) + } + + def "PBS shouldn't emit error message when request does contain gdpr config and global skip gdpr config default for adapter"() { + given: "Default CookieSyncRequest with gdpr config" + def gppSid = TCF_EU_V2 + def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { + it.gppSid = gppSid.intValue + it.gdpr = 1 + it.gdprConsent = new TcfConsent.Builder().setPurposesLITransparency(DEVICE_ACCESS) + .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) + .build() + it.account = PBSUtils.randomNumber + } + + and: "Save account config with requireConsent into DB" + def purposes = [(P1): new PurposeConfig(enforcePurpose: NO, enforceVendors: false)] + def accountGdprConfig = new AccountGdprConfig(purposes: purposes) + def privacyConfig = new AccountPrivacyConfig(gdpr: accountGdprConfig) + def account = new Account(uuid: cookieSyncRequest.account, config: new AccountConfig(privacy: privacyConfig)) + accountDao.save(account) + + when: "PBS processes cookie sync request" + def response = prebidServerService.sendCookieSyncRequest(cookieSyncRequest) + + then: "Response should contain proper userSync url" + def bidderStatus = response.getBidderUserSync(GENERIC) + assert bidderStatus.userSync?.url == USER_SYNC_URL + + and: "Response shouldn't contains any error" + assert !bidderStatus.error + } + def "PBS shouldn't emit error message when request doesn't contain matched gpp config and specific global skip gpp config for adapter"() { given: "Default CookieSyncRequest with gpp and gppSid" def gpp = new UsV1Consent.Builder().build() From 30e6c87d0595450182d11d61978317c05553f012 Mon Sep 17 00:00:00 2001 From: osulzhenko Date: Fri, 20 Jun 2025 15:53:17 +0300 Subject: [PATCH 4/4] update after review --- .../functional/tests/privacy/GppCookieSyncSpec.groovy | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy index efc79d1113e..40bb6e74d96 100644 --- a/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy +++ b/src/test/groovy/org/prebid/server/functional/tests/privacy/GppCookieSyncSpec.groovy @@ -415,9 +415,8 @@ class GppCookieSyncSpec extends BaseSpec { def prebidServerService = pbsServiceFactory.getService(pbsConfig) and: "Default CookieSyncRequest with gdpr config" - def gppSid = TCF_EU_V2 def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { - it.gppSid = gppSid.intValue + it.gppSid = TCF_EU_V2 it.gdpr = 1 it.gdprConsent = new TcfConsent.Builder().setPurposesLITransparency(DEVICE_ACCESS) .setVendorLegitimateInterest([GENERIC_VENDOR_ID]) @@ -448,9 +447,8 @@ class GppCookieSyncSpec extends BaseSpec { def "PBS shouldn't emit error message when request does contain gdpr config and global skip gdpr config default for adapter"() { given: "Default CookieSyncRequest with gdpr config" - def gppSid = TCF_EU_V2 def cookieSyncRequest = CookieSyncRequest.defaultCookieSyncRequest.tap { - it.gppSid = gppSid.intValue + it.gppSid = TCF_EU_V2 it.gdpr = 1 it.gdprConsent = new TcfConsent.Builder().setPurposesLITransparency(DEVICE_ACCESS) .setVendorLegitimateInterest([GENERIC_VENDOR_ID])