Skip to content

Commit 7b3fd70

Browse files
authored
Privacy: Fix GDPR being ignored when in COPPA scope (#3565)
1 parent b3795a0 commit 7b3fd70

File tree

16 files changed

+640
-317
lines changed

16 files changed

+640
-317
lines changed

src/main/java/org/prebid/server/auction/privacy/enforcement/ActivityEnforcement.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import org.prebid.server.activity.infrastructure.payload.ActivityInvocationPayload;
1313
import org.prebid.server.activity.infrastructure.payload.impl.ActivityInvocationPayloadImpl;
1414
import org.prebid.server.activity.infrastructure.payload.impl.PrivacyEnforcementServiceActivityInvocationPayload;
15+
import org.prebid.server.auction.BidderAliases;
1516
import org.prebid.server.auction.model.AuctionContext;
1617
import org.prebid.server.auction.model.BidderPrivacyResult;
1718
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdActivityMask;
@@ -21,25 +22,27 @@
2122
import java.util.Objects;
2223
import java.util.Optional;
2324

24-
public class ActivityEnforcement {
25+
public class ActivityEnforcement implements PrivacyEnforcement {
2526

2627
private final UserFpdActivityMask userFpdActivityMask;
2728

2829
public ActivityEnforcement(UserFpdActivityMask userFpdActivityMask) {
2930
this.userFpdActivityMask = Objects.requireNonNull(userFpdActivityMask);
3031
}
3132

32-
public Future<List<BidderPrivacyResult>> enforce(List<BidderPrivacyResult> bidderPrivacyResults,
33-
AuctionContext auctionContext) {
33+
@Override
34+
public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
35+
BidderAliases aliases,
36+
List<BidderPrivacyResult> results) {
3437

35-
final List<BidderPrivacyResult> results = bidderPrivacyResults.stream()
38+
final List<BidderPrivacyResult> enforcedResults = results.stream()
3639
.map(bidderPrivacyResult -> applyActivityRestrictions(
3740
bidderPrivacyResult,
3841
auctionContext.getActivityInfrastructure(),
3942
auctionContext.getBidRequest()))
4043
.toList();
4144

42-
return Future.succeededFuture(results);
45+
return Future.succeededFuture(enforcedResults);
4346
}
4447

4548
private BidderPrivacyResult applyActivityRestrictions(BidderPrivacyResult bidderPrivacyResult,

src/main/java/org/prebid/server/auction/privacy/enforcement/CcpaEnforcement.java

Lines changed: 30 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package org.prebid.server.auction.privacy.enforcement;
22

33
import com.iab.openrtb.request.BidRequest;
4-
import com.iab.openrtb.request.Device;
5-
import com.iab.openrtb.request.User;
64
import io.vertx.core.Future;
7-
import org.apache.commons.lang3.ObjectUtils;
85
import org.prebid.server.auction.BidderAliases;
96
import org.prebid.server.auction.model.AuctionContext;
107
import org.prebid.server.auction.model.BidderPrivacyResult;
@@ -22,12 +19,12 @@
2219
import java.util.Collections;
2320
import java.util.HashSet;
2421
import java.util.List;
25-
import java.util.Map;
2622
import java.util.Objects;
2723
import java.util.Optional;
2824
import java.util.Set;
25+
import java.util.stream.Collectors;
2926

30-
public class CcpaEnforcement {
27+
public class CcpaEnforcement implements PrivacyEnforcement {
3128

3229
private static final String CATCH_ALL_BIDDERS = "*";
3330

@@ -47,9 +44,10 @@ public CcpaEnforcement(UserFpdCcpaMask userFpdCcpaMask,
4744
this.ccpaEnforce = ccpaEnforce;
4845
}
4946

47+
@Override
5048
public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
51-
Map<String, User> bidderToUser,
52-
BidderAliases aliases) {
49+
BidderAliases aliases,
50+
List<BidderPrivacyResult> results) {
5351

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

6058
final Set<String> enforcedBidders = isCcpaEnabled && isCcpaEnforced
61-
? extractCcpaEnforcedBidders(bidderToUser.keySet(), bidRequest, aliases)
59+
? extractCcpaEnforcedBidders(results, bidRequest, aliases)
6260
: Collections.emptySet();
6361

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

71-
return Future.succeededFuture(maskCcpa(bidderToUser, enforcedBidders, bidRequest.getDevice()));
69+
final List<BidderPrivacyResult> enforcedResults = results.stream()
70+
.map(result -> enforcedBidders.contains(result.getRequestBidder()) ? maskCcpa(result) : result)
71+
.toList();
72+
73+
return Future.succeededFuture(enforcedResults);
7274
}
7375

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

82-
return ObjectUtils.firstNonNull(
83-
accountCcpaConfig
84-
.map(AccountCcpaConfig::getEnabledForRequestType)
85-
.map(enabledForRequestType -> enabledForRequestType.isEnabledFor(requestType))
86-
.orElse(null),
87-
accountCcpaConfig
88-
.map(AccountCcpaConfig::getEnabled)
89-
.orElse(null),
90-
ccpaEnforce);
84+
return accountCcpaConfig
85+
.map(AccountCcpaConfig::getEnabledForRequestType)
86+
.map(enabledForRequestType -> enabledForRequestType.isEnabledFor(requestType))
87+
.or(() -> accountCcpaConfig.map(AccountCcpaConfig::getEnabled))
88+
.orElse(ccpaEnforce);
9189
}
9290

93-
private Set<String> extractCcpaEnforcedBidders(Set<String> bidders, BidRequest bidRequest, BidderAliases aliases) {
94-
final Set<String> ccpaEnforcedBidders = new HashSet<>(bidders);
91+
private Set<String> extractCcpaEnforcedBidders(List<BidderPrivacyResult> results,
92+
BidRequest bidRequest,
93+
BidderAliases aliases) {
94+
95+
final Set<String> ccpaEnforcedBidders = results.stream()
96+
.map(BidderPrivacyResult::getRequestBidder)
97+
.collect(Collectors.toCollection(HashSet::new));
98+
9599
final List<String> nosaleBidders = Optional.ofNullable(bidRequest.getExt())
96100
.map(ExtRequest::getPrebid)
97101
.map(ExtRequestPrebid::getNosale)
@@ -109,14 +113,11 @@ private Set<String> extractCcpaEnforcedBidders(Set<String> bidders, BidRequest b
109113
return ccpaEnforcedBidders;
110114
}
111115

112-
private List<BidderPrivacyResult> maskCcpa(Map<String, User> bidderToUser, Set<String> bidders, Device device) {
113-
final Device maskedDevice = userFpdCcpaMask.maskDevice(device);
114-
return bidders.stream()
115-
.map(bidder -> BidderPrivacyResult.builder()
116-
.requestBidder(bidder)
117-
.user(userFpdCcpaMask.maskUser(bidderToUser.get(bidder)))
118-
.device(maskedDevice)
119-
.build())
120-
.toList();
116+
private BidderPrivacyResult maskCcpa(BidderPrivacyResult result) {
117+
return BidderPrivacyResult.builder()
118+
.requestBidder(result.getRequestBidder())
119+
.user(userFpdCcpaMask.maskUser(result.getUser()))
120+
.device(userFpdCcpaMask.maskDevice(result.getDevice()))
121+
.build();
121122
}
122123
}
Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
package org.prebid.server.auction.privacy.enforcement;
22

3-
import com.iab.openrtb.request.Device;
4-
import com.iab.openrtb.request.User;
53
import io.vertx.core.Future;
4+
import org.prebid.server.auction.BidderAliases;
65
import org.prebid.server.auction.model.AuctionContext;
76
import org.prebid.server.auction.model.BidderPrivacyResult;
87
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdCoppaMask;
98
import org.prebid.server.metric.Metrics;
109

1110
import java.util.List;
12-
import java.util.Map;
1311
import java.util.Objects;
12+
import java.util.Set;
13+
import java.util.stream.Collectors;
1414

15-
public class CoppaEnforcement {
15+
public class CoppaEnforcement implements PrivacyEnforcement {
1616

1717
private final UserFpdCoppaMask userFpdCoppaMask;
1818
private final Metrics metrics;
@@ -22,23 +22,34 @@ public CoppaEnforcement(UserFpdCoppaMask userFpdCoppaMask, Metrics metrics) {
2222
this.metrics = Objects.requireNonNull(metrics);
2323
}
2424

25-
public boolean isApplicable(AuctionContext auctionContext) {
26-
return auctionContext.getPrivacyContext().getPrivacy().getCoppa() == 1;
27-
}
25+
@Override
26+
public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
27+
BidderAliases aliases,
28+
List<BidderPrivacyResult> results) {
29+
30+
if (!isApplicable(auctionContext)) {
31+
return Future.succeededFuture(results);
32+
}
33+
34+
final Set<String> bidders = results.stream()
35+
.map(BidderPrivacyResult::getRequestBidder)
36+
.collect(Collectors.toSet());
2837

29-
public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext, Map<String, User> bidderToUser) {
30-
metrics.updatePrivacyCoppaMetric(auctionContext.getActivityInfrastructure(), bidderToUser.keySet());
31-
return Future.succeededFuture(results(bidderToUser, auctionContext.getBidRequest().getDevice()));
38+
metrics.updatePrivacyCoppaMetric(auctionContext.getActivityInfrastructure(), bidders);
39+
return Future.succeededFuture(enforce(results));
3240
}
3341

34-
private List<BidderPrivacyResult> results(Map<String, User> bidderToUser, Device device) {
35-
final Device maskedDevice = userFpdCoppaMask.maskDevice(device);
36-
return bidderToUser.entrySet().stream()
37-
.map(bidderAndUser -> BidderPrivacyResult.builder()
38-
.requestBidder(bidderAndUser.getKey())
39-
.user(userFpdCoppaMask.maskUser(bidderAndUser.getValue()))
40-
.device(maskedDevice)
42+
private List<BidderPrivacyResult> enforce(List<BidderPrivacyResult> results) {
43+
return results.stream()
44+
.map(result -> BidderPrivacyResult.builder()
45+
.requestBidder(result.getRequestBidder())
46+
.user(userFpdCoppaMask.maskUser(result.getUser()))
47+
.device(userFpdCoppaMask.maskDevice(result.getDevice()))
4148
.build())
4249
.toList();
4350
}
51+
52+
private static boolean isApplicable(AuctionContext auctionContext) {
53+
return auctionContext.getPrivacyContext().getPrivacy().getCoppa() == 1;
54+
}
4455
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.prebid.server.auction.privacy.enforcement;
2+
3+
import io.vertx.core.Future;
4+
import org.prebid.server.auction.BidderAliases;
5+
import org.prebid.server.auction.model.AuctionContext;
6+
import org.prebid.server.auction.model.BidderPrivacyResult;
7+
8+
import java.util.List;
9+
10+
public interface PrivacyEnforcement {
11+
12+
Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
13+
BidderAliases aliases,
14+
List<BidderPrivacyResult> results);
15+
}

src/main/java/org/prebid/server/auction/privacy/enforcement/PrivacyEnforcementService.java

Lines changed: 17 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,41 @@
55
import org.prebid.server.auction.BidderAliases;
66
import org.prebid.server.auction.model.AuctionContext;
77
import org.prebid.server.auction.model.BidderPrivacyResult;
8-
import org.prebid.server.util.ListUtil;
98

10-
import java.util.HashSet;
119
import java.util.List;
1210
import java.util.Map;
1311
import java.util.Objects;
14-
import java.util.Set;
1512

1613
/**
1714
* Service provides masking for OpenRTB client sensitive information.
1815
*/
1916
public class PrivacyEnforcementService {
2017

21-
private final CoppaEnforcement coppaEnforcement;
22-
private final CcpaEnforcement ccpaEnforcement;
23-
private final TcfEnforcement tcfEnforcement;
24-
private final ActivityEnforcement activityEnforcement;
18+
private final List<PrivacyEnforcement> enforcements;
2519

26-
public PrivacyEnforcementService(CoppaEnforcement coppaEnforcement,
27-
CcpaEnforcement ccpaEnforcement,
28-
TcfEnforcement tcfEnforcement,
29-
ActivityEnforcement activityEnforcement) {
30-
31-
this.coppaEnforcement = Objects.requireNonNull(coppaEnforcement);
32-
this.ccpaEnforcement = Objects.requireNonNull(ccpaEnforcement);
33-
this.tcfEnforcement = Objects.requireNonNull(tcfEnforcement);
34-
this.activityEnforcement = Objects.requireNonNull(activityEnforcement);
20+
public PrivacyEnforcementService(final List<PrivacyEnforcement> enforcements) {
21+
this.enforcements = Objects.requireNonNull(enforcements);
3522
}
3623

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

41-
// For now, COPPA masking all values, so we can omit TCF masking.
42-
return coppaEnforcement.isApplicable(auctionContext)
43-
? coppaEnforcement.enforce(auctionContext, bidderToUser)
44-
: ccpaEnforcement.enforce(auctionContext, bidderToUser, aliases)
45-
.compose(ccpaResult -> tcfEnforcement.enforce(
46-
auctionContext,
47-
bidderToUser,
48-
biddersToApplyTcf(bidderToUser.keySet(), ccpaResult),
49-
aliases)
50-
.map(tcfResult -> ListUtil.union(ccpaResult, tcfResult)))
51-
.compose(bidderPrivacyResults -> activityEnforcement.enforce(bidderPrivacyResults, auctionContext));
52-
}
28+
final List<BidderPrivacyResult> initialResults = bidderToUser.entrySet().stream()
29+
.map(entry -> BidderPrivacyResult.builder()
30+
.requestBidder(entry.getKey())
31+
.user(entry.getValue())
32+
.device(auctionContext.getBidRequest().getDevice())
33+
.build())
34+
.toList();
35+
36+
Future<List<BidderPrivacyResult>> composedResult = Future.succeededFuture(initialResults);
5337

54-
private static Set<String> biddersToApplyTcf(Set<String> bidders, List<BidderPrivacyResult> ccpaResult) {
55-
final Set<String> biddersToApplyTcf = new HashSet<>(bidders);
56-
ccpaResult.stream()
57-
.map(BidderPrivacyResult::getRequestBidder)
58-
.forEach(biddersToApplyTcf::remove);
38+
for (PrivacyEnforcement enforcement : enforcements) {
39+
composedResult = composedResult.compose(
40+
results -> enforcement.enforce(auctionContext, aliases, results));
41+
}
5942

60-
return biddersToApplyTcf;
43+
return composedResult;
6144
}
6245
}

0 commit comments

Comments
 (0)