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

Buyeruid Scrubbed Metric #3674

Merged
merged 3 commits into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
AntoxaAntoxic marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdTcfMask;
import org.prebid.server.bidder.BidderCatalog;
import org.prebid.server.log.Logger;
import org.prebid.server.log.LoggerFactory;
import org.prebid.server.metric.MetricName;
Expand Down Expand Up @@ -38,19 +37,16 @@ public class TcfEnforcement implements PrivacyEnforcement {

private final TcfDefinerService tcfDefinerService;
private final UserFpdTcfMask userFpdTcfMask;
private final BidderCatalog bidderCatalog;
private final Metrics metrics;
private final boolean lmtEnforce;

public TcfEnforcement(TcfDefinerService tcfDefinerService,
UserFpdTcfMask userFpdTcfMask,
BidderCatalog bidderCatalog,
Metrics metrics,
boolean lmtEnforce) {

this.tcfDefinerService = Objects.requireNonNull(tcfDefinerService);
this.userFpdTcfMask = Objects.requireNonNull(userFpdTcfMask);
this.bidderCatalog = Objects.requireNonNull(bidderCatalog);
this.metrics = Objects.requireNonNull(metrics);
this.lmtEnforce = lmtEnforce;
}
Expand All @@ -67,6 +63,7 @@ public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,

final MetricName requestType = auctionContext.getRequestTypeMetric();
final ActivityInfrastructure activityInfrastructure = auctionContext.getActivityInfrastructure();
final Account account = auctionContext.getAccount();
final Set<String> bidders = results.stream()
.map(BidderPrivacyResult::getRequestBidder)
.collect(Collectors.toSet());
Expand All @@ -75,9 +72,15 @@ public Future<List<BidderPrivacyResult>> enforce(AuctionContext auctionContext,
bidders,
VendorIdResolver.of(aliases),
auctionContext.getPrivacyContext().getTcfContext(),
accountGdprConfig(auctionContext.getAccount()))
accountGdprConfig(account))
.map(TcfResponse::getActions)
.map(enforcements -> updateMetrics(activityInfrastructure, enforcements, aliases, requestType, results))
.map(enforcements -> updateMetrics(
activityInfrastructure,
enforcements,
aliases,
requestType,
results,
account))
.map(enforcements -> applyEnforcements(enforcements, results));
}

Expand All @@ -86,11 +89,16 @@ private static AccountGdprConfig accountGdprConfig(Account account) {
return privacyConfig != null ? privacyConfig.getGdpr() : null;
}

private static boolean hasBuyerUid(User user) {
return user != null && user.getBuyeruid() != null;
}

private Map<String, PrivacyEnforcementAction> updateMetrics(ActivityInfrastructure activityInfrastructure,
Map<String, PrivacyEnforcementAction> enforcements,
BidderAliases aliases,
MetricName requestType,
List<BidderPrivacyResult> results) {
List<BidderPrivacyResult> results,
Account account) {

// Metrics should represent real picture of the bidding process, so if bidder request is blocked
// by privacy then no reason to increment another metrics, like geo masked, etc.
Expand Down Expand Up @@ -120,6 +128,10 @@ private Map<String, PrivacyEnforcementAction> updateMetrics(ActivityInfrastructu
requestBlocked,
isLmtEnforcedAndEnabled);

if (hasBuyerUid(user) && ufpdRemoved) {
metrics.updateAdapterRequestBuyerUidScrubbedMetrics(bidder, account);
}

if (ufpdRemoved) {
logger.warn("The UFPD fields have been removed due to a consent check.");
}
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/prebid/server/metric/MetricName.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ public enum MetricName {
unknown_error,
err,
networkerr,
buyeruid_scrubbed,

// bids validation
warn,
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/org/prebid/server/metric/Metrics.java
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,13 @@ public void updateAdapterRequestTypeAndNoCookieMetrics(String bidder, MetricName
}
}

public void updateAdapterRequestBuyerUidScrubbedMetrics(String bidder, Account account) {
forAdapter(bidder).request().incCounter(MetricName.buyeruid_scrubbed);
if (accountMetricsVerbosityResolver.forAccount(account).isAtLeast(AccountMetricsVerbosityLevel.detailed)) {
forAccount(account.getId()).adapter().forAdapter(bidder).request().incCounter(MetricName.buyeruid_scrubbed);
}
}

public void updateAdapterResponseTime(String bidder, Account account, int responseTime) {
final AdapterTypeMetrics adapterTypeMetrics = forAdapter(bidder);
adapterTypeMetrics.updateTimer(MetricName.request_time, responseTime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -393,11 +393,10 @@ CoppaEnforcement coppaEnforcement(UserFpdCoppaMask userFpdCoppaMask, Metrics met
@Bean
TcfEnforcement tcfEnforcement(TcfDefinerService tcfDefinerService,
UserFpdTcfMask userFpdTcfMask,
BidderCatalog bidderCatalog,
Metrics metrics,
@Value("${lmt.enforce}") boolean lmtEnforce) {

return new TcfEnforcement(tcfDefinerService, userFpdTcfMask, bidderCatalog, metrics, lmtEnforce);
return new TcfEnforcement(tcfDefinerService, userFpdTcfMask, metrics, lmtEnforce);
}

@Data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package org.prebid.server.functional.tests.privacy
import org.mockserver.model.Delay
import org.prebid.server.functional.model.ChannelType
import org.prebid.server.functional.model.config.AccountGdprConfig
import org.prebid.server.functional.model.config.AccountMetricsConfig
import org.prebid.server.functional.model.config.AccountMetricsVerbosityLevel
import org.prebid.server.functional.model.config.PurposeConfig
import org.prebid.server.functional.model.config.PurposeEnforcement
import org.prebid.server.functional.model.pricefloors.Country
Expand All @@ -21,6 +23,8 @@ import java.time.Instant
import static org.prebid.server.functional.model.ChannelType.PBJS
import static org.prebid.server.functional.model.ChannelType.WEB
import static org.prebid.server.functional.model.bidder.BidderName.GENERIC

import static org.prebid.server.functional.model.config.AccountMetricsVerbosityLevel.DETAILED
import static org.prebid.server.functional.model.config.Purpose.P1
import static org.prebid.server.functional.model.config.Purpose.P2
import static org.prebid.server.functional.model.config.Purpose.P4
Expand Down Expand Up @@ -960,4 +964,139 @@ class GdprAuctionSpec extends PrivacyBaseSpec {
null | BULGARIA | null | null | [:]
null | null | null | null | [:]
}

def "PBS auction shouldn't update buyeruid scrubbed metrics when user.buyeruid not requested"() {
given: "Default bid requests with personal data"
def bidRequest = bidRequestWithPersonalData.tap {
regs.gdpr = 1
user.buyeruid = null
user.ext.consent = new TcfConsent.Builder().build()
ext.prebid.trace = VERBOSE
}

and: "Save account config with requireConsent into DB"
def purposes = [(P2): new PurposeConfig(enforcePurpose: NO, enforceVendors: false)]
def accountGdprConfig = new AccountGdprConfig(purposes: purposes)
def account = getAccountWithGdpr(bidRequest.accountId, accountGdprConfig).tap {
config.metrics = new AccountMetricsConfig(verbosityLevel: verbosityLevel)
}
accountDao.save(account)

and: "Flush metric"
flushMetrics(privacyPbsService)

when: "PBS processes auction requests"
privacyPbsService.sendAuctionRequest(bidRequest)

then: "Bidder request should mask user personal data"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
verifyAll(bidderRequest.user) {
!id
!buyeruid
!yob
!gender
!eids
!data
!geo
!ext
!eids
!ext?.eids
}

and: "Metrics buyeruid scrubbed shouldn't be updated"
def metrics = privacyPbsService.sendCollectedMetricsRequest()
assert !metrics["adapter.${GENERIC.value}.requests.buyeruid_scrubbed"]
assert !metrics["account.${account.uuid}.adapter.${GENERIC.value}.requests.buyeruid_scrubbed"]

where:
verbosityLevel << [DETAILED, AccountMetricsVerbosityLevel.BASIC]
}

def "PBS auction should update buyeruid scrubbed general metrics when user.buyeruid requested and verbosityLevel BASIC"() {
given: "Default bid requests with personal data"
def bidRequest = bidRequestWithPersonalData.tap {
regs.gdpr = 1
user.ext.consent = new TcfConsent.Builder().build()
ext.prebid.trace = VERBOSE
}

and: "Save account config with requireConsent into DB"
def purposes = [(P2): new PurposeConfig(enforcePurpose: NO, enforceVendors: false)]
def accountGdprConfig = new AccountGdprConfig(purposes: purposes)
def account = getAccountWithGdpr(bidRequest.accountId, accountGdprConfig).tap {
config.metrics = new AccountMetricsConfig(verbosityLevel: AccountMetricsVerbosityLevel.BASIC)
}
accountDao.save(account)

and: "Flush metric"
flushMetrics(privacyPbsService)

when: "PBS processes auction requests"
privacyPbsService.sendAuctionRequest(bidRequest)

then: "Bidder request should mask user personal data"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
verifyAll(bidderRequest.user) {
!id
!buyeruid
!yob
!gender
!eids
!data
!geo
!ext
!eids
!ext?.eids
}

and: "Metrics buyeruid scrubbed should be updated"
def metrics = privacyPbsService.sendCollectedMetricsRequest()
assert metrics["adapter.${GENERIC.value}.requests.buyeruid_scrubbed"] == 1

and: "Account metric shouldn't be populated"
assert !metrics["account.${account.uuid}.adapter.${GENERIC.value}.requests.buyeruid_scrubbed"]
}

def "PBS auction should update buyeruid scrubbed general and account metrics when user.buyeruid requested and verbosityLevel DETAILED"() {
given: "Default bid requests with personal data"
def bidRequest = bidRequestWithPersonalData.tap {
regs.gdpr = 1
user.ext.consent = new TcfConsent.Builder().build()
ext.prebid.trace = VERBOSE
}

and: "Save account config with requireConsent into DB"
def purposes = [(P2): new PurposeConfig(enforcePurpose: NO, enforceVendors: false)]
def accountGdprConfig = new AccountGdprConfig(purposes: purposes)
def account = getAccountWithGdpr(bidRequest.accountId, accountGdprConfig).tap {
config.metrics = new AccountMetricsConfig(verbosityLevel: DETAILED)
}
accountDao.save(account)

and: "Flush metric"
flushMetrics(privacyPbsService)

when: "PBS processes auction requests"
privacyPbsService.sendAuctionRequest(bidRequest)

then: "Bidder request should mask user personal data"
def bidderRequest = bidder.getBidderRequest(bidRequest.id)
verifyAll(bidderRequest.user) {
!id
!buyeruid
!yob
!gender
!eids
!data
!geo
!ext
!eids
!ext?.eids
}

and: "Metrics buyeruid scrubbed should be updated"
def metrics = privacyPbsService.sendCollectedMetricsRequest()
assert metrics["adapter.${GENERIC.value}.requests.buyeruid_scrubbed"] == 1
assert metrics["account.${account.uuid}.adapter.${GENERIC.value}.requests.buyeruid_scrubbed"] == 1
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;
import org.prebid.server.auction.privacy.enforcement.mask.UserFpdTcfMask;
import org.prebid.server.bidder.BidderCatalog;
import org.prebid.server.metric.MetricName;
import org.prebid.server.metric.Metrics;
import org.prebid.server.privacy.gdpr.TcfDefinerService;
Expand Down Expand Up @@ -44,6 +43,7 @@
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mock.Strictness.LENIENT;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

@ExtendWith(MockitoExtension.class)
Expand All @@ -54,8 +54,6 @@ public class TcfEnforcementTest {
@Mock(strictness = LENIENT)
private UserFpdTcfMask userFpdTcfMask;
@Mock
private BidderCatalog bidderCatalog;
@Mock
private Metrics metrics;

private TcfEnforcement target;
Expand All @@ -73,7 +71,7 @@ public void setUp() {
given(userFpdTcfMask.maskDevice(any(), anyBoolean(), anyBoolean(), anyBoolean()))
.willAnswer(invocation -> invocation.getArgument(0));

target = new TcfEnforcement(tcfDefinerService, userFpdTcfMask, bidderCatalog, metrics, true);
target = new TcfEnforcement(tcfDefinerService, userFpdTcfMask, metrics, true);

given(aliases.resolveBidder(anyString()))
.willAnswer(invocation -> invocation.getArgument(0));
Expand Down Expand Up @@ -169,6 +167,44 @@ public void enforceShouldEmitExpectedMetricsWhenUserHasPrivacyData() {
verifyMetric("bidder2", true, false, false, false, false, false);
}

@Test
public void enforceShouldEmitBuyerUidScrubbedMetricsWhenUserHasBuyerUid() {
// give
givenPrivacyEnforcementActions(Map.of(
"bidder0", givenEnforcementAction(PrivacyEnforcementAction::setMaskGeo),
"bidder1", givenEnforcementAction(PrivacyEnforcementAction::setMaskDeviceInfo),
"bidder2", givenEnforcementAction(PrivacyEnforcementAction::setRemoveUserFpd),
"bidder3", givenEnforcementAction(PrivacyEnforcementAction::setMaskDeviceInfo),
"bidder4", givenEnforcementAction(PrivacyEnforcementAction::setRemoveUserFpd),
"bidder5", givenEnforcementAction(PrivacyEnforcementAction::setMaskDeviceInfo)));

final User givenUserWithoutBuyerUid = givenUserWithPrivacyData();

final User givenUserWithBuyerUid = givenUserWithoutBuyerUid.toBuilder()
.buyeruid("buyeruid")
.build();

final AuctionContext auctionContext = givenAuctionContext(givenDeviceWithNoPrivacyData());
final List<BidderPrivacyResult> initialResults = List.of(
givenBidderPrivacyResult("bidder0", givenUserWithBuyerUid, givenDeviceWithNoPrivacyData()),
givenBidderPrivacyResult("bidder1", givenUserWithBuyerUid, givenDeviceWithNoPrivacyData()),
givenBidderPrivacyResult("bidder2", givenUserWithoutBuyerUid, givenDeviceWithNoPrivacyData()),
givenBidderPrivacyResult("bidder3", givenUserWithoutBuyerUid, givenDeviceWithPrivacyData()),
givenBidderPrivacyResult("bidder4", givenUserWithBuyerUid, givenDeviceWithNoPrivacyData()),
givenBidderPrivacyResult("bidder5", givenUserWithBuyerUid, givenDeviceWithPrivacyData()));

// when
target.enforce(auctionContext, aliases, initialResults);

// then
verify(metrics, never()).updateAdapterRequestBuyerUidScrubbedMetrics(eq("bidder0"), any());
verify(metrics, never()).updateAdapterRequestBuyerUidScrubbedMetrics(eq("bidder1"), any());
verify(metrics, never()).updateAdapterRequestBuyerUidScrubbedMetrics(eq("bidder2"), any());
verify(metrics, never()).updateAdapterRequestBuyerUidScrubbedMetrics(eq("bidder3"), any());
verify(metrics).updateAdapterRequestBuyerUidScrubbedMetrics(eq("bidder4"), any());
verify(metrics).updateAdapterRequestBuyerUidScrubbedMetrics(eq("bidder5"), any());
}

@Test
public void enforceShouldEmitExpectedMetricsWhenDeviceHavePrivacyData() {
// give
Expand Down Expand Up @@ -265,7 +301,7 @@ public void enforceShouldNotEmitPrivacyLmtMetricWhenLmtNotEnforced() {
final List<BidderPrivacyResult> initialResults = List.of(
givenBidderPrivacyResult("bidder", givenUserWithPrivacyData(), device));

target = new TcfEnforcement(tcfDefinerService, userFpdTcfMask, bidderCatalog, metrics, false);
target = new TcfEnforcement(tcfDefinerService, userFpdTcfMask, metrics, false);

// when
target.enforce(auctionContext, aliases, initialResults);
Expand Down
22 changes: 22 additions & 0 deletions src/test/java/org/prebid/server/metric/MetricsTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -538,6 +538,28 @@ public void updateAdapterResponseTimeShouldUpdateMetrics() {
assertThat(metricRegistry.timer("account.accountId.adapter.conversant.request_time").getCount()).isEqualTo(2);
}

@Test
public void updateAdapterRequestBuyerUidScrubbedMetricsShouldIncrementMetrics() {
// when
metrics.updateAdapterRequestBuyerUidScrubbedMetrics(RUBICON, Account.empty(ACCOUNT_ID));
metrics.updateAdapterRequestBuyerUidScrubbedMetrics(CONVERSANT, Account.empty(ACCOUNT_ID));
metrics.updateAdapterRequestBuyerUidScrubbedMetrics(CONVERSANT, Account.empty(ACCOUNT_ID));

// then
assertThat(metricRegistry.counter("adapter.rubicon.requests.buyeruid_scrubbed")
.getCount())
.isOne();
assertThat(metricRegistry.counter("account.accountId.adapter.rubicon.requests.buyeruid_scrubbed")
.getCount())
.isOne();
assertThat(metricRegistry.counter("adapter.conversant.requests.buyeruid_scrubbed")
.getCount())
.isEqualTo(2);
assertThat(metricRegistry.counter("account.accountId.adapter.conversant.requests.buyeruid_scrubbed")
.getCount())
.isEqualTo(2);
}

@Test
public void updateAdapterRequestNobidMetricsShouldIncrementMetrics() {
// when
Expand Down
Loading