Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
56 changes: 28 additions & 28 deletions src/main/java/org/prebid/server/auction/ExchangeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import org.prebid.server.auction.model.BidderPrivacyResult;
import org.prebid.server.auction.model.BidderRequest;
import org.prebid.server.auction.model.BidderResponse;
import org.prebid.server.auction.model.EidPermissionIndex;
import org.prebid.server.auction.model.MultiBidConfig;
import org.prebid.server.auction.model.StoredResponseResult;
import org.prebid.server.auction.model.TimeoutContext;
Expand Down Expand Up @@ -88,7 +89,6 @@
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidBidderConfig;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidCache;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidData;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidMultiBid;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSchain;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting;
Expand Down Expand Up @@ -131,7 +131,6 @@ public class ExchangeService {
private static final String ALL_BIDDERS_CONFIG = "*";
private static final Integer DEFAULT_MULTIBID_LIMIT_MIN = 1;
private static final Integer DEFAULT_MULTIBID_LIMIT_MAX = 9;
private static final String EID_ALLOWED_FOR_ALL_BIDDERS = "*";
private static final BigDecimal THOUSAND = BigDecimal.valueOf(1000);
private static final Set<String> BIDDER_FIELDS_EXCEPTION_LIST = Set.of(
"adunitcode", "storedrequest", "options", "is_rewarded_inventory");
Expand Down Expand Up @@ -435,7 +434,7 @@ private void removeInvalidBidRejectionTrackers(Map<String, BidRejectionTracker>
BidderAliases aliases) {

final Set<String> bidderNames = new HashSet<>(bidRejectionTrackers.keySet());
for (String bidder: bidderNames) {
for (String bidder : bidderNames) {
if (!isValidBidder(bidder, aliases)) {
bidRejectionTrackers.remove(bidder);
logger.warn("Pre-rejected impressions of the bidder {} have been removed. "
Expand Down Expand Up @@ -541,9 +540,9 @@ private Future<List<AuctionParticipation>> makeAuctionParticipation(
final ExtRequest requestExt = bidRequest.getExt();
final ExtRequestPrebid prebid = requestExt == null ? null : requestExt.getPrebid();
final Map<String, ExtBidderConfigOrtb> biddersToConfigs = getBiddersToConfigs(prebid);
final Map<String, List<String>> eidPermissions = getEidPermissions(prebid);
final EidPermissionIndex eidPermissionIndex = getEidPermissions(prebid);
final Map<String, Pair<User, Device>> bidderToUserAndDevice =
prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissions);
prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissionIndex);

return privacyEnforcementService.mask(context, bidderToUserAndDevice, aliases)
.map(bidderToPrivacyResult -> getAuctionParticipation(
Expand Down Expand Up @@ -581,14 +580,12 @@ private Map<String, ExtBidderConfigOrtb> getBiddersToConfigs(ExtRequestPrebid pr
return bidderToConfig;
}

private Map<String, List<String>> getEidPermissions(ExtRequestPrebid prebid) {
final ExtRequestPrebidData prebidData = prebid != null ? prebid.getData() : null;
final List<ExtRequestPrebidDataEidPermissions> eidPermissions = prebidData != null
? prebidData.getEidPermissions()
: null;
return CollectionUtils.emptyIfNull(eidPermissions).stream()
.collect(Collectors.toMap(ExtRequestPrebidDataEidPermissions::getSource,
ExtRequestPrebidDataEidPermissions::getBidders));
private EidPermissionIndex getEidPermissions(ExtRequestPrebid prebid) {
return Optional.ofNullable(prebid)
.map(ExtRequestPrebid::getData)
.map(ExtRequestPrebidData::getEidPermissions)
.map(EidPermissionIndex::build)
.orElse(null);
}

private static List<String> firstPartyDataBidders(ExtRequest requestExt) {
Expand All @@ -597,11 +594,12 @@ private static List<String> firstPartyDataBidders(ExtRequest requestExt) {
return data == null ? null : data.getBidders();
}

private Map<String, Pair<User, Device>> prepareUsersAndDevices(List<String> bidders,
AuctionContext context,
BidderAliases aliases,
Map<String, ExtBidderConfigOrtb> biddersToConfigs,
Map<String, List<String>> eidPermissions) {
private Map<String, Pair<User, Device>> prepareUsersAndDevices(
List<String> bidders,
AuctionContext context,
BidderAliases aliases,
Map<String, ExtBidderConfigOrtb> biddersToConfigs,
EidPermissionIndex eidPermissionIndex) {

final BidRequest bidRequest = context.getBidRequest();
final List<String> firstPartyDataBidders = firstPartyDataBidders(bidRequest.getExt());
Expand All @@ -613,7 +611,7 @@ private Map<String, Pair<User, Device>> prepareUsersAndDevices(List<String> bidd
final boolean useFirstPartyData = firstPartyDataBidders == null || firstPartyDataBidders.stream()
.anyMatch(fpdBidder -> StringUtils.equalsIgnoreCase(fpdBidder, bidder));
final User preparedUser = prepareUser(
bidder, context, aliases, useFirstPartyData, fpdConfig, eidPermissions);
bidder, context, aliases, useFirstPartyData, fpdConfig, eidPermissionIndex);
final Device preparedDevice = prepareDevice(
bidRequest.getDevice(), fpdConfig, useFirstPartyData);
bidderToUserAndDevice.put(bidder, Pair.of(preparedUser, preparedDevice));
Expand All @@ -626,13 +624,13 @@ private User prepareUser(String bidder,
BidderAliases aliases,
boolean useFirstPartyData,
ExtBidderConfigOrtb fpdConfig,
Map<String, List<String>> eidPermissions) {
EidPermissionIndex eidPermissionIndex) {

final User user = context.getBidRequest().getUser();
final ExtUser extUser = user != null ? user.getExt() : null;
final UpdateResult<String> buyerUidUpdateResult = uidUpdater.updateUid(bidder, context, aliases);
final List<Eid> userEids = extractUserEids(user);
final List<Eid> allowedUserEids = resolveAllowedEids(userEids, bidder, eidPermissions);
final List<Eid> allowedUserEids = resolveAllowedEids(userEids, bidder, eidPermissionIndex);
final boolean shouldUpdateUserEids = allowedUserEids.size() != CollectionUtils.emptyIfNull(userEids).size();
final boolean shouldCleanExtPrebid = extUser != null && extUser.getPrebid() != null;
final boolean shouldCleanExtData = extUser != null && extUser.getData() != null && !useFirstPartyData;
Expand Down Expand Up @@ -672,18 +670,20 @@ private List<Eid> extractUserEids(User user) {
return user != null ? user.getEids() : null;
}

private List<Eid> resolveAllowedEids(List<Eid> userEids, String bidder, Map<String, List<String>> eidPermissions) {
private List<Eid> resolveAllowedEids(List<Eid> userEids, String bidder, EidPermissionIndex eidPermissionIndex) {
return CollectionUtils.emptyIfNull(userEids)
.stream()
.filter(userEid -> isUserEidAllowed(userEid.getSource(), eidPermissions, bidder))
.filter(userEid -> isUserEidAllowed(userEid, eidPermissionIndex, bidder))
.toList();
}

private boolean isUserEidAllowed(String source, Map<String, List<String>> eidPermissions, String bidder) {
final List<String> allowedBidders = eidPermissions.get(source);
return CollectionUtils.isEmpty(allowedBidders) || allowedBidders.stream()
.anyMatch(allowedBidder -> StringUtils.equalsIgnoreCase(allowedBidder, bidder)
|| EID_ALLOWED_FOR_ALL_BIDDERS.equals(allowedBidder));
private boolean isUserEidAllowed(Eid eid,
EidPermissionIndex eidPermissionIndex,
String bidder) {
if (eidPermissionIndex == null) {
return true;
Comment thread
And1sS marked this conversation as resolved.
Outdated
}
return eidPermissionIndex.isAllowed(eid, bidder);
}

private List<AuctionParticipation> getAuctionParticipation(
Expand Down
118 changes: 118 additions & 0 deletions src/main/java/org/prebid/server/auction/model/EidPermissionIndex.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.prebid.server.auction.model;

import com.iab.openrtb.request.Eid;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public final class EidPermissionIndex {

// bitmask for which fields are present in a permission
private static final int INSERTER = 1; // 0001
Comment thread
Net-burst marked this conversation as resolved.
Outdated
private static final int SOURCE = 2; // 0010
private static final int MATCHER = 4; // 0100
private static final int MM = 8; // 1000
Comment thread
Net-burst marked this conversation as resolved.
Outdated
private static final String WILDCARD_BIDDER = "*";

private final Map<Integer, Map<Key, Set<String>>> ruleIndexByMask;

private record Key(String inserter, String source, String matcher, Integer mm) {
}

private EidPermissionIndex(Map<Integer, Map<Key, Set<String>>> ruleIndexByMask) {
this.ruleIndexByMask = ruleIndexByMask;
}

public static EidPermissionIndex build(List<ExtRequestPrebidDataEidPermissions> permissions) {
if (ObjectUtils.isEmpty(permissions)) {
return null;
}

final Map<Integer, Map<Key, Set<String>>> idx = new HashMap<>();

for (ExtRequestPrebidDataEidPermissions permission : permissions) {
final List<String> bidders = CollectionUtils.emptyIfNull(permission.getBidders())
.stream()
.filter(StringUtils::isNotBlank)
.map(String::toLowerCase)
.toList();

if (bidders.isEmpty()) {
continue;
}

final int ruleMask = maskOf(permission.getInserter(),
permission.getSource(),
permission.getMatcher(),
permission.getMm());
final Key ruleKey = new Key(permission.getInserter(),
permission.getSource(),
permission.getMatcher(),
permission.getMm());

idx.computeIfAbsent(ruleMask, ignored -> new HashMap<>())
.computeIfAbsent(ruleKey, ignored -> new HashSet<>())
.addAll(bidders);
}

return new EidPermissionIndex(idx);
}

private static int maskOf(String inserter, String source, String matcher, Integer mm) {
int mask = 0;

if (inserter != null) {
mask |= INSERTER;
}
if (source != null) {
mask |= SOURCE;
}
if (matcher != null) {
mask |= MATCHER;
}
if (mm != null) {
mask |= MM;
}

return mask;
Comment thread
Net-burst marked this conversation as resolved.
Outdated
}

public boolean isAllowed(Eid eid, String bidder) {
final int eidMask = maskOf(eid.getInserter(), eid.getSource(), eid.getMatcher(), eid.getMm());

boolean ruleMatched = false;

// Check every permission bucket whose criteria fields are a subset of the Eid’s populated fields
for (Map.Entry<Integer, Map<Key, Set<String>>> ruleBucket : ruleIndexByMask.entrySet()) {
final int ruleMask = ruleBucket.getKey();

// rule can only match if all its required fields exist on the Eid
if ((ruleMask & eidMask) != ruleMask) {
continue;
}

final Key normalizedEidKey = new Key((ruleMask & INSERTER) != 0 ? eid.getInserter() : null,
(ruleMask & SOURCE) != 0 ? eid.getSource() : null,
(ruleMask & MATCHER) != 0 ? eid.getMatcher() : null,
(ruleMask & MM) != 0 ? eid.getMm() : null);

final Set<String> allowedBidders = ruleBucket.getValue().get(normalizedEidKey);
if (allowedBidders != null) {
ruleMatched = true;
if (allowedBidders.contains(WILDCARD_BIDDER) || allowedBidders.contains(bidder.toLowerCase())) {
return true;
}
}
}

// allow-by-default: if no rule matched at all, allow
return !ruleMatched;
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,43 @@
package org.prebid.server.proto.openrtb.ext.request;

import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Builder;
import lombok.Value;

import java.util.List;

/**
* Defines the contract for bidrequest.ext.prebid.data.eidPermissions
*/
@Value(staticConstructor = "of")
@Value
@Builder
public class ExtRequestPrebidDataEidPermissions {

/**
* Defines the contract for bidrequest.ext.prebid.data.eidPermissions.inserter
*/
String inserter;

/**
* Defines the contract for bidrequest.ext.prebid.data.eidPermissions.source
*/
String source;

/**
* Defines the contract for bidrequest.ext.prebid.data.eidPermissions.matcher
*/
String matcher;

/**
* Defines the contract for bidrequest.ext.prebid.data.eidPermissions.mm
*/
Integer mm;

/**
* Defines the contract for bidrequest.ext.prebid.data.eidPermissions.bidders
*/
@JsonFormat(without = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
List<String> bidders;

@Deprecated
public static ExtRequestPrebidDataEidPermissions of(String source, List<String> bidders) {
return new ExtRequestPrebidDataEidPermissions(null, source, null, null, bidders);
}
Comment thread
And1sS marked this conversation as resolved.
}
20 changes: 15 additions & 5 deletions src/main/java/org/prebid/server/validation/RequestValidator.java
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ private void validateEidPermissions(List<ExtRequestPrebidDataEidPermissions> eid
boolean isDebugEnabled,
List<String> warnings) throws ValidationException {

if (eidPermissions == null) {
if (ObjectUtils.isEmpty(eidPermissions)) {
Comment thread
And1sS marked this conversation as resolved.
Outdated
return;
}

Expand All @@ -385,14 +385,24 @@ private void validateEidPermissions(List<ExtRequestPrebidDataEidPermissions> eid
throw new ValidationException("request.ext.prebid.data.eidpermissions[i] can't be null");
}

validateEidPermissionSource(eidPermission.getSource());
validateEidPermissionCriteria(eidPermission.getInserter(),
eidPermission.getSource(),
eidPermission.getMatcher(),
eidPermission.getMm());
Comment thread
And1sS marked this conversation as resolved.
validateEidPermissionBidders(eidPermission.getBidders(), aliases, isDebugEnabled, warnings);
}
}

private void validateEidPermissionSource(String source) throws ValidationException {
if (StringUtils.isEmpty(source)) {
throw new ValidationException("Missing required value request.ext.prebid.data.eidPermissions[].source");
private void validateEidPermissionCriteria(String inserter,
String source,
String matcher,
Integer mm) throws ValidationException {
if (StringUtils.isEmpty(inserter)
Comment thread
And1sS marked this conversation as resolved.
Outdated
&& StringUtils.isEmpty(source)
&& StringUtils.isEmpty(matcher)
&& mm == null) {
Comment thread
And1sS marked this conversation as resolved.
Outdated
throw new ValidationException("Missing required parameter(s) in request.ext.prebid.data.eidPermissions[]. "
+ "Either one or a combination of inserter, source, matcher, or mm should be defined.");
}
}

Expand Down
Loading
Loading