Skip to content

Commit

Permalink
Privacy: Fix GDPR being ignored when in COPPA scope (#3565)
Browse files Browse the repository at this point in the history
  • Loading branch information
And1sS authored Dec 4, 2024
1 parent b3795a0 commit 7b3fd70
Show file tree
Hide file tree
Showing 16 changed files with 640 additions and 317 deletions.
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

0 comments on commit 7b3fd70

Please sign in to comment.