Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f40ec3c
WURFL-devicedetection module implementation (compatible with PBS Java…
andreacastello Jan 17, 2025
eef4efc
updated README.md
andreacastello Jan 17, 2025
12eb97c
updated README.md title
andreacastello Jan 17, 2025
da8fd51
some README.md wording improvements
andreacastello Jan 17, 2025
c0dd077
used markdown table for config params.
andreacastello Jan 17, 2025
39bf6d7
added maintainer email address
andreacastello Jan 17, 2025
3aff037
modified sample request data
andreacastello Jan 17, 2025
eedc3e0
fixed typos
andreacastello Jan 17, 2025
cac7b71
Merge pull request #1 from WURFL/module-wurfl-devicedetection
andreacastello Jan 17, 2025
0e93e64
improved wording
andreacastello Jan 17, 2025
89cb0dd
improved published specific enrichment paragraph
andreacastello Jan 17, 2025
7a55ba2
better doc formatting
andreacastello Jan 17, 2025
9d95efd
Merged upstream master branch to local master
andreacastello Feb 21, 2025
93f3182
Updated WURFL API version, uncommented module add into modules pom. M…
andreacastello Feb 24, 2025
96d91e4
Added WURFL API mock classes
andreacastello Feb 25, 2025
9b8b10e
checkstyle compliance
andreacastello Feb 25, 2025
1bd2865
Updated README. More checkstyle compliance
andreacastello Feb 25, 2025
cee250a
Removed commented dependency
andreacastello Feb 27, 2025
59fe1a6
Improved WURFL module README.md
andreacastello Mar 3, 2025
c0fe0db
Merge pull request #2 from WURFL/module-wurfl-devicedetection
andreacastello Mar 3, 2025
a4fcd1f
potential mock issue fix
andreacastello Mar 3, 2025
958f7c1
Merge pull request #3 from WURFL/module-wurfl-devicedetection
andreacastello Mar 4, 2025
f5bf2e3
WURFL module: improved README
andreacastello Mar 6, 2025
57d2378
WURFL module README: better wording
andreacastello Mar 6, 2025
93cbf8c
WURFL module README: more doc improvement
andreacastello Mar 6, 2025
ffe67fa
PREB-39: simplified call to virtual capabilities API
andreacastello Mar 6, 2025
6af13df
Merge pull request #4 from WURFL/PREB-39
andreacastello Mar 6, 2025
7cf5ccd
Merge branch 'prebid:master' into master
andreacastello Mar 10, 2025
1cbb9b6
Updated WURFL module version to 3.23.0-SNAPSHOT
andreacastello Mar 10, 2025
b846032
Added a WURFL API mock dependency from a public repo. Updated README …
andreacastello Mar 12, 2025
bc21beb
More README update
andreacastello Mar 12, 2025
1215903
Merge pull request #6 from WURFL/wurfl-module-with-mock-from-repo
andreacastello Mar 12, 2025
0850daa
Used WURFL lowecase in module names
andreacastello Mar 12, 2025
e559a6f
removed unused log
andreacastello Mar 12, 2025
8d712ef
handled exception in virtual capability retrieval
andreacastello Mar 12, 2025
4155848
removed print stacktrace used for debug
andreacastello Mar 13, 2025
b596b5f
Merge pull request #7 from WURFL/wurfl-module-with-mock-from-repo
andreacastello Mar 13, 2025
2034a74
Merge branch 'prebid:master' into master
andreacastello Apr 7, 2025
b5466cb
PREB-41: used FileSyncer and FileProcessor to update WURFL file and e…
andreacastello Apr 7, 2025
2c12d92
PREB-41: removed logs
andreacastello Apr 7, 2025
de636c5
PREB-41: added comment
andreacastello Apr 7, 2025
b4b9c88
Merge pull request #9 from WURFL/PREB-41-new
andreacastello Apr 7, 2025
9c0d522
Merge branch 'prebid:master' into support-bidder-specific-device-data…
andreacastello Apr 17, 2025
0c594fc
Removed all references to WURFL module dev branches
andreacastello Apr 17, 2025
1852768
Bidder specific device data impl + 2 unit tests
andreacastello Apr 17, 2025
a78d5d0
Review of #3922: reverted code in JsonMergerTest. No need for it.
andreacastello Apr 18, 2025
06c43fb
Review of #3922: added unit tests for FpdResolver.
andreacastello Apr 18, 2025
dc42fca
Review of #3922: modified PrivacyEnforcementService mask method to ad…
andreacastello Apr 22, 2025
5499fb0
Merge branch 'prebid:master' into support-bidder-specific-device-data…
andreacastello Apr 29, 2025
6f88ed6
Fix code formatting issues. Removed unneeded test case
andreacastello Apr 30, 2025
18b3054
Moved test position
andreacastello Apr 30, 2025
d7a9e88
Merge branch 'prebid:master' into support-bidder-specific-device-data…
andreacastello May 7, 2025
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
26 changes: 18 additions & 8 deletions src/main/java/org/prebid/server/auction/ExchangeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.iab.openrtb.request.App;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Content;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.Dooh;
import com.iab.openrtb.request.Eid;
import com.iab.openrtb.request.Imp;
Expand Down Expand Up @@ -97,6 +98,7 @@
import org.prebid.server.util.ListUtil;
import org.prebid.server.util.PbsUtil;
import org.prebid.server.util.StreamUtil;
import org.apache.commons.lang3.tuple.Pair;

import java.math.BigDecimal;
import java.time.Clock;
Expand Down Expand Up @@ -502,10 +504,10 @@ private Future<List<AuctionParticipation>> makeAuctionParticipation(
final ExtRequestPrebid prebid = requestExt == null ? null : requestExt.getPrebid();
final Map<String, ExtBidderConfigOrtb> biddersToConfigs = getBiddersToConfigs(prebid);
final Map<String, List<String>> eidPermissions = getEidPermissions(prebid);
final Map<String, User> bidderToUser =
prepareUsers(bidders, context, aliases, biddersToConfigs, eidPermissions);
final Map<String, Pair<User, Device>> bidderToUserAndDevice =
prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissions);

return privacyEnforcementService.mask(context, bidderToUser, aliases)
return privacyEnforcementService.mask(context, bidderToUserAndDevice, aliases)
.map(bidderToPrivacyResult -> getAuctionParticipation(
bidderToPrivacyResult,
bidRequest,
Expand Down Expand Up @@ -557,7 +559,7 @@ private static List<String> firstPartyDataBidders(ExtRequest requestExt) {
return data == null ? null : data.getBidders();
}

private Map<String, User> prepareUsers(List<String> bidders,
private Map<String, Pair<User, Device>> prepareUsersAndDevices(List<String> bidders,
AuctionContext context,
BidderAliases aliases,
Map<String, ExtBidderConfigOrtb> biddersToConfigs,
Expand All @@ -566,17 +568,19 @@ private Map<String, User> prepareUsers(List<String> bidders,
final BidRequest bidRequest = context.getBidRequest();
final List<String> firstPartyDataBidders = firstPartyDataBidders(bidRequest.getExt());

final Map<String, User> bidderToUser = new HashMap<>();
final Map<String, Pair<User, Device>> bidderToUserAndDevice = new HashMap<>();
for (String bidder : bidders) {
final ExtBidderConfigOrtb fpdConfig = ObjectUtils.defaultIfNull(biddersToConfigs.get(bidder),
biddersToConfigs.get(ALL_BIDDERS_CONFIG));
final boolean useFirstPartyData = firstPartyDataBidders == null || firstPartyDataBidders.stream()
.anyMatch(fpdBidder -> StringUtils.equalsIgnoreCase(fpdBidder, bidder));
final User preparedUser = prepareUser(
bidder, context, aliases, useFirstPartyData, fpdConfig, eidPermissions);
bidderToUser.put(bidder, preparedUser);
final Device preparedDevice = prepareDevice(
bidRequest.getDevice(), fpdConfig, useFirstPartyData);
bidderToUserAndDevice.put(bidder, Pair.of(preparedUser, preparedDevice));
}
return bidderToUser;
return bidderToUserAndDevice;
}

private User prepareUser(String bidder,
Expand Down Expand Up @@ -813,7 +817,6 @@ private BidRequest prepareBidRequest(BidderPrivacyResult bidderPrivacyResult,
final boolean isApp = preparedApp != null;
final boolean isDooh = !isApp && preparedDooh != null;
final boolean isSite = !isApp && !isDooh && preparedSite != null;

final List<Imp> preparedImps = prepareImps(
bidder,
bidRequest,
Expand Down Expand Up @@ -945,6 +948,13 @@ private App prepareApp(App app, ObjectNode fpdApp, boolean useFirstPartyData) {
return useFirstPartyData ? fpdResolver.resolveApp(maskedApp, fpdApp) : maskedApp;
}

private Device prepareDevice(Device device, ExtBidderConfigOrtb fpdConfig, boolean useFirstPartyData) {
if (fpdConfig == null) {
return device;
}
return useFirstPartyData ? fpdResolver.resolveDevice(device, fpdConfig.getDevice()) : device;
}

private static ExtApp maskExtApp(ExtApp appExt) {
final ExtApp maskedExtApp = ExtApp.of(appExt.getPrebid(), null);
return maskedExtApp.isEmpty() ? null : maskedExtApp;
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/org/prebid/server/auction/FpdResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.App;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.Dooh;
import com.iab.openrtb.request.Site;
import com.iab.openrtb.request.User;
Expand All @@ -23,7 +24,8 @@ public class FpdResolver {
private static final String BIDDERS = "bidders";
private static final String APP = "app";
private static final String DOOH = "dooh";
private static final Set<String> KNOWN_FPD_ATTRIBUTES = Set.of(USER, SITE, APP, DOOH, BIDDERS);
private static final String DEVICE = "device";
private static final Set<String> KNOWN_FPD_ATTRIBUTES = Set.of(USER, SITE, APP, DOOH, DEVICE, BIDDERS);
private static final String CONTEXT = "context";
private static final String DATA = "data";

Expand Down Expand Up @@ -51,6 +53,10 @@ public Dooh resolveDooh(Dooh originDooh, ObjectNode fpdDooh) {
return mergeFpd(originDooh, fpdDooh, Dooh.class);
}

public Device resolveDevice(Device originDevice, ObjectNode fpdDevice) {
return mergeFpd(originDevice, fpdDevice, Device.class);
}

private <T> T mergeFpd(T original, ObjectNode fpd, Class<T> tClass) {
if (fpd == null || fpd.isNull() || fpd.isMissingNode()) {
return original;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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.aliases.BidderAliases;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.BidderPrivacyResult;
import org.apache.commons.lang3.tuple.Pair;

import java.util.List;
import java.util.Map;
Expand All @@ -22,14 +24,14 @@ public PrivacyEnforcementService(final List<PrivacyEnforcement> enforcements) {
}

public Future<List<BidderPrivacyResult>> mask(AuctionContext auctionContext,
Map<String, User> bidderToUser,
Map<String, Pair<User, Device>> bidderToUserAndDevice,
BidderAliases aliases) {

final List<BidderPrivacyResult> initialResults = bidderToUser.entrySet().stream()
final List<BidderPrivacyResult> initialResults = bidderToUserAndDevice.entrySet().stream()
.map(entry -> BidderPrivacyResult.builder()
.requestBidder(entry.getKey())
.user(entry.getValue())
.device(auctionContext.getBidRequest().getDevice())
.user(entry.getValue().getLeft())
.device(entry.getValue().getRight())
.build())
.toList();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ public class ExtBidderConfigOrtb {
* Defines the contract for bidrequest.ext.prebid.bidderconfig.config.ortb2.user
*/
ObjectNode user;

/**
* Defines the contract for bidrequest.ext.prebid.bidderconfig.config.ortb2.device
*/
ObjectNode device;
}
60 changes: 50 additions & 10 deletions src/test/java/org/prebid/server/auction/ExchangeServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -322,10 +322,12 @@ public void setUp() {

given(privacyEnforcementService.mask(any(), argThat(MapUtils::isNotEmpty), any()))
.willAnswer(inv ->
Future.succeededFuture(((Map<String, User>) inv.getArgument(1)).entrySet().stream()
Future.succeededFuture(((Map<String, Pair<User, Device>>) inv.getArgument(1)).entrySet()
.stream()
.map(bidderAndUser -> BidderPrivacyResult.builder()
.requestBidder(bidderAndUser.getKey())
.user(bidderAndUser.getValue())
.user(bidderAndUser.getValue().getLeft())
.device(bidderAndUser.getValue().getRight())
.build())
.toList()));

Expand Down Expand Up @@ -2691,12 +2693,12 @@ public void shouldUseConcreteOverGeneralSiteWithExtPrebidBidderConfigIgnoringCas

final ObjectNode siteWithPage = mapper.valueToTree(Site.builder().page("testPage").build());
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(siteWithPage, null, null, null));
ExtBidderConfigOrtb.of(siteWithPage, null, null, null, null));
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
singletonList("SoMeBiDdEr"), extBidderConfig);
final ObjectNode siteWithDomain = mapper.valueToTree(Site.builder().domain("notUsed").build());
final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(siteWithDomain, null, null, null));
ExtBidderConfigOrtb.of(siteWithDomain, null, null, null, null));
final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"),
allExtBidderConfig);

Expand Down Expand Up @@ -2738,12 +2740,12 @@ public void shouldUseConcreteOverGeneralDoohWithExtPrebidBidderConfig() {

final ObjectNode doohWithVenueType = mapper.valueToTree(Dooh.builder().venuetype(List.of("venuetype")).build());
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(null, null, doohWithVenueType, null));
ExtBidderConfigOrtb.of(null, null, doohWithVenueType, null, null));
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
singletonList("someBidder"), extBidderConfig);
final ObjectNode doohWithDomain = mapper.valueToTree(Dooh.builder().domain("notUsed").build());
final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(null, null, doohWithDomain, null));
ExtBidderConfigOrtb.of(null, null, doohWithDomain, null, null));
final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(
singletonList("*"),
allExtBidderConfig);
Expand Down Expand Up @@ -2787,15 +2789,15 @@ public void shouldUseConcreteOverGeneralAppWithExtPrebidBidderConfigIgnoringCase
final Publisher publisherWithId = Publisher.builder().id("testId").build();
final ObjectNode appWithPublisherId = mapper.valueToTree(App.builder().publisher(publisherWithId).build());
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(null, appWithPublisherId, null, null));
ExtBidderConfigOrtb.of(null, appWithPublisherId, null, null, null));
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
singletonList("SoMeBiDdEr"), extBidderConfig);

final Publisher publisherWithIdAndDomain = Publisher.builder().id("notUsed").domain("notUsed").build();
final ObjectNode appWithUpdatedPublisher = mapper.valueToTree(
App.builder().publisher(publisherWithIdAndDomain).build());
final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(null, appWithUpdatedPublisher, null, null));
ExtBidderConfigOrtb.of(null, appWithUpdatedPublisher, null, null, null));
final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"),
allExtBidderConfig);

Expand Down Expand Up @@ -2834,13 +2836,13 @@ public void shouldUseConcreteOverGeneralUserWithExtPrebidBidderConfig() {
givenBidder("someBidder", bidder, givenEmptySeatBid());
final ObjectNode bidderConfigUser = mapper.valueToTree(User.builder().id("userFromConfig").build());
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(null, null, null, bidderConfigUser));
ExtBidderConfigOrtb.of(null, null, null, bidderConfigUser, null));
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
singletonList("SomMeBiDdEr"), extBidderConfig);

final ObjectNode emptyUser = mapper.valueToTree(User.builder().build());
final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(null, null, null, emptyUser));
ExtBidderConfigOrtb.of(null, null, null, emptyUser, null));
final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"),
allExtBidderConfig);
final User requestUser = User.builder().id("erased").buyeruid("testBuyerId").build();
Expand Down Expand Up @@ -2870,6 +2872,44 @@ public void shouldUseConcreteOverGeneralUserWithExtPrebidBidderConfig() {
.containsOnly(mergedUser);
}

@Test
public void shouldUseBidderSpecificDeviceDataInBidderRequest() {
// given
final Bidder<?> bidder = mock(Bidder.class);
givenBidder("someBidder", bidder, givenEmptySeatBid());

final ObjectNode deviceWithMakeAndModel = mapper.valueToTree(
Device.builder().make("TestMake_001").model("TestModel_001").build());
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
ExtBidderConfigOrtb.of(null, null, null, null, deviceWithMakeAndModel));
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
singletonList("someBidder"), extBidderConfig);
final Device requestDevice = Device.builder().build();
final ExtRequestPrebid extRequestPrebid = ExtRequestPrebid.builder()
.bidderconfig(singletonList(concreteFpdConfig))
.build();
final BidRequest bidRequest = givenBidRequest(givenSingleImp(singletonMap("someBidder", 1)),
builder -> builder.device(requestDevice).ext(ExtRequest.of(extRequestPrebid)));
final Device mergedDevice = Device.builder()
.make("TestMake_001").model("TestModel_001").build();

given(fpdResolver.resolveDevice(any(), any())).willReturn(mergedDevice);

// when
target.holdAuction(givenRequestContext(bidRequest));

// then
final ArgumentCaptor<BidderRequest> bidderRequestCaptor = ArgumentCaptor.forClass(BidderRequest.class);
verify(httpBidderRequester)
.requestBids(any(), bidderRequestCaptor.capture(), any(), any(), any(), any(), anyBoolean());
final List<BidderRequest> capturedBidRequests = bidderRequestCaptor.getAllValues();

assertThat(capturedBidRequests)
.extracting(BidderRequest::getBidRequest)
.extracting(BidRequest::getDevice)
.containsOnly(mergedDevice);
}

@Test
public void shouldAddBuyeridToUserFromRequest() {
// given
Expand Down
78 changes: 78 additions & 0 deletions src/test/java/org/prebid/server/auction/FpdResolverTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.iab.openrtb.request.App;
import com.iab.openrtb.request.Content;
import com.iab.openrtb.request.Data;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.Dooh;
import com.iab.openrtb.request.Geo;
import com.iab.openrtb.request.Publisher;
Expand All @@ -17,6 +18,9 @@
import org.prebid.server.json.JsonMerger;
import org.prebid.server.proto.openrtb.ext.request.ExtApp;
import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtDevice;
import org.prebid.server.proto.openrtb.ext.request.ExtDeviceInt;
import org.prebid.server.proto.openrtb.ext.request.ExtDevicePrebid;
import org.prebid.server.proto.openrtb.ext.request.ExtDooh;
import org.prebid.server.proto.openrtb.ext.request.ExtSite;
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
Expand Down Expand Up @@ -134,6 +138,80 @@ public void resolveUserShouldReturnCopyOfUserExtDataIfFPDUserExtDataIsMissing()
assertThat(resultUser.getExt().getData().equals(originExtUserData)).isTrue(); // but the same by value
}

@Test
public void resolveDeviceShouldOverrideFpdFieldsFromFpdDevice() {
// given
final Device originDevice = Device.builder()
.devicetype(1)
.make("original_make")
.model("original_model")
.os("original_os")
.osv("original_osv")
.hwv("original_hwv")
.language("original_language")
.h(1111)
.js(1)
.ip("original_ip")
.build();

final Device fpdDevice = Device.builder()
.devicetype(2)
.make("fpd_make")
.model("fpd_model")
.os("fpd_os")
.osv("fpd_osv")
.hwv("fpd_hwv")
.ip("new_ip")
.build();

// when
final Device resultDevice = target.resolveDevice(originDevice, mapper.valueToTree(fpdDevice));

// then
assertThat(resultDevice).isEqualTo(Device.builder()
.devicetype(2)
.make("fpd_make")
.model("fpd_model")
.os("fpd_os")
.osv("fpd_osv")
.hwv("fpd_hwv")
.language("original_language")
.h(1111)
.js(1)
.ip("new_ip")
.build());
}

@Test
public void resolveDeviceShouldReturnOriginDeviceIfFpdDeviceIsNull() {
assertThat(target.resolveDevice(Device.builder().make("test_make").build(), null))
.isEqualTo(Device.builder().make("test_make").build());
}

@Test
public void resolveDeviceShouldReturnFpdDeviceIfOriginDeviceIsNull() {
assertThat(target.resolveDevice(null, mapper.valueToTree(Device.builder().model("test_model").build())))
.isEqualTo(Device.builder().model("test_model").build());
}

@Test
public void resolveDeviceShouldNotChangeOriginExtDataIfFPDDoesNotHaveExt() {
// given
final Device originDevice = Device.builder()
.ext(ExtDevice.of(1, ExtDevicePrebid.of(ExtDeviceInt.of(10, 20))))
.build();

final Device fpdDevice = Device.builder().build();

// when
final Device resultDevice = target.resolveDevice(originDevice, mapper.valueToTree(fpdDevice));

// then
assertThat(resultDevice).isEqualTo(Device.builder()
.ext(ExtDevice.of(1, ExtDevicePrebid.of(ExtDeviceInt.of(10, 20))))
.build());
}

@Test
public void resolveAppShouldOverrideFpdFieldsFromFpdApp() {
// given
Expand Down
Loading
Loading