Skip to content

Commit b7698be

Browse files
authored
Record geo lookup results in auction request and context for subsequent usage (prebid#888)
* Divide privacy data extraction (from request) and usage into separate steps Enrich bid request with country and possibly masked ip address coming from privacy enforcement` Move request type metric detection to *RequestFactory classes Uncomment test for further fixing Fix broken tests * Tests for new functionality * Misc cleanup
1 parent 1e863a3 commit b7698be

File tree

77 files changed

+1677
-1073
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+1677
-1073
lines changed

docs/endpoints/event.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,4 @@ This endpoint is used to notify about event and do request for tracking pixel if
1919
- `x` : Disables or enables analytics. Allowed values: `1` to enable analytics or `0` to disable. `1` is default.
2020
### Sample request
2121

22-
`GET http://prebid.site.com/event?type=win&bidid=12345&a=1111&bidder=rubicon&f=b`
22+
`GET http://prebid.site.com/event?type=win&bidid=12345&a=1111&bidder=rubicon&f=b`

src/main/java/com/iab/openrtb/request/Data.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.iab.openrtb.request;
22

33
import com.fasterxml.jackson.databind.node.ObjectNode;
4-
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
55
import lombok.Value;
66

77
import java.util.List;
@@ -14,14 +14,18 @@
1414
* The specific data providers in use should be published by the exchange
1515
* <em>a priori</em> to its bidders.
1616
*/
17+
@Builder
1718
@Value
18-
@AllArgsConstructor(staticName = "of")
1919
public class Data {
2020

21-
/** Exchange-specific ID for the data provider. */
21+
/**
22+
* Exchange-specific ID for the data provider.
23+
*/
2224
String id;
2325

24-
/** Exchange-specific name for the data provider. */
26+
/**
27+
* Exchange-specific name for the data provider.
28+
*/
2529
String name;
2630

2731
/**
@@ -30,6 +34,8 @@ public class Data {
3034
*/
3135
List<Segment> segment;
3236

33-
/** Placeholder for exchange-specific extensions to OpenRTB. */
37+
/**
38+
* Placeholder for exchange-specific extensions to OpenRTB.
39+
*/
3440
ObjectNode ext;
3541
}

src/main/java/com/iab/openrtb/request/Deal.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@
1212
* indicates that this impression is available under the terms of that deal.
1313
* Refer to Section 7.3 for more details.
1414
*/
15-
@Builder
15+
@Builder(toBuilder = true)
1616
@Value
1717
public class Deal {
1818

19-
/** A unique identifier for the direct deal. (required) */
19+
/**
20+
* A unique identifier for the direct deal. (required)
21+
*/
2022
String id;
2123

22-
/** Minimum bid for this impression expressed in CPM. */
24+
/**
25+
* Minimum bid for this impression expressed in CPM.
26+
*/
2327
float bidfloor;
2428

2529
/**
@@ -50,6 +54,8 @@ public class Deal {
5054
*/
5155
List<String> wadomain;
5256

53-
/** Placeholder for exchange-specific extensions to OpenRTB. */
57+
/**
58+
* Placeholder for exchange-specific extensions to OpenRTB.
59+
*/
5460
ObjectNode ext;
5561
}

src/main/java/com/iab/openrtb/request/Geo.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.iab.openrtb.request;
22

3-
import com.fasterxml.jackson.databind.node.ObjectNode;
43
import lombok.Builder;
54
import lombok.Value;
5+
import org.prebid.server.proto.openrtb.ext.request.ExtGeo;
66

77
/**
88
* This object encapsulates various methods for specifying a geographic location.
@@ -98,5 +98,5 @@ public class Geo {
9898
/**
9999
* Placeholder for exchange-specific extensions to OpenRTB.
100100
*/
101-
ObjectNode ext;
101+
ExtGeo ext;
102102
}

src/main/java/com/iab/openrtb/request/Segment.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.iab.openrtb.request;
22

33
import com.fasterxml.jackson.databind.node.ObjectNode;
4+
import lombok.Builder;
45
import lombok.Value;
56

67
/**
@@ -10,6 +11,7 @@
1011
* The specific segment names and value options must be published by the
1112
* exchange <em>a priori</em> to its bidders.
1213
*/
14+
@Builder
1315
@Value
1416
public class Segment {
1517

src/main/java/com/iab/openrtb/response/BidResponse.java

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import java.util.Comparator;
88
import java.util.List;
9+
import java.util.Objects;
910

1011
/**
1112
* This object is the top-level bid response object (i.e., the unnamed outer
@@ -63,22 +64,13 @@ public class BidResponse {
6364
ObjectNode ext;
6465

6566
public static final Comparator<BidResponse> COMPARATOR = (left, right) -> {
66-
if (left == null) {
67+
if (Objects.isNull(left)
68+
|| left.getSeatbid().isEmpty()
69+
|| left.getSeatbid().get(0).getBid().isEmpty()
70+
|| Objects.isNull(right)) {
6771
return -1;
6872
}
69-
if (left.getSeatbid().isEmpty()) {
70-
return -1;
71-
}
72-
if (left.getSeatbid().get(0).getBid().isEmpty()) {
73-
return -1;
74-
}
75-
if (right == null) {
76-
return -1;
77-
}
78-
if (right.getSeatbid().isEmpty()) {
79-
return 1;
80-
}
81-
if (right.getSeatbid().get(0).getBid().isEmpty()) {
73+
if (right.getSeatbid().isEmpty() || right.getSeatbid().get(0).getBid().isEmpty()) {
8274
return 1;
8375
}
8476
return left.getSeatbid().get(0).getBid().get(0).getPrice()

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

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.prebid.server.auction.model.Tuple2;
2525
import org.prebid.server.exception.InvalidRequestException;
2626
import org.prebid.server.json.JacksonMapper;
27+
import org.prebid.server.metric.MetricName;
2728
import org.prebid.server.privacy.ccpa.Ccpa;
2829
import org.prebid.server.privacy.gdpr.TcfDefinerService;
2930
import org.prebid.server.proto.openrtb.ext.request.ExtMediaTypePriceGranularity;
@@ -105,9 +106,13 @@ public Future<AuctionContext> fromRequest(RoutingContext routingContext, long st
105106
return Future.failedFuture(new InvalidRequestException("AMP requests require an AMP tag_id"));
106107
}
107108
return createBidRequest(routingContext, tagId)
108-
.compose(bidRequestWithErrors ->
109-
auctionRequestFactory.toAuctionContext(routingContext, bidRequestWithErrors.getLeft(),
110-
bidRequestWithErrors.getRight(), startTime, timeoutResolver));
109+
.compose(bidRequestWithErrors -> auctionRequestFactory.toAuctionContext(
110+
routingContext,
111+
bidRequestWithErrors.getLeft(),
112+
MetricName.amp,
113+
bidRequestWithErrors.getRight(),
114+
startTime,
115+
timeoutResolver));
111116
}
112117

113118
/**
@@ -253,7 +258,7 @@ private BidRequest overrideParameters(BidRequest bidRequest, HttpServerRequest r
253258
String gdprConsent = null;
254259
String ccpaConsent = null;
255260
if (StringUtils.isNotBlank(consentString)) {
256-
gdprConsent = TcfDefinerService.isGdprConsentValid(consentString) ? consentString : null;
261+
gdprConsent = TcfDefinerService.isConsentStringValid(consentString) ? consentString : null;
257262
ccpaConsent = Ccpa.isValid(consentString) ? consentString : null;
258263

259264
if (StringUtils.isAllBlank(gdprConsent, ccpaConsent)) {

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

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import com.iab.openrtb.request.App;
77
import com.iab.openrtb.request.BidRequest;
88
import com.iab.openrtb.request.Device;
9+
import com.iab.openrtb.request.Geo;
910
import com.iab.openrtb.request.Imp;
1011
import com.iab.openrtb.request.Publisher;
1112
import com.iab.openrtb.request.Site;
@@ -32,9 +33,12 @@
3233
import org.prebid.server.exception.UnauthorizedAccountException;
3334
import org.prebid.server.execution.Timeout;
3435
import org.prebid.server.execution.TimeoutFactory;
36+
import org.prebid.server.geolocation.model.GeoInfo;
3537
import org.prebid.server.identity.IdGenerator;
3638
import org.prebid.server.json.JacksonMapper;
3739
import org.prebid.server.log.ConditionalLogger;
40+
import org.prebid.server.metric.MetricName;
41+
import org.prebid.server.privacy.model.PrivacyContext;
3842
import org.prebid.server.proto.openrtb.ext.request.ExtMediaTypePriceGranularity;
3943
import org.prebid.server.proto.openrtb.ext.request.ExtPriceGranularity;
4044
import org.prebid.server.proto.openrtb.ext.request.ExtPublisher;
@@ -98,6 +102,7 @@ public class AuctionRequestFactory {
98102
private final TimeoutFactory timeoutFactory;
99103
private final ApplicationSettings applicationSettings;
100104
private final IdGenerator idGenerator;
105+
private final PrivacyEnforcementService privacyEnforcementService;
101106
private final JacksonMapper mapper;
102107
private final OrtbTypesResolver ortbTypesResolver;
103108

@@ -119,6 +124,7 @@ public AuctionRequestFactory(long maxRequestSize,
119124
TimeoutFactory timeoutFactory,
120125
ApplicationSettings applicationSettings,
121126
IdGenerator idGenerator,
127+
PrivacyEnforcementService privacyEnforcementService,
122128
JacksonMapper mapper) {
123129

124130
this.maxRequestSize = maxRequestSize;
@@ -139,6 +145,7 @@ public AuctionRequestFactory(long maxRequestSize,
139145
this.timeoutFactory = Objects.requireNonNull(timeoutFactory);
140146
this.applicationSettings = Objects.requireNonNull(applicationSettings);
141147
this.idGenerator = Objects.requireNonNull(idGenerator);
148+
this.privacyEnforcementService = Objects.requireNonNull(privacyEnforcementService);
142149
this.mapper = Objects.requireNonNull(mapper);
143150
}
144151

@@ -171,9 +178,13 @@ public Future<AuctionContext> fromRequest(RoutingContext routingContext, long st
171178
}
172179

173180
return updateBidRequest(routingContext, incomingBidRequest)
174-
.compose(bidRequest ->
175-
toAuctionContext(routingContext, bidRequest, errors, startTime,
176-
timeoutResolver));
181+
.compose(bidRequest -> toAuctionContext(
182+
routingContext,
183+
bidRequest,
184+
requestTypeMetric(bidRequest),
185+
errors,
186+
startTime,
187+
timeoutResolver));
177188
}
178189

179190
/**
@@ -183,21 +194,28 @@ public Future<AuctionContext> fromRequest(RoutingContext routingContext, long st
183194
*/
184195
Future<AuctionContext> toAuctionContext(RoutingContext routingContext,
185196
BidRequest bidRequest,
197+
MetricName requestTypeMetric,
186198
List<String> errors,
187199
long startTime,
188200
TimeoutResolver timeoutResolver) {
189201

190202
final Timeout timeout = timeout(bidRequest, startTime, timeoutResolver);
191203

192204
return accountFrom(bidRequest, timeout, routingContext)
193-
.map(account -> AuctionContext.builder()
194-
.routingContext(routingContext)
195-
.uidsCookie(uidsCookieService.parseFromRequest(routingContext))
196-
.bidRequest(enrichBidRequestWithAccountBasedData(bidRequest, account))
197-
.timeout(timeout)
198-
.account(account)
199-
.prebidErrors(errors)
200-
.build());
205+
.compose(account -> privacyEnforcementService.contextFromBidRequest(
206+
bidRequest, account, requestTypeMetric, timeout)
207+
.map(privacyContext -> AuctionContext.builder()
208+
.routingContext(routingContext)
209+
.uidsCookie(uidsCookieService.parseFromRequest(routingContext))
210+
.bidRequest(enrichBidRequestWithAccountAndPrivacyData(
211+
bidRequest, account, privacyContext))
212+
.requestTypeMetric(requestTypeMetric)
213+
.timeout(timeout)
214+
.account(account)
215+
.prebidErrors(errors)
216+
.privacyContext(privacyContext)
217+
.geoInfo(privacyContext.getTcfContext().getGeoInfo())
218+
.build()));
201219
}
202220

203221
/**
@@ -430,14 +448,13 @@ private User populateUser(User user) {
430448
final ExtUser ext = userExtOrNull(user);
431449

432450
if (ext != null) {
433-
final User.UserBuilder builder = user == null ? User.builder() : user.toBuilder();
434-
return builder.ext(ext).build();
451+
return user.toBuilder().ext(ext).build();
435452
}
436453
return null;
437454
}
438455

439456
/**
440-
* Returns {@link ObjectNode} of updated {@link ExtUser} or null if no updates needed.
457+
* Returns updated {@link ExtUser} or null if no updates needed.
441458
*/
442459
private ExtUser userExtOrNull(User user) {
443460
final ExtUser extUser = user != null ? user.getExt() : null;
@@ -571,7 +588,7 @@ private ExtRequestTargeting targetingOrNull(ExtRequestPrebid prebid, Set<BidType
571588
.pricegranularity(populatePriceGranularity(targeting, isPriceGranularityNull,
572589
isPriceGranularityTextual, impMediaTypes))
573590
.mediatypepricegranularity(targeting.getMediatypepricegranularity())
574-
.includewinners(isIncludeWinnersNull ? true : targeting.getIncludewinners())
591+
.includewinners(isIncludeWinnersNull || targeting.getIncludewinners())
575592
.includebidderkeys(isIncludeBidderKeysNull
576593
? !isWinningOnly(prebid.getCache())
577594
: targeting.getIncludebidderkeys())
@@ -838,13 +855,19 @@ private Future<Account> responseForUnknownAccount(String accountId) {
838855
: Future.succeededFuture(Account.empty(accountId));
839856
}
840857

841-
private BidRequest enrichBidRequestWithAccountBasedData(BidRequest bidRequest, Account account) {
858+
private BidRequest enrichBidRequestWithAccountAndPrivacyData(
859+
BidRequest bidRequest, Account account, PrivacyContext privacyContext) {
860+
842861
final ExtRequest requestExt = bidRequest.getExt();
843862
final ExtRequest enrichedRequestExt = enrichExtRequest(requestExt, account);
844863

845-
if (enrichedRequestExt != null) {
864+
final Device device = bidRequest.getDevice();
865+
final Device enrichedDevice = enrichDevice(device, privacyContext);
866+
867+
if (enrichedRequestExt != null || enrichedDevice != null) {
846868
return bidRequest.toBuilder()
847-
.ext(enrichedRequestExt)
869+
.ext(ObjectUtils.defaultIfNull(enrichedRequestExt, requestExt))
870+
.device(ObjectUtils.defaultIfNull(enrichedDevice, device))
848871
.build();
849872
}
850873

@@ -867,4 +890,39 @@ private ExtRequest enrichExtRequest(ExtRequest ext, Account account) {
867890

868891
return null;
869892
}
893+
894+
private Device enrichDevice(Device device, PrivacyContext privacyContext) {
895+
final String ipAddress = privacyContext.getIpAddress();
896+
final String country = getIfNotNull(privacyContext.getTcfContext().getGeoInfo(), GeoInfo::getCountry);
897+
898+
final String ipAddressInRequest = getIfNotNull(device, Device::getIp);
899+
900+
final Geo geo = getIfNotNull(device, Device::getGeo);
901+
final String countryFromRequest = getIfNotNull(geo, Geo::getCountry);
902+
903+
final boolean shouldUpdateIp = ipAddress != null && !Objects.equals(ipAddressInRequest, ipAddress);
904+
final boolean shouldUpdateCountry = country != null && !Objects.equals(countryFromRequest, country);
905+
906+
if (shouldUpdateIp || shouldUpdateCountry) {
907+
final Device.DeviceBuilder deviceBuilder = device != null ? device.toBuilder() : Device.builder();
908+
909+
if (shouldUpdateIp) {
910+
deviceBuilder.ip(ipAddress);
911+
}
912+
913+
if (shouldUpdateCountry) {
914+
final Geo.GeoBuilder geoBuilder = geo != null ? geo.toBuilder() : Geo.builder();
915+
geoBuilder.country(country);
916+
deviceBuilder.geo(geoBuilder.build());
917+
}
918+
919+
return deviceBuilder.build();
920+
}
921+
922+
return null;
923+
}
924+
925+
private static MetricName requestTypeMetric(BidRequest bidRequest) {
926+
return bidRequest.getApp() != null ? MetricName.openrtb2app : MetricName.openrtb2web;
927+
}
870928
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
import java.util.Collection;
7474
import java.util.Collections;
7575
import java.util.Comparator;
76+
import java.util.EnumMap;
7677
import java.util.HashMap;
7778
import java.util.HashSet;
7879
import java.util.Iterator;
@@ -265,6 +266,7 @@ private ExtBidResponse toExtBidResponse(List<BidderResponse> bidderResponses,
265266
Map<String, List<ExtBidderError>> bidErrors) {
266267

267268
final BidRequest bidRequest = auctionContext.getBidRequest();
269+
268270
final ExtResponseDebug extResponseDebug = debugEnabled
269271
? ExtResponseDebug.of(toExtHttpCalls(bidderResponses, cacheResult), bidRequest)
270272
: null;
@@ -985,7 +987,7 @@ private Map<BidType, TargetingKeywordsCreator> keywordsCreatorByBidType(ExtReque
985987
return Collections.emptyMap();
986988
}
987989

988-
final Map<BidType, TargetingKeywordsCreator> result = new HashMap<>();
990+
final Map<BidType, TargetingKeywordsCreator> result = new EnumMap<>(BidType.class);
989991
final int resolvedTruncateAttrChars = resolveTruncateAttrChars(targeting, account);
990992

991993
final ObjectNode banner = mediaTypePriceGranularity.getBanner();

0 commit comments

Comments
 (0)