Skip to content

Commit

Permalink
Add account CCPA enabled and per-request-type enabled flags (#1044)
Browse files Browse the repository at this point in the history
* Add account CCPA enabled and per-request-type enabled flags

* Fixes after review

* Add account CCPA enabled and per-request-type enabled flags

* Fixes after review

* Merge remote-tracking branch 'remotes/origin/master' into account/ccpa/enabled

# Conflicts:
#	src/main/java/org/prebid/server/auction/PrivacyEnforcementService.java

* Add CCPA to AccountConfigurationProperties

Co-authored-by: Serhii Nahornyi <snahornyi@rubiconproject.com>
Co-authored-by: nickluck8 <msteliuk@magnite.com>
Co-authored-by: rpanchyk <rpanchyk@rubiconproject.com>
  • Loading branch information
4 people authored Aug 13, 2021
1 parent a67ea36 commit 2de846a
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import io.vertx.core.http.HttpServerRequest;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;
Expand All @@ -34,7 +34,9 @@
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
import org.prebid.server.proto.request.CookieSyncRequest;
import org.prebid.server.settings.model.Account;
import org.prebid.server.settings.model.AccountCcpaConfig;
import org.prebid.server.settings.model.AccountGdprConfig;
import org.prebid.server.settings.model.EnabledForRequestType;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
Expand Down Expand Up @@ -201,7 +203,7 @@ Future<List<BidderPrivacyResult>> mask(AuctionContext auctionContext,

updateCcpaMetrics(privacy.getCcpa());
final Map<String, BidderPrivacyResult> ccpaResult =
ccpaResult(bidRequest, account, bidders, aliases, device, bidderToUser, privacy);
ccpaResult(bidRequest, account, bidders, aliases, device, bidderToUser, privacy, requestType);

final Set<String> biddersToApplyTcf = new HashSet<>(bidders);
biddersToApplyTcf.removeAll(ccpaResult.keySet());
Expand All @@ -226,24 +228,53 @@ private Map<String, BidderPrivacyResult> ccpaResult(BidRequest bidRequest,
BidderAliases aliases,
Device device,
Map<String, User> bidderToUser,
Privacy privacy) {
Privacy privacy,
MetricName requestType) {

if (isCcpaEnforced(privacy.getCcpa(), account)) {
if (isCcpaEnforced(privacy.getCcpa(), account, requestType)) {
return maskCcpa(extractCcpaEnforcedBidders(bidders, bidRequest, aliases), device, bidderToUser);
}

return Collections.emptyMap();
}

public boolean isCcpaEnforced(Ccpa ccpa, Account account) {
final boolean shouldEnforceCcpa = BooleanUtils.toBooleanDefaultIfNull(account.getEnforceCcpa(), ccpaEnforce);
final boolean shouldEnforceCcpa = isCcpaEnabled(account);

return shouldEnforceCcpa && ccpa.isEnforced();
}

private Map<String, BidderPrivacyResult> maskCcpa(Set<String> biddersToMask,
Device device,
Map<String, User> bidderToUser) {
private boolean isCcpaEnforced(Ccpa ccpa, Account account, MetricName requestType) {
final boolean shouldEnforceCcpa = isCcpaEnabled(account, requestType);
return shouldEnforceCcpa && ccpa.isEnforced();
}

private Boolean isCcpaEnabled(Account account) {
final AccountCcpaConfig accountCcpaConfig = account.getCcpa();
final Boolean accountCcpaEnabled = accountCcpaConfig != null ? accountCcpaConfig.getEnabled() : null;

return ObjectUtils.firstNonNull(accountCcpaEnabled, account.getEnforceCcpa(), ccpaEnforce);
}

private boolean isCcpaEnabled(Account account, MetricName requestType) {
final AccountCcpaConfig accountCcpaConfig = account.getCcpa();
final Boolean accountCcpaEnabled = accountCcpaConfig != null ? accountCcpaConfig.getEnabled() : null;
if (requestType == null) {
return ObjectUtils.firstNonNull(accountCcpaEnabled, account.getEnforceCcpa(), ccpaEnforce);
}

final EnabledForRequestType enabledForRequestType = accountCcpaConfig != null
? accountCcpaConfig.getEnabledForRequestType()
: null;

final Boolean enabledForType = enabledForRequestType != null
? enabledForRequestType.isEnabledFor(requestType)
: null;
return ObjectUtils.firstNonNull(enabledForType, accountCcpaEnabled, account.getEnforceCcpa(), ccpaEnforce);
}

private Map<String, BidderPrivacyResult> maskCcpa(
Set<String> biddersToMask, Device device, Map<String, User> bidderToUser) {

return biddersToMask.stream()
.collect(Collectors.toMap(Function.identity(),
Expand Down Expand Up @@ -347,7 +378,8 @@ private Future<Map<String, PrivacyEnforcementAction>> getBidderToEnforcementActi
.map(tcfResponse -> mapTcfResponseToEachBidder(tcfResponse, bidders));
}

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

final ExtRequest extBidRequest = bidRequest.getExt();
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/prebid/server/settings/model/Account.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class Account {

AccountGdprConfig gdpr;

AccountCcpaConfig ccpa;

Integer analyticsSamplingFactor;

Integer truncateTargetAttr;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.prebid.server.settings.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Data
public class AccountCcpaConfig {

@JsonProperty("enabled")
Boolean enabled;

@JsonProperty("integration-enabled")
EnabledForRequestType enabledForRequestType;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.prebid.server.settings.model.Account;
import org.prebid.server.settings.model.AccountAnalyticsConfig;
import org.prebid.server.settings.model.AccountBidValidationConfig;
import org.prebid.server.settings.model.AccountCcpaConfig;
import org.prebid.server.settings.model.AccountCookieSyncConfig;
import org.prebid.server.settings.model.AccountGdprConfig;
import org.prebid.server.settings.model.AccountStatus;
Expand All @@ -26,6 +27,8 @@ public class AccountConfigurationProperties {

private String gdpr;

private String ccpa;

private Integer analyticsSamplingFactor;

private Integer truncateTargetAttr;
Expand All @@ -48,6 +51,7 @@ public Account toAccount(JacksonMapper mapper) {
.eventsEnabled(getEventsEnabled())
.enforceCcpa(getEnforceCcpa())
.gdpr(toModel(mapper, getGdpr(), AccountGdprConfig.class))
.ccpa(toModel(mapper, getCcpa(), AccountCcpaConfig.class))
.analyticsSamplingFactor(getAnalyticsSamplingFactor())
.truncateTargetAttr(getTruncateTargetAttr())
.defaultIntegration(getDefaultIntegration())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
import org.prebid.server.proto.request.CookieSyncRequest;
import org.prebid.server.proto.response.BidderInfo;
import org.prebid.server.settings.model.Account;
import org.prebid.server.settings.model.AccountCcpaConfig;
import org.prebid.server.settings.model.EnabledForRequestType;

import java.time.Clock;
import java.time.Instant;
Expand Down Expand Up @@ -447,6 +449,148 @@ public void shouldMaskForCcpaWhenUsPolicyIsValidAndCoppaIsZero() {
assertThat(result).isEqualTo(singletonList(expected));
}

@Test
public void shouldMaskForCcpaWhenAccountHasCppaConfigEnabledForRequestType() {
// given
privacyEnforcementService = new PrivacyEnforcementService(
bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper,
metrics, false, true);

given(tcfDefinerService.resultForBidderNames(anySet(), any(), any(), any()))
.willReturn(Future.succeededFuture(TcfResponse.of(true, emptyMap(), null)));

given(bidderCatalog.bidderInfoByName(BIDDER_NAME)).willReturn(givenBidderInfo(1, true));

final User user = notMaskedUser(notMaskedExtUser());
final Device device = notMaskedDevice();
final Map<String, User> bidderToUser = singletonMap(BIDDER_NAME, user);

final BidRequest bidRequest = givenBidRequest(givenSingleImp(
singletonMap(BIDDER_NAME, 1)),
bidRequestBuilder -> bidRequestBuilder
.user(user)
.device(device));

final PrivacyContext privacyContext = givenPrivacyContext("1", Ccpa.of("1YYY"), 0);

final AuctionContext context = AuctionContext.builder()
.account(Account.builder()
.ccpa(AccountCcpaConfig.builder()
.enabledForRequestType(EnabledForRequestType.of(false, false, true, false))
.build())
.build())
.requestTypeMetric(MetricName.openrtb2app)
.bidRequest(bidRequest)
.timeout(timeout)
.privacyContext(privacyContext)
.build();

// when
final List<BidderPrivacyResult> result = privacyEnforcementService
.mask(context, bidderToUser, singletonList(BIDDER_NAME), aliases)
.result();

// then
final BidderPrivacyResult expected = BidderPrivacyResult.builder()
.requestBidder(BIDDER_NAME)
.user(userTcfMasked(extUserIdsMasked()))
.device(deviceTcfMasked())
.build();
assertThat(result).isEqualTo(singletonList(expected));
}

@Test
public void shouldMaskForCcpaWhenAccountHasCppaEnforcedTrue() {
// given
privacyEnforcementService = new PrivacyEnforcementService(
bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper,
metrics, false, true);

given(tcfDefinerService.resultForBidderNames(anySet(), any(), any(), any()))
.willReturn(Future.succeededFuture(TcfResponse.of(true, emptyMap(), null)));

given(bidderCatalog.bidderInfoByName(BIDDER_NAME)).willReturn(givenBidderInfo(1, true));

final User user = notMaskedUser(notMaskedExtUser());
final Device device = notMaskedDevice();
final Map<String, User> bidderToUser = singletonMap(BIDDER_NAME, user);

final BidRequest bidRequest = givenBidRequest(givenSingleImp(
singletonMap(BIDDER_NAME, 1)),
bidRequestBuilder -> bidRequestBuilder
.user(user)
.device(device));

final PrivacyContext privacyContext = givenPrivacyContext("1", Ccpa.of("1YYY"), 0);

final AuctionContext context = AuctionContext.builder()
.account(Account.builder().enforceCcpa(true).build())
.requestTypeMetric(MetricName.openrtb2app)
.bidRequest(bidRequest)
.timeout(timeout)
.privacyContext(privacyContext)
.build();

// when
final List<BidderPrivacyResult> result = privacyEnforcementService
.mask(context, bidderToUser, singletonList(BIDDER_NAME), aliases)
.result();

// then
final BidderPrivacyResult expected = BidderPrivacyResult.builder()
.requestBidder(BIDDER_NAME)
.user(userTcfMasked(extUserIdsMasked()))
.device(deviceTcfMasked())
.build();
assertThat(result).isEqualTo(singletonList(expected));
}

@Test
public void shouldMaskForCcpaWhenAccountHasCcpaConfigEnabled() {
// given
privacyEnforcementService = new PrivacyEnforcementService(
bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper,
metrics, false, true);

given(tcfDefinerService.resultForBidderNames(anySet(), any(), any(), any()))
.willReturn(Future.succeededFuture(TcfResponse.of(true, emptyMap(), null)));

given(bidderCatalog.bidderInfoByName(BIDDER_NAME)).willReturn(givenBidderInfo(1, true));

final User user = notMaskedUser(notMaskedExtUser());
final Device device = notMaskedDevice();
final Map<String, User> bidderToUser = singletonMap(BIDDER_NAME, user);

final BidRequest bidRequest = givenBidRequest(givenSingleImp(
singletonMap(BIDDER_NAME, 1)),
bidRequestBuilder -> bidRequestBuilder
.user(user)
.device(device));

final PrivacyContext privacyContext = givenPrivacyContext("1", Ccpa.of("1YYY"), 0);

final AuctionContext context = AuctionContext.builder()
.account(Account.builder().ccpa(AccountCcpaConfig.builder().enabled(true).build()).build())
.requestTypeMetric(null)
.bidRequest(bidRequest)
.timeout(timeout)
.privacyContext(privacyContext)
.build();

// when
final List<BidderPrivacyResult> result = privacyEnforcementService
.mask(context, bidderToUser, singletonList(BIDDER_NAME), aliases)
.result();

// then
final BidderPrivacyResult expected = BidderPrivacyResult.builder()
.requestBidder(BIDDER_NAME)
.user(userTcfMasked(extUserIdsMasked()))
.device(deviceTcfMasked())
.build();
assertThat(result).isEqualTo(singletonList(expected));
}

@Test
public void shouldTolerateEmptyBidderToBidderPrivacyResultList() {
// given
Expand Down Expand Up @@ -1330,6 +1474,20 @@ public void isCcpaEnforcedShouldReturnFalseWhenEnforcedPropertyIsTrue() {
assertThat(privacyEnforcementService.isCcpaEnforced(ccpa, account)).isFalse();
}

@Test
public void isCcpaEnforcedShouldReturnFalseWhenAccountCcpaConfigHasEnabledTrue() {
// given
privacyEnforcementService = new PrivacyEnforcementService(
bidderCatalog, privacyExtractor, tcfDefinerService, implicitParametersExtractor, ipAddressHelper,
metrics, false, true);

final Ccpa ccpa = Ccpa.of("1YYY");
final Account account = Account.builder().ccpa(AccountCcpaConfig.builder().enabled(true).build()).build();

// when and then
assertThat(privacyEnforcementService.isCcpaEnforced(ccpa, account)).isTrue();
}

@Test
public void isCcpaEnforcedShouldReturnTrueWhenEnforcedPropertyIsTrueAndCcpaReturnsTrue() {
// given
Expand Down

0 comments on commit 2de846a

Please sign in to comment.