Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import lombok.Value;
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
import org.prebid.server.auction.gpp.model.GppContext;
import org.prebid.server.bidder.UsersyncMethodType;
import org.prebid.server.bidder.Usersyncer;
import org.prebid.server.cookie.UidsCookie;
import org.prebid.server.execution.timeout.Timeout;
import org.prebid.server.privacy.model.PrivacyContext;
Expand All @@ -27,10 +27,10 @@ public class SetuidContext {

Account account;

String cookieName;
String bidder;

@JsonIgnore
UsersyncMethodType syncType;
Usersyncer usersyncer;

PrivacyContext privacyContext;

Expand Down
10 changes: 5 additions & 5 deletions src/main/java/org/prebid/server/bidder/Usersyncer.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.prebid.server.bidder;

import lombok.Value;
import org.prebid.server.spring.config.bidder.model.usersync.CookieFamilySource;

import java.util.List;

Expand All @@ -10,9 +9,9 @@ public class Usersyncer {

boolean enabled;

String cookieFamilyName;
String bidder;

CookieFamilySource cookieFamilySource;
String cookieFamilyName;

UsersyncMethod iframe;

Expand All @@ -22,16 +21,17 @@ public class Usersyncer {

List<Integer> gppSidToSkip;

public static Usersyncer of(String cookieFamilyName,
public static Usersyncer of(String bidder,
String cookieFamilyName,
UsersyncMethod iframe,
UsersyncMethod redirect,
boolean skipWhenInGdprScope,
List<Integer> gppSidToSkip) {

return of(
true,
bidder,
cookieFamilyName,
CookieFamilySource.ROOT,
iframe,
redirect,
skipWhenInGdprScope,
Expand Down
140 changes: 39 additions & 101 deletions src/main/java/org/prebid/server/cookie/CookieSyncService.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.vertx.core.Future;
import io.vertx.ext.web.RoutingContext;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.SetUtils;
import org.apache.commons.lang3.ObjectUtils;
Expand Down Expand Up @@ -40,10 +41,8 @@
import org.prebid.server.settings.model.AccountCookieSyncConfig;
import org.prebid.server.settings.model.AccountGdprConfig;
import org.prebid.server.settings.model.AccountPrivacyConfig;
import org.prebid.server.spring.config.bidder.model.usersync.CookieFamilySource;
import org.prebid.server.util.HttpUtil;
import org.prebid.server.util.ObjectUtil;
import org.prebid.server.util.StreamUtil;

import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -115,7 +114,8 @@ public Future<CookieSyncContext> processContext(CookieSyncContext cookieSyncCont
.map(this::filterBiddersByGppSid)
.map(this::applyRequestFilterSettings)
.compose(this::applyPrivacyFilteringRules)
.map(this::filterInSyncBidders);
.map(this::filterInSyncBidders)
.map(this::applyLimitAndDeduplicate);
}

private CookieSyncContext validateCookieSyncContext(CookieSyncContext cookieSyncContext) {
Expand Down Expand Up @@ -161,16 +161,13 @@ private CookieSyncContext resolveLimit(CookieSyncContext cookieSyncContext) {
}

private CookieSyncContext resolveBiddersToSync(CookieSyncContext cookieSyncContext) {
// TODO: Add multisync bidders from 1.a)
// TODO: filter that are done with multisync
final List<String> requestedBiddersAsList = new ArrayList<>(
SetUtils.emptyIfNull(cookieSyncContext.getCookieSyncRequest().getBidders()));
Collections.shuffle(requestedBiddersAsList);

final BiddersContext updatedContext = cookieSyncContext.getBiddersContext().toBuilder()
.requestedBidders(new LinkedHashSet<>(requestedBiddersAsList))
.coopSyncBidders(coopSyncProvider.coopSyncBidders(cookieSyncContext))
.multiSyncBidders(Set.of())
.build();

return cookieSyncContext.with(updatedContext);
Expand Down Expand Up @@ -285,6 +282,30 @@ private Future<CookieSyncContext> applyPrivacyFilteringRules(CookieSyncContext c
.map(context -> filterDisallowedActivities(context, tcfContext));
}

private CookieSyncContext applyLimitAndDeduplicate(CookieSyncContext cookieSyncContext) {
final Set<String> allowedBiddersByPriority = cookieSyncContext.getBiddersContext().allowedBiddersByPriority();

final Set<String> cookieFamiliesSeen = new HashSet<>();
final Set<String> biddersWithDuplicateCookieFamilyName = new HashSet<>();
final Iterator<String> biddersIterator = allowedBiddersByPriority.iterator();

while (cookieFamiliesSeen.size() < cookieSyncContext.getLimit() && biddersIterator.hasNext()) {
final String bidder = biddersIterator.next();
final String cookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElseThrow();

if (!cookieFamiliesSeen.add(cookieFamilyName)) {
biddersWithDuplicateCookieFamilyName.add(bidder);
}
}
final Set<String> biddersOverLimit = new HashSet<>(IteratorUtils.toList(biddersIterator));

final BiddersContext updatedBiddersContext = cookieSyncContext.getBiddersContext()
.withRejectedBidders(biddersOverLimit, RejectionReason.OVER_LIMIT)
.withRejectedBidders(biddersWithDuplicateCookieFamilyName,
RejectionReason.DUPLICATE_COOKIE_FAMILY_NAME);
return cookieSyncContext.with(updatedBiddersContext);
}

private Future<CookieSyncContext> filterWithTcfResponse(HostVendorTcfResponse hostVendorTcfResponse,
CookieSyncContext cookieSyncContext) {

Expand Down Expand Up @@ -363,11 +384,9 @@ public CookieSyncResponse prepareResponse(CookieSyncContext cookieSyncContext) {
? CookieSyncStatus.OK
: CookieSyncStatus.NO_COOKIE;

final Set<String> biddersToSync = biddersToSync(cookieSyncContext);

final List<BidderUsersyncStatus> statuses = ListUtils.union(
validStatuses(biddersToSync, cookieSyncContext),
debugStatuses(biddersToSync, cookieSyncContext));
validStatuses(cookieSyncContext),
cookieSyncContext.isDebug() ? rejectionStatuses(cookieSyncContext) : Collections.emptyList());

final List<String> warnings = cookieSyncContext.getWarnings();
final List<String> resolvedWarnings = CollectionUtils.isNotEmpty(warnings)
Expand All @@ -377,37 +396,8 @@ public CookieSyncResponse prepareResponse(CookieSyncContext cookieSyncContext) {
return CookieSyncResponse.of(cookieSyncStatus, Collections.unmodifiableList(statuses), resolvedWarnings);
}

private Set<String> biddersToSync(CookieSyncContext cookieSyncContext) {
final Set<String> allowedBiddersByPriority = allowedBiddersByPriority(cookieSyncContext);

final Set<String> cookieFamiliesToSync = new HashSet<>(); // multiple bidders may have same cookie families
final Set<String> biddersToSync = new LinkedHashSet<>();
final Iterator<String> biddersIterator = allowedBiddersByPriority.iterator();

while (cookieFamiliesToSync.size() < cookieSyncContext.getLimit() && biddersIterator.hasNext()) {
final String bidder = biddersIterator.next();
final String cookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElseThrow();

cookieFamiliesToSync.add(cookieFamilyName);
biddersToSync.add(bidder);
}

return biddersToSync;
}

private static Set<String> allowedBiddersByPriority(CookieSyncContext cookieSyncContext) {
final BiddersContext biddersContext = cookieSyncContext.getBiddersContext();

final Set<String> allowedBiddersByPriority = new LinkedHashSet<>();
allowedBiddersByPriority.addAll(biddersContext.allowedRequestedBidders());
allowedBiddersByPriority.addAll(biddersContext.allowedCoopSyncBidders());

return allowedBiddersByPriority;
}

private List<BidderUsersyncStatus> validStatuses(Set<String> biddersToSync, CookieSyncContext cookieSyncContext) {
return biddersToSync.stream()
.filter(StreamUtil.distinctBy(bidder -> bidderCatalog.cookieFamilyName(bidder).orElseThrow()))
private List<BidderUsersyncStatus> validStatuses(CookieSyncContext cookieSyncContext) {
return cookieSyncContext.getBiddersContext().allowedBidders().stream()
.map(bidder -> validStatus(bidder, cookieSyncContext))
.toList();
}
Expand All @@ -421,25 +411,25 @@ private BidderUsersyncStatus validStatus(String bidder, CookieSyncContext cookie
final Privacy privacy = cookieSyncContext.getPrivacyContext().getPrivacy();
final String hostCookieUid = uidsCookieService.hostCookieUidToSync(routingContext, cookieFamilyName);

final UsersyncInfo usersyncInfo = toUsersyncInfo(usersyncMethod, cookieFamilyName, hostCookieUid, privacy);
final UsersyncInfo usersyncInfo = toUsersyncInfo(usersyncMethod, bidder, hostCookieUid, privacy);

return BidderUsersyncStatus.builder()
.bidder(cookieFamilyName) // we are syncing cookie-family-names instead of bidder codes
.bidder(bidder)
.noCookie(true)
.usersync(usersyncInfo)
.build();
}

private UsersyncInfo toUsersyncInfo(UsersyncMethod usersyncMethod,
String cookieFamilyName,
String bidder,
String hostCookieUid,
Privacy privacy) {

final UsersyncInfoBuilder usersyncInfoBuilder = UsersyncInfoBuilder.from(usersyncMethod);

if (hostCookieUid != null) {
final String url = UsersyncUtil.CALLBACK_URL_TEMPLATE.formatted(
externalUrl, HttpUtil.encodeUrl(cookieFamilyName), HttpUtil.encodeUrl(hostCookieUid));
externalUrl, HttpUtil.encodeUrl(bidder), HttpUtil.encodeUrl(hostCookieUid));

usersyncInfoBuilder
.usersyncUrl(UsersyncUtil.enrichUrlWithFormat(url, UsersyncUtil.resolveFormat(usersyncMethod)))
Expand All @@ -451,19 +441,6 @@ private UsersyncInfo toUsersyncInfo(UsersyncMethod usersyncMethod,
.build();
}

private List<BidderUsersyncStatus> debugStatuses(Set<String> biddersToSync, CookieSyncContext cookieSyncContext) {
if (!cookieSyncContext.isDebug()) {
return Collections.emptyList();
}

final List<BidderUsersyncStatus> debugStatuses = new ArrayList<>();
debugStatuses.addAll(rejectionStatuses(cookieSyncContext));
debugStatuses.addAll(limitStatuses(biddersToSync, cookieSyncContext));
debugStatuses.addAll(aliasSyncedAsRootStatuses(biddersToSync, cookieSyncContext));

return debugStatuses;
}

private List<BidderUsersyncStatus> rejectionStatuses(CookieSyncContext cookieSyncContext) {
final BiddersContext biddersContext = cookieSyncContext.getBiddersContext();
return biddersContext.rejectedBidders().entrySet().stream()
Expand All @@ -474,9 +451,7 @@ private List<BidderUsersyncStatus> rejectionStatuses(CookieSyncContext cookieSyn
}

private BidderUsersyncStatus rejectionStatus(String bidder, RejectionReason reason, BiddersContext biddersContext) {
final String cookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElse(bidder);
BidderUsersyncStatus.BidderUsersyncStatusBuilder builder = BidderUsersyncStatus.builder()
.bidder(cookieFamilyName);
BidderUsersyncStatus.BidderUsersyncStatusBuilder builder = BidderUsersyncStatus.builder().bidder(bidder);

final boolean requested = biddersContext.isRequested(bidder);
final boolean coopSync = biddersContext.isCoopSync(bidder);
Expand All @@ -493,51 +468,14 @@ private BidderUsersyncStatus rejectionStatus(String bidder, RejectionReason reas
case ALREADY_IN_SYNC -> builder.conditionalError(requested, "Already in sync");
case REJECTED_BY_REGULATION_SCOPE -> builder.conditionalError(
requested || coopSync, "Rejected by regulation scope");
case OVER_LIMIT -> builder.conditionalError(requested, "Limit reached");
case DUPLICATE_COOKIE_FAMILY_NAME -> builder.conditionalError(
requested, "Duplicate bidder synced as " + bidderCatalog.cookieFamilyName(bidder).orElseThrow());
};

return builder.build();
}

private List<BidderUsersyncStatus> limitStatuses(Set<String> biddersToSync, CookieSyncContext cookieSyncContext) {
final Set<String> droppedDueToLimitBidders = SetUtils.difference(
cookieSyncContext.getBiddersContext().allowedRequestedBidders(), biddersToSync);

return droppedDueToLimitBidders.stream()
.map(bidder -> BidderUsersyncStatus.builder()
.bidder(bidderCatalog.cookieFamilyName(bidder).orElseThrow())
.error("limit reached")
.build())
.toList();
}

private List<BidderUsersyncStatus> aliasSyncedAsRootStatuses(Set<String> biddersToSync,
CookieSyncContext cookieSyncContext) {

final Set<String> allowedRequestedBidders = cookieSyncContext.getBiddersContext().allowedRequestedBidders();

return biddersToSync.stream()
.filter(allowedRequestedBidders::contains)
.filter(this::isAliasSyncedAsRootFamily)
.map(this::warningForAliasSyncedAsRootFamily)
.toList();
}

private boolean isAliasSyncedAsRootFamily(String bidder) {
return bidderCatalog.isAlias(bidder)
&& bidderCatalog.usersyncerByName(bidder)
.map(Usersyncer::getCookieFamilySource)
.filter(source -> source == CookieFamilySource.ROOT)
.isPresent();
}

private BidderUsersyncStatus warningForAliasSyncedAsRootFamily(String bidder) {
final String cookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElseThrow();
return BidderUsersyncStatus.builder()
.bidder(bidder)
.error("synced as " + cookieFamilyName)
.build();
}

private void updateCookieSyncTcfMetrics(BiddersContext biddersContext) {
biddersContext.rejectedBidders().entrySet().stream()
.filter(entry -> entry.getValue() == RejectionReason.REJECTED_BY_TCF)
Expand Down
22 changes: 8 additions & 14 deletions src/main/java/org/prebid/server/cookie/model/BiddersContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

Expand All @@ -24,9 +25,6 @@ public class BiddersContext {
@Builder.Default
Set<String> coopSyncBidders = new HashSet<>();

@Builder.Default
Set<String> multiSyncBidders = new HashSet<>();

@Builder.Default
Map<String, RejectionReason> rejectedBidders = new HashMap<>();

Expand All @@ -42,25 +40,21 @@ public boolean isCoopSync(String bidder) {
}

private Set<String> involvedBidders() {
return SetUtils.union(
multiSyncBidders,
SetUtils.union(requestedBidders, coopSyncBidders));
return SetUtils.union(requestedBidders, coopSyncBidders);
}

public Set<String> allowedBidders() {
return SetUtils.difference(involvedBidders(), rejectedBidders.keySet());
}

public Set<String> allowedRequestedBidders() {
return SetUtils.difference(requestedBidders, rejectedBidders.keySet());
}
public Set<String> allowedBiddersByPriority() {
final Set<String> allowedBiddersByPriority = new LinkedHashSet<>();

public Set<String> allowedCoopSyncBidders() {
return SetUtils.difference(coopSyncBidders, rejectedBidders.keySet());
}
allowedBiddersByPriority.addAll(requestedBidders);
allowedBiddersByPriority.addAll(coopSyncBidders);
allowedBiddersByPriority.removeAll(rejectedBidders.keySet());

public Set<String> allowedMultisyncBidders() {
return SetUtils.difference(multiSyncBidders, rejectedBidders.keySet());
return allowedBiddersByPriority;
}

public BiddersContext withRejectedBidder(String bidder, RejectionReason reason) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ public enum RejectionReason {
DISABLED_USERSYNC,
REJECTED_BY_FILTER,
ALREADY_IN_SYNC,
REJECTED_BY_REGULATION_SCOPE
REJECTED_BY_REGULATION_SCOPE,
OVER_LIMIT,
DUPLICATE_COOKIE_FAMILY_NAME
}
Loading
Loading