Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Privacy: Fix GDPR being ignored when in COPPA scope #3565

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
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,26 @@
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) {
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 @@ -48,8 +45,8 @@ public CcpaEnforcement(UserFpdCcpaMask userFpdCcpaMask,
}

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 +55,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 +65,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 +80,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 +112,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,33 @@ public CoppaEnforcement(UserFpdCoppaMask userFpdCoppaMask, Metrics metrics) {
this.metrics = Objects.requireNonNull(metrics);
}

public boolean isApplicable(AuctionContext auctionContext) {
return auctionContext.getPrivacyContext().getPrivacy().getCoppa() == 1;
}
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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
import org.prebid.server.settings.model.AccountPrivacyConfig;
import org.prebid.server.util.ObjectUtil;

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

public class TcfEnforcement {
public class TcfEnforcement implements PrivacyEnforcement {

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

Expand Down Expand Up @@ -60,14 +63,19 @@ public Future<Map<Integer, PrivacyEnforcementAction>> enforce(Set<Integer> vendo
}

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

final Device device = auctionContext.getBidRequest().getDevice();
final AccountGdprConfig accountGdprConfig = accountGdprConfig(auctionContext.getAccount());
final MetricName requestType = auctionContext.getRequestTypeMetric();
final ActivityInfrastructure activityInfrastructure = auctionContext.getActivityInfrastructure();
final Set<String> bidders = results.stream()
.map(BidderPrivacyResult::getRequestBidder)
.collect(Collectors.toSet());
final Map<String, User> bidderToUser = results.stream()
.map(result -> new AbstractMap.SimpleEntry<>(result.getRequestBidder(), result.getUser()))
.collect(HashMap::new, (map, entry) -> map.put(entry.getKey(), entry.getValue()), HashMap::putAll);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

        final Map<String, User> bidderToUser = results.stream()
                .collect(Collectors.toMap(BidderPrivacyResult::getRequestBidder, BidderPrivacyResult::getUser));
        final Set<String> bidders = bidderToUser.keySet();


return tcfDefinerService.resultForBidderNames(
bidders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,9 @@
import org.prebid.server.auction.privacy.contextfactory.AuctionPrivacyContextFactory;
import org.prebid.server.auction.privacy.contextfactory.CookieSyncPrivacyContextFactory;
import org.prebid.server.auction.privacy.contextfactory.SetuidPrivacyContextFactory;
import org.prebid.server.auction.privacy.enforcement.ActivityEnforcement;
import org.prebid.server.auction.privacy.enforcement.CcpaEnforcement;
import org.prebid.server.auction.privacy.enforcement.CoppaEnforcement;
import org.prebid.server.auction.privacy.enforcement.PrivacyEnforcement;
import org.prebid.server.auction.privacy.enforcement.PrivacyEnforcementService;
import org.prebid.server.auction.privacy.enforcement.TcfEnforcement;
import org.prebid.server.auction.requestfactory.AmpRequestFactory;
import org.prebid.server.auction.requestfactory.AuctionRequestFactory;
import org.prebid.server.auction.requestfactory.Ortb2ImplicitParametersResolver;
Expand Down Expand Up @@ -933,16 +931,8 @@ StoredResponseProcessor storedResponseProcessor(ApplicationSettings applicationS
}

@Bean
PrivacyEnforcementService privacyEnforcementService(CoppaEnforcement coppaEnforcement,
CcpaEnforcement ccpaEnforcement,
TcfEnforcement tcfEnforcement,
ActivityEnforcement activityEnforcement) {

return new PrivacyEnforcementService(
coppaEnforcement,
ccpaEnforcement,
tcfEnforcement,
activityEnforcement);
PrivacyEnforcementService privacyEnforcementService(List<PrivacyEnforcement> enforcements) {
return new PrivacyEnforcementService(enforcements);
}

@Bean
Expand Down
Loading
Loading