Skip to content

Commit 74fbd3e

Browse files
Enhanced /setuid TCF support (#3633)
1 parent 6bdb547 commit 74fbd3e

File tree

5 files changed

+281
-49
lines changed

5 files changed

+281
-49
lines changed

src/main/java/org/prebid/server/handler/SetuidHandler.java

Lines changed: 26 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import io.netty.handler.codec.http.HttpResponseStatus;
44
import io.vertx.core.AsyncResult;
5+
import io.vertx.core.CompositeFuture;
56
import io.vertx.core.Future;
67
import io.vertx.core.http.Cookie;
78
import io.vertx.core.http.HttpHeaders;
@@ -50,6 +51,8 @@
5051
import org.prebid.server.privacy.gdpr.model.TcfResponse;
5152
import org.prebid.server.settings.ApplicationSettings;
5253
import org.prebid.server.settings.model.Account;
54+
import org.prebid.server.settings.model.AccountGdprConfig;
55+
import org.prebid.server.settings.model.AccountPrivacyConfig;
5356
import org.prebid.server.util.HttpUtil;
5457
import org.prebid.server.vertx.verticles.server.HttpEndpoint;
5558
import org.prebid.server.vertx.verticles.server.application.ApplicationResource;
@@ -208,17 +211,23 @@ private void handleSetuidContextResult(AsyncResult<SetuidContext> setuidContextR
208211

209212
if (setuidContextResult.succeeded()) {
210213
final SetuidContext setuidContext = setuidContextResult.result();
211-
final String bidder = setuidContext.getCookieName();
214+
final String bidderCookieName = setuidContext.getCookieName();
212215
final TcfContext tcfContext = setuidContext.getPrivacyContext().getTcfContext();
213216

214217
try {
215-
validateSetuidContext(setuidContext, bidder);
218+
validateSetuidContext(setuidContext, bidderCookieName);
216219
} catch (InvalidRequestException | UnauthorizedUidsException | UnavailableForLegalReasonsException e) {
217220
handleErrors(e, routingContext, tcfContext);
218221
return;
219222
}
220223

221-
isAllowedForHostVendorId(tcfContext)
224+
final AccountPrivacyConfig privacyConfig = setuidContext.getAccount().getPrivacy();
225+
final AccountGdprConfig accountGdprConfig = privacyConfig != null ? privacyConfig.getGdpr() : null;
226+
227+
Future.all(
228+
tcfDefinerService.isAllowedForHostVendorId(tcfContext),
229+
tcfDefinerService.resultForBidderNames(
230+
Collections.singleton(bidderCookieName), tcfContext, accountGdprConfig))
222231
.onComplete(hostTcfResponseResult -> respondByTcfResponse(hostTcfResponseResult, setuidContext));
223232
} else {
224233
final Throwable error = setuidContextResult.cause();
@@ -255,44 +264,25 @@ private void validateSetuidContext(SetuidContext setuidContext, String bidder) {
255264
}
256265
}
257266

258-
/**
259-
* If host vendor id is null, host allowed to setuid.
260-
*/
261-
private Future<HostVendorTcfResponse> isAllowedForHostVendorId(TcfContext tcfContext) {
262-
final Integer gdprHostVendorId = tcfDefinerService.getGdprHostVendorId();
263-
return gdprHostVendorId == null
264-
? Future.succeededFuture(HostVendorTcfResponse.allowedVendor())
265-
: tcfDefinerService.resultForVendorIds(Collections.singleton(gdprHostVendorId), tcfContext)
266-
.map(this::toHostVendorTcfResponse);
267-
}
268-
269-
private HostVendorTcfResponse toHostVendorTcfResponse(TcfResponse<Integer> tcfResponse) {
270-
return HostVendorTcfResponse.of(tcfResponse.getUserInGdprScope(), tcfResponse.getCountry(),
271-
isSetuidAllowed(tcfResponse));
272-
}
273-
274-
private boolean isSetuidAllowed(TcfResponse<Integer> hostTcfResponseToSetuidContext) {
275-
// allow cookie only if user is not in GDPR scope or vendor passed GDPR check
276-
final boolean notInGdprScope = BooleanUtils.isFalse(hostTcfResponseToSetuidContext.getUserInGdprScope());
277-
278-
final Map<Integer, PrivacyEnforcementAction> vendorIdToAction = hostTcfResponseToSetuidContext.getActions();
279-
final PrivacyEnforcementAction hostPrivacyAction = vendorIdToAction != null
280-
? vendorIdToAction.get(tcfDefinerService.getGdprHostVendorId())
281-
: null;
282-
final boolean blockPixelSync = hostPrivacyAction == null || hostPrivacyAction.isBlockPixelSync();
283-
284-
return notInGdprScope || !blockPixelSync;
285-
}
286-
287-
private void respondByTcfResponse(AsyncResult<HostVendorTcfResponse> hostTcfResponseResult,
288-
SetuidContext setuidContext) {
267+
private void respondByTcfResponse(AsyncResult<CompositeFuture> hostTcfResponseResult, SetuidContext setuidContext) {
289268
final String bidderCookieName = setuidContext.getCookieName();
290269
final TcfContext tcfContext = setuidContext.getPrivacyContext().getTcfContext();
291270
final RoutingContext routingContext = setuidContext.getRoutingContext();
292271

293272
if (hostTcfResponseResult.succeeded()) {
294-
final HostVendorTcfResponse hostTcfResponse = hostTcfResponseResult.result();
295-
if (hostTcfResponse.isVendorAllowed()) {
273+
final CompositeFuture compositeFuture = hostTcfResponseResult.result();
274+
final HostVendorTcfResponse hostVendorTcfResponse = compositeFuture.resultAt(0);
275+
final TcfResponse<String> bidderTcfResponse = compositeFuture.resultAt(1);
276+
277+
final Map<String, PrivacyEnforcementAction> vendorIdToAction = bidderTcfResponse.getActions();
278+
final PrivacyEnforcementAction action = vendorIdToAction != null
279+
? vendorIdToAction.get(bidderCookieName)
280+
: null;
281+
282+
final boolean notInGdprScope = BooleanUtils.isFalse(bidderTcfResponse.getUserInGdprScope());
283+
final boolean isBidderVendorAllowed = notInGdprScope || action == null || !action.isBlockPixelSync();
284+
285+
if (hostVendorTcfResponse.isVendorAllowed() && isBidderVendorAllowed) {
296286
respondWithCookie(setuidContext);
297287
} else {
298288
metrics.updateUserSyncTcfBlockedMetric(bidderCookieName);
@@ -308,7 +298,6 @@ private void respondByTcfResponse(AsyncResult<HostVendorTcfResponse> hostTcfResp
308298

309299
analyticsDelegator.processEvent(SetuidEvent.error(status.code()), tcfContext);
310300
}
311-
312301
} else {
313302
final Throwable error = hostTcfResponseResult.cause();
314303
metrics.updateUserSyncTcfBlockedMetric(bidderCookieName);

src/main/java/org/prebid/server/privacy/HostVendorTcfDefinerService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ private HostVendorTcfResponse toHostVendorTcfResponse(TcfResponse<Integer> tcfRe
5151
return HostVendorTcfResponse.of(
5252
tcfResponse.getUserInGdprScope(),
5353
tcfResponse.getCountry(),
54-
isCookieSyncAllowed(tcfResponse));
54+
isVendorAllowed(tcfResponse));
5555
}
5656

57-
private boolean isCookieSyncAllowed(TcfResponse<Integer> hostTcfResponse) {
57+
private boolean isVendorAllowed(TcfResponse<Integer> hostTcfResponse) {
5858
return Optional.ofNullable(hostTcfResponse.getActions())
5959
.map(vendorIdToAction -> vendorIdToAction.get(gdprHostVendorId))
6060
.map(hostActions -> !hostActions.isBlockPixelSync())

src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class SetUidSpec extends BaseSpec {
3737
"adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value,
3838
"adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL,
3939
"adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()]
40+
private static final String TCF_ERROR_MESSAGE = "The gdpr_consent param prevents cookies from being saved"
41+
private static final int UNAVAILABLE_FOR_LEGAL_REASONS_CODE = 451
4042

4143
@Shared
4244
PrebidServerService prebidServerService = pbsServiceFactory.getService(PBS_CONFIG)
@@ -199,8 +201,8 @@ class SetUidSpec extends BaseSpec {
199201

200202
then: "Request should fail with error"
201203
def exception = thrown(PrebidServerException)
202-
assert exception.statusCode == 451
203-
assert exception.responseBody == "The gdpr_consent param prevents cookies from being saved"
204+
assert exception.statusCode == UNAVAILABLE_FOR_LEGAL_REASONS_CODE
205+
assert exception.responseBody == TCF_ERROR_MESSAGE
204206

205207
and: "usersync.FAMILY.tcf.blocked metric should be updated"
206208
def metric = prebidServerService.sendCollectedMetricsRequest()

0 commit comments

Comments
 (0)