Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload;
import org.prebid.server.activity.infrastructure.payload.impl.ActivityInvocationPayloadImpl;
import org.prebid.server.activity.infrastructure.payload.impl.PrivacyEnforcementServiceActivityInvocationPayload;
import org.prebid.server.auction.BidderAliases;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask;
Expand All @@ -21,25 +22,27 @@
import java.util.Objects;
import java.util.Optional;

public class ActivityEnforcement {
public class ActivityEnforcement implements PrivacyEnforcement {

private final UserFpdActivityMask userFpdActivityMask;

public ActivityEnforcement(UserFpdActivityMask userFpdActivityMask) {
this.userFpdActivityMask = Objects.requireNonNull(userFpdActivityMask);
}

public Future<List<BidderPrivacyResult>> enforce(List<BidderPrivacyResult> bidderPrivacyResults,
AuctionContext auctionContext) {
@Override
public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
BidderAliases aliases,
List<BidderPrivacyResult> results) {

final List<BidderPrivacyResult> results = bidderPrivacyResults.stream()
final List<BidderPrivacyResult> enforcedResults = results.stream()
.map(bidderPrivacyResult -> applyActivityRestrictions(
bidderPrivacyResult,
auctionContext.getActivityInfrastructure(),
auctionContext.getBidRequest()))
.toList();

return Future.succeededFuture(results);
return Future.succeededFuture(enforcedResults);
}

private BidderPrivacyResult applyActivityRestrictions(BidderPrivacyResult bidderPrivacyResult,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package org.prebid.server.auction.privacy.enforcement;

import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.User;
import io.vertx.core.Future;
import org.apache.commons.lang3.ObjectUtils;
import org.prebid.server.auction.BidderAliases;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;
Expand All @@ -22,12 +19,12 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

public class CcpaEnforcement {
public class CcpaEnforcement implements PrivacyEnforcement {

private static final String CATCH_ALL_BIDDERS = "*";

Expand All @@ -47,9 +44,10 @@ public CcpaEnforcement(UserFpdCcpaMask userFpdCcpaMask,
this.ccpaEnforce = ccpaEnforce;
}

@Override
public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
Map<String, User> bidderToUser,
BidderAliases aliases) {
BidderAliases aliases,
List<BidderPrivacyResult> results) {

final Ccpa ccpa = auctionContext.getPrivacyContext().getPrivacy().getCcpa();
final BidRequest bidRequest = auctionContext.getBidRequest();
Expand All @@ -58,7 +56,7 @@ public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
final boolean isCcpaEnabled = isCcpaEnabled(auctionContext.getAccount(), auctionContext.getRequestTypeMetric());

final Set<String> enforcedBidders = isCcpaEnabled && isCcpaEnforced
? extractCcpaEnforcedBidders(bidderToUser.keySet(), bidRequest, aliases)
? extractCcpaEnforcedBidders(results, bidRequest, aliases)
: Collections.emptySet();

metrics.updatePrivacyCcpaMetrics(
Expand All @@ -68,7 +66,11 @@ public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
isCcpaEnabled,
enforcedBidders);

return Future.succeededFuture(maskCcpa(bidderToUser, enforcedBidders, bidRequest.getDevice()));
final List<BidderPrivacyResult> enforcedResults = results.stream()
.map(result -> enforcedBidders.contains(result.getRequestBidder()) ? maskCcpa(result) : result)
.toList();

return Future.succeededFuture(enforcedResults);
}

public boolean isCcpaEnforced(Ccpa ccpa, Account account) {
Expand All @@ -79,19 +81,21 @@ private boolean isCcpaEnabled(Account account, MetricName requestType) {
final Optional<AccountCcpaConfig> accountCcpaConfig = Optional.ofNullable(account.getPrivacy())
.map(AccountPrivacyConfig::getCcpa);

return ObjectUtils.firstNonNull(
accountCcpaConfig
.map(AccountCcpaConfig::getEnabledForRequestType)
.map(enabledForRequestType -> enabledForRequestType.isEnabledFor(requestType))
.orElse(null),
accountCcpaConfig
.map(AccountCcpaConfig::getEnabled)
.orElse(null),
ccpaEnforce);
return accountCcpaConfig
.map(AccountCcpaConfig::getEnabledForRequestType)
.map(enabledForRequestType -> enabledForRequestType.isEnabledFor(requestType))
.or(() -> accountCcpaConfig.map(AccountCcpaConfig::getEnabled))
.orElse(ccpaEnforce);
}

private Set<String> extractCcpaEnforcedBidders(Set<String> bidders, BidRequest bidRequest, BidderAliases aliases) {
final Set<String> ccpaEnforcedBidders = new HashSet<>(bidders);
private Set<String> extractCcpaEnforcedBidders(List<BidderPrivacyResult> results,
BidRequest bidRequest,
BidderAliases aliases) {

final Set<String> ccpaEnforcedBidders = results.stream()
.map(BidderPrivacyResult::getRequestBidder)
.collect(Collectors.toCollection(HashSet::new));

final List<String> nosaleBidders = Optional.ofNullable(bidRequest.getExt())
.map(ExtRequest::getPrebid)
.map(ExtRequestPrebid::getNosale)
Expand All @@ -109,14 +113,11 @@ private Set<String> extractCcpaEnforcedBidders(Set<String> bidders, BidRequest b
return ccpaEnforcedBidders;
}

private List<BidderPrivacyResult> maskCcpa(Map<String, User> bidderToUser, Set<String> bidders, Device device) {
final Device maskedDevice = userFpdCcpaMask.maskDevice(device);
return bidders.stream()
.map(bidder -> BidderPrivacyResult.builder()
.requestBidder(bidder)
.user(userFpdCcpaMask.maskUser(bidderToUser.get(bidder)))
.device(maskedDevice)
.build())
.toList();
private BidderPrivacyResult maskCcpa(BidderPrivacyResult result) {
return BidderPrivacyResult.builder()
.requestBidder(result.getRequestBidder())
.user(userFpdCcpaMask.maskUser(result.getUser()))
.device(userFpdCcpaMask.maskDevice(result.getDevice()))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package org.prebid.server.auction.privacy.enforcement;

import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.User;
import io.vertx.core.Future;
import org.prebid.server.auction.BidderAliases;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdCoppaMask;
import org.prebid.server.metric.Metrics;

import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

public class CoppaEnforcement {
public class CoppaEnforcement implements PrivacyEnforcement {

private final UserFpdCoppaMask userFpdCoppaMask;
private final Metrics metrics;
Expand All @@ -22,23 +22,34 @@ public CoppaEnforcement(UserFpdCoppaMask userFpdCoppaMask, Metrics metrics) {
this.metrics = Objects.requireNonNull(metrics);
}

public boolean isApplicable(AuctionContext auctionContext) {
return auctionContext.getPrivacyContext().getPrivacy().getCoppa() == 1;
}
@Override
public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
BidderAliases aliases,
List<BidderPrivacyResult> results) {

if (!isApplicable(auctionContext)) {
return Future.succeededFuture(results);
}

final Set<String> bidders = results.stream()
.map(BidderPrivacyResult::getRequestBidder)
.collect(Collectors.toSet());

public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext, Map<String, User> bidderToUser) {
metrics.updatePrivacyCoppaMetric(auctionContext.getActivityInfrastructure(), bidderToUser.keySet());
return Future.succeededFuture(results(bidderToUser, auctionContext.getBidRequest().getDevice()));
metrics.updatePrivacyCoppaMetric(auctionContext.getActivityInfrastructure(), bidders);
return Future.succeededFuture(enforce(results));
}

private List<BidderPrivacyResult> results(Map<String, User> bidderToUser, Device device) {
final Device maskedDevice = userFpdCoppaMask.maskDevice(device);
return bidderToUser.entrySet().stream()
.map(bidderAndUser -> BidderPrivacyResult.builder()
.requestBidder(bidderAndUser.getKey())
.user(userFpdCoppaMask.maskUser(bidderAndUser.getValue()))
.device(maskedDevice)
private List<BidderPrivacyResult> enforce(List<BidderPrivacyResult> results) {
return results.stream()
.map(result -> BidderPrivacyResult.builder()
.requestBidder(result.getRequestBidder())
.user(userFpdCoppaMask.maskUser(result.getUser()))
.device(userFpdCoppaMask.maskDevice(result.getDevice()))
.build())
.toList();
}

private static boolean isApplicable(AuctionContext auctionContext) {
return auctionContext.getPrivacyContext().getPrivacy().getCoppa() == 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.prebid.server.auction.privacy.enforcement;

import io.vertx.core.Future;
import org.prebid.server.auction.BidderAliases;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;

import java.util.List;

public interface PrivacyEnforcement {

Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
BidderAliases aliases,
List<BidderPrivacyResult> results);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,58 +5,41 @@
import org.prebid.server.auction.BidderAliases;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;
import org.prebid.server.util.ListUtil;

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

/**
* Service provides masking for OpenRTB client sensitive information.
*/
public class PrivacyEnforcementService {

private final CoppaEnforcement coppaEnforcement;
private final CcpaEnforcement ccpaEnforcement;
private final TcfEnforcement tcfEnforcement;
private final ActivityEnforcement activityEnforcement;
private final List<PrivacyEnforcement> enforcements;

public PrivacyEnforcementService(CoppaEnforcement coppaEnforcement,
CcpaEnforcement ccpaEnforcement,
TcfEnforcement tcfEnforcement,
ActivityEnforcement activityEnforcement) {

this.coppaEnforcement = Objects.requireNonNull(coppaEnforcement);
this.ccpaEnforcement = Objects.requireNonNull(ccpaEnforcement);
this.tcfEnforcement = Objects.requireNonNull(tcfEnforcement);
this.activityEnforcement = Objects.requireNonNull(activityEnforcement);
public PrivacyEnforcementService(final List<PrivacyEnforcement> enforcements) {
this.enforcements = Objects.requireNonNull(enforcements);
}

public Future<List<BidderPrivacyResult>> mask(AuctionContext auctionContext,
Map<String, User> bidderToUser,
BidderAliases aliases) {

// For now, COPPA masking all values, so we can omit TCF masking.
return coppaEnforcement.isApplicable(auctionContext)
? coppaEnforcement.enforce(auctionContext, bidderToUser)
: ccpaEnforcement.enforce(auctionContext, bidderToUser, aliases)
.compose(ccpaResult -> tcfEnforcement.enforce(
auctionContext,
bidderToUser,
biddersToApplyTcf(bidderToUser.keySet(), ccpaResult),
aliases)
.map(tcfResult -> ListUtil.union(ccpaResult, tcfResult)))
.compose(bidderPrivacyResults -> activityEnforcement.enforce(bidderPrivacyResults, auctionContext));
}
final List<BidderPrivacyResult> initialResults = bidderToUser.entrySet().stream()
.map(entry -> BidderPrivacyResult.builder()
.requestBidder(entry.getKey())
.user(entry.getValue())
.device(auctionContext.getBidRequest().getDevice())
.build())
.toList();

Future<List<BidderPrivacyResult>> composedResult = Future.succeededFuture(initialResults);

private static Set<String> biddersToApplyTcf(Set<String> bidders, List<BidderPrivacyResult> ccpaResult) {
final Set<String> biddersToApplyTcf = new HashSet<>(bidders);
ccpaResult.stream()
.map(BidderPrivacyResult::getRequestBidder)
.forEach(biddersToApplyTcf::remove);
for (PrivacyEnforcement enforcement : enforcements) {
composedResult = composedResult.compose(
results -> enforcement.enforce(auctionContext, aliases, results));
}

return biddersToApplyTcf;
return composedResult;
}
}
Loading
Loading