Skip to content

Commit 33daa4f

Browse files
andreacastelloosulzhenko
authored andcommitted
Core: Support bidder specific device data (#3922)
1 parent f785d99 commit 33daa4f

8 files changed

Lines changed: 183 additions & 28 deletions

File tree

src/main/java/org/prebid/server/auction/ExchangeService.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.iab.openrtb.request.App;
66
import com.iab.openrtb.request.BidRequest;
77
import com.iab.openrtb.request.Content;
8+
import com.iab.openrtb.request.Device;
89
import com.iab.openrtb.request.Dooh;
910
import com.iab.openrtb.request.Eid;
1011
import com.iab.openrtb.request.Imp;
@@ -97,6 +98,7 @@
9798
import org.prebid.server.util.ListUtil;
9899
import org.prebid.server.util.PbsUtil;
99100
import org.prebid.server.util.StreamUtil;
101+
import org.apache.commons.lang3.tuple.Pair;
100102

101103
import java.math.BigDecimal;
102104
import java.time.Clock;
@@ -502,10 +504,10 @@ private Future<List<AuctionParticipation>> makeAuctionParticipation(
502504
final ExtRequestPrebid prebid = requestExt == null ? null : requestExt.getPrebid();
503505
final Map<String, ExtBidderConfigOrtb> biddersToConfigs = getBiddersToConfigs(prebid);
504506
final Map<String, List<String>> eidPermissions = getEidPermissions(prebid);
505-
final Map<String, User> bidderToUser =
506-
prepareUsers(bidders, context, aliases, biddersToConfigs, eidPermissions);
507+
final Map<String, Pair<User, Device>> bidderToUserAndDevice =
508+
prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissions);
507509

508-
return privacyEnforcementService.mask(context, bidderToUser, aliases)
510+
return privacyEnforcementService.mask(context, bidderToUserAndDevice, aliases)
509511
.map(bidderToPrivacyResult -> getAuctionParticipation(
510512
bidderToPrivacyResult,
511513
bidRequest,
@@ -557,7 +559,7 @@ private static List<String> firstPartyDataBidders(ExtRequest requestExt) {
557559
return data == null ? null : data.getBidders();
558560
}
559561

560-
private Map<String, User> prepareUsers(List<String> bidders,
562+
private Map<String, Pair<User, Device>> prepareUsersAndDevices(List<String> bidders,
561563
AuctionContext context,
562564
BidderAliases aliases,
563565
Map<String, ExtBidderConfigOrtb> biddersToConfigs,
@@ -566,17 +568,19 @@ private Map<String, User> prepareUsers(List<String> bidders,
566568
final BidRequest bidRequest = context.getBidRequest();
567569
final List<String> firstPartyDataBidders = firstPartyDataBidders(bidRequest.getExt());
568570

569-
final Map<String, User> bidderToUser = new HashMap<>();
571+
final Map<String, Pair<User, Device>> bidderToUserAndDevice = new HashMap<>();
570572
for (String bidder : bidders) {
571573
final ExtBidderConfigOrtb fpdConfig = ObjectUtils.defaultIfNull(biddersToConfigs.get(bidder),
572574
biddersToConfigs.get(ALL_BIDDERS_CONFIG));
573575
final boolean useFirstPartyData = firstPartyDataBidders == null || firstPartyDataBidders.stream()
574576
.anyMatch(fpdBidder -> StringUtils.equalsIgnoreCase(fpdBidder, bidder));
575577
final User preparedUser = prepareUser(
576578
bidder, context, aliases, useFirstPartyData, fpdConfig, eidPermissions);
577-
bidderToUser.put(bidder, preparedUser);
579+
final Device preparedDevice = prepareDevice(
580+
bidRequest.getDevice(), fpdConfig, useFirstPartyData);
581+
bidderToUserAndDevice.put(bidder, Pair.of(preparedUser, preparedDevice));
578582
}
579-
return bidderToUser;
583+
return bidderToUserAndDevice;
580584
}
581585

582586
private User prepareUser(String bidder,
@@ -813,7 +817,6 @@ private BidRequest prepareBidRequest(BidderPrivacyResult bidderPrivacyResult,
813817
final boolean isApp = preparedApp != null;
814818
final boolean isDooh = !isApp && preparedDooh != null;
815819
final boolean isSite = !isApp && !isDooh && preparedSite != null;
816-
817820
final List<Imp> preparedImps = prepareImps(
818821
bidder,
819822
bidRequest,
@@ -945,6 +948,13 @@ private App prepareApp(App app, ObjectNode fpdApp, boolean useFirstPartyData) {
945948
return useFirstPartyData ? fpdResolver.resolveApp(maskedApp, fpdApp) : maskedApp;
946949
}
947950

951+
private Device prepareDevice(Device device, ExtBidderConfigOrtb fpdConfig, boolean useFirstPartyData) {
952+
if (fpdConfig == null) {
953+
return device;
954+
}
955+
return useFirstPartyData ? fpdResolver.resolveDevice(device, fpdConfig.getDevice()) : device;
956+
}
957+
948958
private static ExtApp maskExtApp(ExtApp appExt) {
949959
final ExtApp maskedExtApp = ExtApp.of(appExt.getPrebid(), null);
950960
return maskedExtApp.isEmpty() ? null : maskedExtApp;

src/main/java/org/prebid/server/auction/FpdResolver.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.fasterxml.jackson.databind.node.NullNode;
77
import com.fasterxml.jackson.databind.node.ObjectNode;
88
import com.iab.openrtb.request.App;
9+
import com.iab.openrtb.request.Device;
910
import com.iab.openrtb.request.Dooh;
1011
import com.iab.openrtb.request.Site;
1112
import com.iab.openrtb.request.User;
@@ -23,7 +24,8 @@ public class FpdResolver {
2324
private static final String BIDDERS = "bidders";
2425
private static final String APP = "app";
2526
private static final String DOOH = "dooh";
26-
private static final Set<String> KNOWN_FPD_ATTRIBUTES = Set.of(USER, SITE, APP, DOOH, BIDDERS);
27+
private static final String DEVICE = "device";
28+
private static final Set<String> KNOWN_FPD_ATTRIBUTES = Set.of(USER, SITE, APP, DOOH, DEVICE, BIDDERS);
2729
private static final String CONTEXT = "context";
2830
private static final String DATA = "data";
2931

@@ -51,6 +53,10 @@ public Dooh resolveDooh(Dooh originDooh, ObjectNode fpdDooh) {
5153
return mergeFpd(originDooh, fpdDooh, Dooh.class);
5254
}
5355

56+
public Device resolveDevice(Device originDevice, ObjectNode fpdDevice) {
57+
return mergeFpd(originDevice, fpdDevice, Device.class);
58+
}
59+
5460
private <T> T mergeFpd(T original, ObjectNode fpd, Class<T> tClass) {
5561
if (fpd == null || fpd.isNull() || fpd.isMissingNode()) {
5662
return original;

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

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

3+
import com.iab.openrtb.request.Device;
34
import com.iab.openrtb.request.User;
45
import io.vertx.core.Future;
56
import org.prebid.server.auction.aliases.BidderAliases;
67
import org.prebid.server.auction.model.AuctionContext;
78
import org.prebid.server.auction.model.BidderPrivacyResult;
9+
import org.apache.commons.lang3.tuple.Pair;
810

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

2426
public Future<List<BidderPrivacyResult>> mask(AuctionContext auctionContext,
25-
Map<String, User> bidderToUser,
27+
Map<String, Pair<User, Device>> bidderToUserAndDevice,
2628
BidderAliases aliases) {
2729

28-
final List<BidderPrivacyResult> initialResults = bidderToUser.entrySet().stream()
30+
final List<BidderPrivacyResult> initialResults = bidderToUserAndDevice.entrySet().stream()
2931
.map(entry -> BidderPrivacyResult.builder()
3032
.requestBidder(entry.getKey())
31-
.user(entry.getValue())
32-
.device(auctionContext.getBidRequest().getDevice())
33+
.user(entry.getValue().getLeft())
34+
.device(entry.getValue().getRight())
3335
.build())
3436
.toList();
3537

src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtBidderConfigOrtb.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,9 @@ public class ExtBidderConfigOrtb {
2525
* Defines the contract for bidrequest.ext.prebid.bidderconfig.config.ortb2.user
2626
*/
2727
ObjectNode user;
28+
29+
/**
30+
* Defines the contract for bidrequest.ext.prebid.bidderconfig.config.ortb2.device
31+
*/
32+
ObjectNode device;
2833
}

src/test/java/org/prebid/server/auction/ExchangeServiceTest.java

Lines changed: 50 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -322,10 +322,12 @@ public void setUp() {
322322

323323
given(privacyEnforcementService.mask(any(), argThat(MapUtils::isNotEmpty), any()))
324324
.willAnswer(inv ->
325-
Future.succeededFuture(((Map<String, User>) inv.getArgument(1)).entrySet().stream()
325+
Future.succeededFuture(((Map<String, Pair<User, Device>>) inv.getArgument(1)).entrySet()
326+
.stream()
326327
.map(bidderAndUser -> BidderPrivacyResult.builder()
327328
.requestBidder(bidderAndUser.getKey())
328-
.user(bidderAndUser.getValue())
329+
.user(bidderAndUser.getValue().getLeft())
330+
.device(bidderAndUser.getValue().getRight())
329331
.build())
330332
.toList()));
331333

@@ -2691,12 +2693,12 @@ public void shouldUseConcreteOverGeneralSiteWithExtPrebidBidderConfigIgnoringCas
26912693

26922694
final ObjectNode siteWithPage = mapper.valueToTree(Site.builder().page("testPage").build());
26932695
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
2694-
ExtBidderConfigOrtb.of(siteWithPage, null, null, null));
2696+
ExtBidderConfigOrtb.of(siteWithPage, null, null, null, null));
26952697
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
26962698
singletonList("SoMeBiDdEr"), extBidderConfig);
26972699
final ObjectNode siteWithDomain = mapper.valueToTree(Site.builder().domain("notUsed").build());
26982700
final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of(
2699-
ExtBidderConfigOrtb.of(siteWithDomain, null, null, null));
2701+
ExtBidderConfigOrtb.of(siteWithDomain, null, null, null, null));
27002702
final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"),
27012703
allExtBidderConfig);
27022704

@@ -2738,12 +2740,12 @@ public void shouldUseConcreteOverGeneralDoohWithExtPrebidBidderConfig() {
27382740

27392741
final ObjectNode doohWithVenueType = mapper.valueToTree(Dooh.builder().venuetype(List.of("venuetype")).build());
27402742
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
2741-
ExtBidderConfigOrtb.of(null, null, doohWithVenueType, null));
2743+
ExtBidderConfigOrtb.of(null, null, doohWithVenueType, null, null));
27422744
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
27432745
singletonList("someBidder"), extBidderConfig);
27442746
final ObjectNode doohWithDomain = mapper.valueToTree(Dooh.builder().domain("notUsed").build());
27452747
final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of(
2746-
ExtBidderConfigOrtb.of(null, null, doohWithDomain, null));
2748+
ExtBidderConfigOrtb.of(null, null, doohWithDomain, null, null));
27472749
final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(
27482750
singletonList("*"),
27492751
allExtBidderConfig);
@@ -2787,15 +2789,15 @@ public void shouldUseConcreteOverGeneralAppWithExtPrebidBidderConfigIgnoringCase
27872789
final Publisher publisherWithId = Publisher.builder().id("testId").build();
27882790
final ObjectNode appWithPublisherId = mapper.valueToTree(App.builder().publisher(publisherWithId).build());
27892791
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
2790-
ExtBidderConfigOrtb.of(null, appWithPublisherId, null, null));
2792+
ExtBidderConfigOrtb.of(null, appWithPublisherId, null, null, null));
27912793
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
27922794
singletonList("SoMeBiDdEr"), extBidderConfig);
27932795

27942796
final Publisher publisherWithIdAndDomain = Publisher.builder().id("notUsed").domain("notUsed").build();
27952797
final ObjectNode appWithUpdatedPublisher = mapper.valueToTree(
27962798
App.builder().publisher(publisherWithIdAndDomain).build());
27972799
final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of(
2798-
ExtBidderConfigOrtb.of(null, appWithUpdatedPublisher, null, null));
2800+
ExtBidderConfigOrtb.of(null, appWithUpdatedPublisher, null, null, null));
27992801
final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"),
28002802
allExtBidderConfig);
28012803

@@ -2834,13 +2836,13 @@ public void shouldUseConcreteOverGeneralUserWithExtPrebidBidderConfig() {
28342836
givenBidder("someBidder", bidder, givenEmptySeatBid());
28352837
final ObjectNode bidderConfigUser = mapper.valueToTree(User.builder().id("userFromConfig").build());
28362838
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
2837-
ExtBidderConfigOrtb.of(null, null, null, bidderConfigUser));
2839+
ExtBidderConfigOrtb.of(null, null, null, bidderConfigUser, null));
28382840
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
28392841
singletonList("SomMeBiDdEr"), extBidderConfig);
28402842

28412843
final ObjectNode emptyUser = mapper.valueToTree(User.builder().build());
28422844
final ExtBidderConfig allExtBidderConfig = ExtBidderConfig.of(
2843-
ExtBidderConfigOrtb.of(null, null, null, emptyUser));
2845+
ExtBidderConfigOrtb.of(null, null, null, emptyUser, null));
28442846
final ExtRequestPrebidBidderConfig allFpdConfig = ExtRequestPrebidBidderConfig.of(singletonList("*"),
28452847
allExtBidderConfig);
28462848
final User requestUser = User.builder().id("erased").buyeruid("testBuyerId").build();
@@ -2870,6 +2872,44 @@ public void shouldUseConcreteOverGeneralUserWithExtPrebidBidderConfig() {
28702872
.containsOnly(mergedUser);
28712873
}
28722874

2875+
@Test
2876+
public void shouldUseBidderSpecificDeviceDataInBidderRequest() {
2877+
// given
2878+
final Bidder<?> bidder = mock(Bidder.class);
2879+
givenBidder("someBidder", bidder, givenEmptySeatBid());
2880+
2881+
final ObjectNode deviceWithMakeAndModel = mapper.valueToTree(
2882+
Device.builder().make("TestMake_001").model("TestModel_001").build());
2883+
final ExtBidderConfig extBidderConfig = ExtBidderConfig.of(
2884+
ExtBidderConfigOrtb.of(null, null, null, null, deviceWithMakeAndModel));
2885+
final ExtRequestPrebidBidderConfig concreteFpdConfig = ExtRequestPrebidBidderConfig.of(
2886+
singletonList("someBidder"), extBidderConfig);
2887+
final Device requestDevice = Device.builder().build();
2888+
final ExtRequestPrebid extRequestPrebid = ExtRequestPrebid.builder()
2889+
.bidderconfig(singletonList(concreteFpdConfig))
2890+
.build();
2891+
final BidRequest bidRequest = givenBidRequest(givenSingleImp(singletonMap("someBidder", 1)),
2892+
builder -> builder.device(requestDevice).ext(ExtRequest.of(extRequestPrebid)));
2893+
final Device mergedDevice = Device.builder()
2894+
.make("TestMake_001").model("TestModel_001").build();
2895+
2896+
given(fpdResolver.resolveDevice(any(), any())).willReturn(mergedDevice);
2897+
2898+
// when
2899+
target.holdAuction(givenRequestContext(bidRequest));
2900+
2901+
// then
2902+
final ArgumentCaptor<BidderRequest> bidderRequestCaptor = ArgumentCaptor.forClass(BidderRequest.class);
2903+
verify(httpBidderRequester)
2904+
.requestBids(any(), bidderRequestCaptor.capture(), any(), any(), any(), any(), anyBoolean());
2905+
final List<BidderRequest> capturedBidRequests = bidderRequestCaptor.getAllValues();
2906+
2907+
assertThat(capturedBidRequests)
2908+
.extracting(BidderRequest::getBidRequest)
2909+
.extracting(BidRequest::getDevice)
2910+
.containsOnly(mergedDevice);
2911+
}
2912+
28732913
@Test
28742914
public void shouldAddBuyeridToUserFromRequest() {
28752915
// given

src/test/java/org/prebid/server/auction/FpdResolverTest.java

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.iab.openrtb.request.App;
55
import com.iab.openrtb.request.Content;
66
import com.iab.openrtb.request.Data;
7+
import com.iab.openrtb.request.Device;
78
import com.iab.openrtb.request.Dooh;
89
import com.iab.openrtb.request.Geo;
910
import com.iab.openrtb.request.Publisher;
@@ -17,6 +18,9 @@
1718
import org.prebid.server.json.JsonMerger;
1819
import org.prebid.server.proto.openrtb.ext.request.ExtApp;
1920
import org.prebid.server.proto.openrtb.ext.request.ExtAppPrebid;
21+
import org.prebid.server.proto.openrtb.ext.request.ExtDevice;
22+
import org.prebid.server.proto.openrtb.ext.request.ExtDeviceInt;
23+
import org.prebid.server.proto.openrtb.ext.request.ExtDevicePrebid;
2024
import org.prebid.server.proto.openrtb.ext.request.ExtDooh;
2125
import org.prebid.server.proto.openrtb.ext.request.ExtSite;
2226
import org.prebid.server.proto.openrtb.ext.request.ExtUser;
@@ -134,6 +138,80 @@ public void resolveUserShouldReturnCopyOfUserExtDataIfFPDUserExtDataIsMissing()
134138
assertThat(resultUser.getExt().getData().equals(originExtUserData)).isTrue(); // but the same by value
135139
}
136140

141+
@Test
142+
public void resolveDeviceShouldOverrideFpdFieldsFromFpdDevice() {
143+
// given
144+
final Device originDevice = Device.builder()
145+
.devicetype(1)
146+
.make("original_make")
147+
.model("original_model")
148+
.os("original_os")
149+
.osv("original_osv")
150+
.hwv("original_hwv")
151+
.language("original_language")
152+
.h(1111)
153+
.js(1)
154+
.ip("original_ip")
155+
.build();
156+
157+
final Device fpdDevice = Device.builder()
158+
.devicetype(2)
159+
.make("fpd_make")
160+
.model("fpd_model")
161+
.os("fpd_os")
162+
.osv("fpd_osv")
163+
.hwv("fpd_hwv")
164+
.ip("new_ip")
165+
.build();
166+
167+
// when
168+
final Device resultDevice = target.resolveDevice(originDevice, mapper.valueToTree(fpdDevice));
169+
170+
// then
171+
assertThat(resultDevice).isEqualTo(Device.builder()
172+
.devicetype(2)
173+
.make("fpd_make")
174+
.model("fpd_model")
175+
.os("fpd_os")
176+
.osv("fpd_osv")
177+
.hwv("fpd_hwv")
178+
.language("original_language")
179+
.h(1111)
180+
.js(1)
181+
.ip("new_ip")
182+
.build());
183+
}
184+
185+
@Test
186+
public void resolveDeviceShouldReturnOriginDeviceIfFpdDeviceIsNull() {
187+
assertThat(target.resolveDevice(Device.builder().make("test_make").build(), null))
188+
.isEqualTo(Device.builder().make("test_make").build());
189+
}
190+
191+
@Test
192+
public void resolveDeviceShouldReturnFpdDeviceIfOriginDeviceIsNull() {
193+
assertThat(target.resolveDevice(null, mapper.valueToTree(Device.builder().model("test_model").build())))
194+
.isEqualTo(Device.builder().model("test_model").build());
195+
}
196+
197+
@Test
198+
public void resolveDeviceShouldNotChangeOriginExtDataIfFPDDoesNotHaveExt() {
199+
// given
200+
final Device originDevice = Device.builder()
201+
.ext(ExtDevice.of(1, ExtDevicePrebid.of(ExtDeviceInt.of(10, 20))))
202+
.build();
203+
204+
final Device fpdDevice = Device.builder().build();
205+
206+
// when
207+
final Device resultDevice = target.resolveDevice(originDevice, mapper.valueToTree(fpdDevice));
208+
209+
// then
210+
assertThat(resultDevice).isEqualTo(Device.builder()
211+
.ext(ExtDevice.of(1, ExtDevicePrebid.of(ExtDeviceInt.of(10, 20))))
212+
.build());
213+
}
214+
137215
@Test
138216
public void resolveAppShouldOverrideFpdFieldsFromFpdApp() {
139217
// given

0 commit comments

Comments
 (0)