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
2 changes: 1 addition & 1 deletion src/main/java/org/prebid/server/bidder/BidderCatalog.java
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ public Optional<Usersyncer> usersyncerByName(String name) {
.map(BidderInstanceDeps::getUsersyncer);
}

private Optional<String> aliasOf(String bidder) {
public Optional<String> aliasOf(String bidder) {
return Optional.ofNullable(bidder)
.map(bidderDepsMap::get)
.map(BidderInstanceDeps::getBidderInfo)
Expand Down
13 changes: 7 additions & 6 deletions src/main/java/org/prebid/server/cookie/CookieSyncService.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -382,14 +381,16 @@ private Set<String> biddersToSync(CookieSyncContext 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();
for (String bidder : allowedBiddersByPriority) {
final String cookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElseThrow();

cookieFamiliesToSync.add(cookieFamilyName);
biddersToSync.add(bidder);
if (cookieFamiliesToSync.size() < cookieSyncContext.getLimit()) {
cookieFamiliesToSync.add(cookieFamilyName);
biddersToSync.add(bidder);
} else if (cookieFamiliesToSync.contains(cookieFamilyName)) {
biddersToSync.add(bidder);
}
}

return biddersToSync;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,23 @@
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

public class PrioritizedCoopSyncProvider {

private static final Logger logger = LoggerFactory.getLogger(PrioritizedCoopSyncProvider.class);

private final Set<String> prioritizedBidders;
private final Map<String, String> prioritizedCookieFamilyNameToBidderName;
private final Set<String> prioritizedCookieFamilyNames;

public PrioritizedCoopSyncProvider(Set<String> bidders, BidderCatalog bidderCatalog) {
this.prioritizedBidders = validCoopSyncBidders(Objects.requireNonNull(bidders), bidderCatalog);
this.prioritizedCookieFamilyNameToBidderName = prioritizedBidders.stream()
.collect(Collectors.toMap(
bidder -> bidderCatalog.cookieFamilyName(bidder).orElseThrow(),
Function.identity()));
this.prioritizedCookieFamilyNames = prioritizedBidders.stream()
.map(bidder -> bidderCatalog.cookieFamilyName(bidder).orElseThrow())
.collect(Collectors.toSet());
}

private static Set<String> validCoopSyncBidders(Set<String> bidders, BidderCatalog bidderCatalog) {
Expand Down Expand Up @@ -71,7 +68,6 @@ public Set<String> prioritizedBidders(Account account) {
}

public boolean isPrioritizedFamily(String cookieFamilyName) {
final String bidder = prioritizedCookieFamilyNameToBidderName.get(cookieFamilyName);
return prioritizedBidders.contains(bidder);
return prioritizedCookieFamilyNames.contains(cookieFamilyName);
}
}
109 changes: 62 additions & 47 deletions src/main/java/org/prebid/server/handler/SetuidHandler.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.ext.web.RoutingContext;
import lombok.Value;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.prebid.server.activity.Activity;
import org.prebid.server.activity.ComponentType;
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
Expand Down Expand Up @@ -54,19 +54,16 @@
import org.prebid.server.settings.model.AccountGdprConfig;
import org.prebid.server.settings.model.AccountPrivacyConfig;
import org.prebid.server.util.HttpUtil;
import org.prebid.server.util.StreamUtil;
import org.prebid.server.vertx.verticles.server.HttpEndpoint;
import org.prebid.server.vertx.verticles.server.application.ApplicationResource;

import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class SetuidHandler implements ApplicationResource {
Expand All @@ -89,7 +86,7 @@ public class SetuidHandler implements ApplicationResource {
private final AnalyticsReporterDelegator analyticsDelegator;
private final Metrics metrics;
private final TimeoutFactory timeoutFactory;
private final Map<String, Pair<String, UsersyncMethodType>> cookieNameToBidderAndSyncType;
private final Map<String, UsersyncConfig> cookieFamilyNameToUsersyncConfig;

public SetuidHandler(long defaultTimeout,
UidsCookieService uidsCookieService,
Expand All @@ -113,49 +110,58 @@ public SetuidHandler(long defaultTimeout,
this.analyticsDelegator = Objects.requireNonNull(analyticsDelegator);
this.metrics = Objects.requireNonNull(metrics);
this.timeoutFactory = Objects.requireNonNull(timeoutFactory);
this.cookieNameToBidderAndSyncType = collectUsersyncers(bidderCatalog);
this.cookieFamilyNameToUsersyncConfig = collectUserSyncConfigs(bidderCatalog);
}

private static Map<String, Pair<String, UsersyncMethodType>> collectUsersyncers(BidderCatalog bidderCatalog) {
validateUsersyncersDuplicates(bidderCatalog);

private static Map<String, UsersyncConfig> collectUserSyncConfigs(BidderCatalog bidderCatalog) {
return bidderCatalog.usersyncReadyBidders().stream()
.sorted(Comparator.comparing(bidderName -> BooleanUtils.toInteger(bidderCatalog.isAlias(bidderName))))
.filter(StreamUtil.distinctBy(bidderCatalog::cookieFamilyName))
.map(bidderName -> bidderCatalog.usersyncerByName(bidderName)
.map(usersyncer -> Pair.of(bidderName, usersyncer)))
.flatMap(Optional::stream)
.collect(Collectors.toMap(
pair -> pair.getRight().getCookieFamilyName(),
pair -> Pair.of(pair.getLeft(), preferredUserSyncType(pair.getRight()))));
.collect(Collectors.groupingBy(
bidderName -> bidderCatalog.cookieFamilyName(bidderName).orElseThrow(),
Collectors.collectingAndThen(Collectors.toSet(), bidderNames ->
usersyncConfigForSingleCookieFamilyName(bidderNames, bidderCatalog))));
}

private static void validateUsersyncersDuplicates(BidderCatalog bidderCatalog) {
final List<String> duplicatedCookieFamilyNames = bidderCatalog.usersyncReadyBidders().stream()
.filter(bidderName -> !isAliasWithRootCookieFamilyName(bidderCatalog, bidderName))
private static UsersyncConfig usersyncConfigForSingleCookieFamilyName(Set<String> bidderNames,
BidderCatalog bidderCatalog) {

final Set<String> biddersWithoutAliases = bidderNames.stream()
.filter(bidderName -> bidderCatalog.aliasOf(bidderName).filter(bidderNames::contains).isEmpty())
.collect(Collectors.toSet());

validateBiddersHaveTheSameVendorId(biddersWithoutAliases, bidderCatalog);
validateBiddersHaveTheSameUsersyncConfig(biddersWithoutAliases, bidderCatalog);

final Usersyncer usersyncer = biddersWithoutAliases.stream()
.map(bidderCatalog::usersyncerByName)
.flatMap(Optional::stream)
.map(Usersyncer::getCookieFamilyName)
.filter(Predicate.not(StreamUtil.distinctBy(Function.identity())))
.distinct()
.sorted()
.toList();

if (!duplicatedCookieFamilyNames.isEmpty()) {
.map(Optional::orElseThrow)
.findAny().orElseThrow();

return UsersyncConfig.of(biddersWithoutAliases, preferredUserSyncType(usersyncer));
}

private static void validateBiddersHaveTheSameVendorId(Set<String> bidders, BidderCatalog bidderCatalog) {
final Set<Integer> vendorIds = bidders.stream()
.map(bidderCatalog::vendorIdByName)
.collect(Collectors.toSet());

if (vendorIds.size() > 1) {
throw new IllegalArgumentException(
"Duplicated \"cookie-family-name\" found, values: "
+ String.join(", ", duplicatedCookieFamilyNames));
"Found bidders with the same cookie family name but different vendor ids. "
+ "Bidders: %s. Vendor ids: %s".formatted(bidders, vendorIds));
}
}

private static boolean isAliasWithRootCookieFamilyName(BidderCatalog bidderCatalog, String bidder) {
final String bidderCookieFamilyName = bidderCatalog.cookieFamilyName(bidder).orElse(StringUtils.EMPTY);
final String parentCookieFamilyName =
bidderCatalog.cookieFamilyName(bidderCatalog.resolveBaseBidder(bidder)).orElse(null);
private static void validateBiddersHaveTheSameUsersyncConfig(Set<String> bidders, BidderCatalog bidderCatalog) {
final Set<Usersyncer> usersyncers = bidders.stream()
.map(bidderCatalog::usersyncerByName)
.map(Optional::orElseThrow)
.collect(Collectors.toSet());

return bidderCatalog.isAlias(bidder)
&& parentCookieFamilyName != null
&& parentCookieFamilyName.equals(bidderCookieFamilyName);
if (usersyncers.size() > 1) {
throw new IllegalArgumentException(
"Found bidders with the same cookie family name but different usersync configs. "
+ "Bidders: %s. Usersync configs: %s".formatted(bidders, usersyncers));
}
}

private static UsersyncMethodType preferredUserSyncType(Usersyncer usersyncer) {
Expand All @@ -181,8 +187,8 @@ private Future<SetuidContext> toSetuidContext(RoutingContext routingContext) {
final Timeout timeout = timeoutFactory.create(defaultTimeout);

final UsersyncMethodType syncType = Optional.ofNullable(cookieName)
.map(cookieNameToBidderAndSyncType::get)
.map(Pair::getRight)
.map(cookieFamilyNameToUsersyncConfig::get)
.map(UsersyncConfig::getUsersyncMethodType)
.orElse(null);

return accountById(requestAccount, timeout)
Expand Down Expand Up @@ -236,15 +242,14 @@ private void handleSetuidContextResult(AsyncResult<SetuidContext> setuidContextR
final AccountPrivacyConfig privacyConfig = setuidContext.getAccount().getPrivacy();
final AccountGdprConfig accountGdprConfig = privacyConfig != null ? privacyConfig.getGdpr() : null;

final String bidderName = cookieNameToBidderAndSyncType.get(bidderCookieFamily).getLeft();
final Set<String> bidderNames = cookieFamilyNameToUsersyncConfig.get(bidderCookieFamily).getBidders();

Future.all(
tcfDefinerService.isAllowedForHostVendorId(tcfContext),
tcfDefinerService.resultForBidderNames(
Collections.singleton(bidderName), tcfContext, accountGdprConfig))
tcfDefinerService.resultForBidderNames(bidderNames, tcfContext, accountGdprConfig))
.onComplete(hostTcfResponseResult -> respondByTcfResponse(
hostTcfResponseResult,
bidderName,
bidderNames,
setuidContext));
} else {
final Throwable error = setuidContextResult.cause();
Expand All @@ -255,7 +260,7 @@ private void handleSetuidContextResult(AsyncResult<SetuidContext> setuidContextR
private void validateSetuidContext(SetuidContext setuidContext, String bidderCookieFamily) {
final String cookieName = setuidContext.getCookieName();
final boolean isCookieNameBlank = StringUtils.isBlank(cookieName);
if (isCookieNameBlank || !cookieNameToBidderAndSyncType.containsKey(cookieName)) {
if (isCookieNameBlank || !cookieFamilyNameToUsersyncConfig.containsKey(cookieName)) {
final String cookieNameError = isCookieNameBlank ? "required" : "invalid";
throw new InvalidRequestException("\"bidder\" query param is " + cookieNameError);
}
Expand All @@ -282,7 +287,7 @@ private void validateSetuidContext(SetuidContext setuidContext, String bidderCoo
}

private void respondByTcfResponse(AsyncResult<CompositeFuture> hostTcfResponseResult,
String bidderName,
Set<String> bidderNames,
SetuidContext setuidContext) {

final TcfContext tcfContext = setuidContext.getPrivacyContext().getTcfContext();
Expand All @@ -293,9 +298,11 @@ private void respondByTcfResponse(AsyncResult<CompositeFuture> hostTcfResponseRe
final HostVendorTcfResponse hostVendorTcfResponse = compositeFuture.resultAt(0);
final TcfResponse<String> bidderTcfResponse = compositeFuture.resultAt(1);

// bidders corresponding to the same cookie family name should have the same vendor id
// so we can use any one of them here
final Map<String, PrivacyEnforcementAction> vendorIdToAction = bidderTcfResponse.getActions();
final PrivacyEnforcementAction action = vendorIdToAction != null
? vendorIdToAction.get(bidderName)
? vendorIdToAction.get(bidderNames.iterator().next())
: null;

final boolean notInGdprScope = BooleanUtils.isFalse(bidderTcfResponse.getUserInGdprScope());
Expand Down Expand Up @@ -416,4 +423,12 @@ private void handleErrors(Throwable error, RoutingContext routingContext, TcfCon
private void addCookie(RoutingContext routingContext, Cookie cookie) {
routingContext.response().headers().add(HttpUtil.SET_COOKIE_HEADER, cookie.encode());
}

@Value(staticConstructor = "of")
private static class UsersyncConfig {

Set<String> bidders;

UsersyncMethodType usersyncMethodType;
}
}
Loading
Loading