Skip to content
Merged
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
81 changes: 81 additions & 0 deletions src/main/java/org/prebid/server/auction/EidPermissionResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.prebid.server.auction;

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class EidPermissionResolver {

private static final String WILDCARD_BIDDER = "*";

private static final ExtRequestPrebidDataEidPermissions DEFAULT_RULE = ExtRequestPrebidDataEidPermissions.builder()
.bidders(Collections.singletonList(WILDCARD_BIDDER))
.build();

private static final EidPermissionResolver EMPTY = new EidPermissionResolver(Collections.emptyList());

private final List<ExtRequestPrebidDataEidPermissions> eidPermissions;

private EidPermissionResolver(List<ExtRequestPrebidDataEidPermissions> eidPermissions) {
this.eidPermissions = new ArrayList<>(eidPermissions);
this.eidPermissions.add(DEFAULT_RULE);
}

public static EidPermissionResolver of(List<ExtRequestPrebidDataEidPermissions> eidPermissions) {
return new EidPermissionResolver(eidPermissions);
}

public static EidPermissionResolver empty() {
return EMPTY;
}

public List<Eid> resolveAllowedEids(List<Eid> userEids, String bidder) {
return CollectionUtils.emptyIfNull(userEids)
.stream()
.filter(userEid -> isAllowed(userEid, bidder))
.toList();
}

private boolean isAllowed(Eid eid, String bidder) {
final Map<Integer, List<ExtRequestPrebidDataEidPermissions>> matchingRulesBySpecificity = eidPermissions
.stream()
.filter(rule -> isRuleMatched(eid, rule))
.collect(Collectors.groupingBy(this::getRuleSpecificity));

final int highestSpecificityMatchingRules = Collections.max(matchingRulesBySpecificity.keySet());
return matchingRulesBySpecificity.get(highestSpecificityMatchingRules).stream()
.anyMatch(eidPermission -> isBidderAllowed(bidder, eidPermission.getBidders()));
}

private int getRuleSpecificity(ExtRequestPrebidDataEidPermissions eidPermission) {
return (int) Stream.of(
eidPermission.getInserter(),
eidPermission.getSource(),
eidPermission.getMatcher(),
eidPermission.getMm())
.filter(Objects::nonNull)
.count();
}

private boolean isRuleMatched(Eid eid, ExtRequestPrebidDataEidPermissions eidPermission) {
return (eidPermission.getInserter() == null || eidPermission.getInserter().equals(eid.getInserter()))
&& (eidPermission.getSource() == null || eidPermission.getSource().equals(eid.getSource()))
&& (eidPermission.getMatcher() == null || eidPermission.getMatcher().equals(eid.getMatcher()))
&& (eidPermission.getMm() == null || eidPermission.getMm().equals(eid.getMm()));
}

private boolean isBidderAllowed(String bidder, List<String> ruleBidders) {
return ruleBidders == null || ruleBidders.stream()
.anyMatch(allowedBidder -> StringUtils.equalsIgnoreCase(allowedBidder, bidder)
|| WILDCARD_BIDDER.equals(allowedBidder));
}
}
51 changes: 17 additions & 34 deletions src/main/java/org/prebid/server/auction/ExchangeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,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 @@ -129,7 +128,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 @@ -538,9 +536,9 @@ private Future<List<AuctionParticipation>> makeAuctionParticipation(
final ExtRequest requestExt = bidRequest.getExt();
final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null;
final Map<String, ExtBidderConfigOrtb> biddersToConfigs = getBiddersToConfigs(prebid);
final Map<String, List<String>> eidPermissions = getEidPermissions(prebid);
final EidPermissionResolver eidPermissionResolver = getEidPermissions(prebid);
final Map<String, Pair<User, Device>> bidderToUserAndDevice =
prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissions);
prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissionResolver);

return privacyEnforcementService.mask(context, bidderToUserAndDevice, aliases)
.map(bidderToPrivacyResult -> getAuctionParticipation(
Expand Down Expand Up @@ -578,14 +576,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 EidPermissionResolver getEidPermissions(ExtRequestPrebid prebid) {
return Optional.ofNullable(prebid)
.map(ExtRequestPrebid::getData)
.map(ExtRequestPrebidData::getEidPermissions)
.map(EidPermissionResolver::of)
.orElse(EidPermissionResolver.empty());
}

private static List<String> firstPartyDataBidders(ExtRequest requestExt) {
Expand All @@ -594,11 +590,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,
EidPermissionResolver eidPermissionResolver) {

final BidRequest bidRequest = context.getBidRequest();
final List<String> firstPartyDataBidders = firstPartyDataBidders(bidRequest.getExt());
Expand All @@ -610,7 +607,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, eidPermissionResolver);
final Device preparedDevice = prepareDevice(
bidRequest.getDevice(), fpdConfig, useFirstPartyData);
bidderToUserAndDevice.put(bidder, Pair.of(preparedUser, preparedDevice));
Expand All @@ -623,13 +620,13 @@ private User prepareUser(String bidder,
BidderAliases aliases,
boolean useFirstPartyData,
ExtBidderConfigOrtb fpdConfig,
Map<String, List<String>> eidPermissions) {
EidPermissionResolver eidPermissionResolver) {

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 = eidPermissionResolver.resolveAllowedEids(userEids, bidder);
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 @@ -669,20 +666,6 @@ 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) {
return CollectionUtils.emptyIfNull(userEids)
.stream()
.filter(userEid -> isUserEidAllowed(userEid.getSource(), eidPermissions, 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 List<AuctionParticipation> getAuctionParticipation(
List<BidderPrivacyResult> bidderPrivacyResults,
BidRequest bidRequest,
Expand Down
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);
}
}
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 (CollectionUtils.isEmpty(eidPermissions)) {
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());

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.isAllEmpty(inserter, source, matcher) && mm == null) {
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
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,14 @@ class Eid {
it.matchMethod = PBSUtils.randomNumber
}
}

static Eid from(EidPermission eidPermission, List<Uid> uids = [Uid.defaultUid]) {
new Eid().tap {
it.source = eidPermission.source
it.uids = uids
it.inserter = eidPermission.inserter
it.matcher = eidPermission.matcher
it.matchMethod = eidPermission.matchMethod
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,39 @@
package org.prebid.server.functional.model.request.auction

import com.fasterxml.jackson.annotation.JsonProperty
import groovy.transform.ToString
import org.prebid.server.functional.model.bidder.BidderName
import org.prebid.server.functional.util.PBSUtils

import static org.prebid.server.functional.model.bidder.BidderName.GENERIC

@ToString(includeNames = true, ignoreNulls = true)
class EidPermission {

String source
List<BidderName> bidders
String inserter
String matcher
@JsonProperty("mm")
Integer matchMethod
List<BidderName> bidders = [GENERIC]

static EidPermission getDefaultEidPermission(List<BidderName> bidders = [GENERIC]) {
new EidPermission().tap {
it.source = PBSUtils.randomString
it.inserter = PBSUtils.randomString
it.matcher = PBSUtils.randomString
it.matchMethod = PBSUtils.randomNumber
it.bidders = bidders
}
}

static EidPermission from(Eid eid, List<BidderName> bidders = [GENERIC]) {
new EidPermission().tap {
it.source = eid.source
it.inserter = eid.inserter
it.matcher = eid.matcher
it.matchMethod = eid.matchMethod
it.bidders = bidders
}
}
}
Loading
Loading