Skip to content

Commit 6e71d88

Browse files
authored
LI module: Fixing permission logic (#4389)
1 parent e6e13af commit 6e71d88

4 files changed

Lines changed: 141 additions & 76 deletions

File tree

extra/modules/live-intent-omni-channel-identity/README.md

Lines changed: 28 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,45 +2,48 @@
22

33
This module enriches bid requests with user EIDs.
44

5-
The user EIDs to be enriched are configured per partner as part of the LiveIntent HIRO onboarding process. As part of this onboarding process, partners will also be provided with the `identity-resolution-endpoint` URL as well as with the `auth-token`.
5+
The user EIDs to be enriched are configured per partner as part of the LiveIntent HIRO onboarding process. As part of
6+
this onboarding process, partners will also be provided with the `identity-resolution-endpoint` URL as well as with the
7+
`auth-token`.
68

7-
`treatment-rate` is a value between 0.0 and 1.0 (including 0.0 and 1.0) and defines the percentage of requests for which identity enrichment should be performed. This value can be freely picked. We recommend a value between 0.9 and 0.95
9+
`treatment-rate` is a value between 0.0 and 1.0 (including 0.0 and 1.0) and defines the percentage of requests for which
10+
identity enrichment should be performed. This value can be freely picked. We recommend a value between 0.9 and 0.95
811

912
## Configuration
1013

1114
To start using the LiveIntent Omni Channel Identity module you have to enable it and add configuration:
1215

1316
```yaml
1417
hooks:
15-
liveintent-omni-channel-identity:
16-
enabled: true
17-
host-execution-plan: >
18-
{
19-
"endpoints": {
20-
"/openrtb2/auction": {
21-
"stages": {
22-
"processed-auction-request": {
23-
"groups": [
24-
{
25-
"timeout": 100,
26-
"hook-sequence": [
18+
liveintent-omni-channel-identity:
19+
enabled: true
20+
host-execution-plan: >
21+
{
22+
"endpoints": {
23+
"/openrtb2/auction": {
24+
"stages": {
25+
"processed-auction-request": {
26+
"groups": [
2727
{
28-
"module-code": "liveintent-omni-channel-identity",
29-
"hook-impl-code": "liveintent-omni-channel-identity-enrichment-hook"
28+
"timeout": 100,
29+
"hook-sequence": [
30+
{
31+
"module-code": "liveintent-omni-channel-identity",
32+
"hook-impl-code": "liveintent-omni-channel-identity-enrichment-hook"
33+
}
34+
]
3035
}
3136
]
3237
}
33-
]
38+
}
3439
}
3540
}
3641
}
37-
}
38-
}
39-
modules:
40-
liveintent-omni-channel-identity:
41-
request-timeout-ms: 2000
42-
identity-resolution-endpoint: "https://liveintent.com/idx"
43-
auth-token: "secret-token"
44-
treatment-rate: 0.9
42+
modules:
43+
liveintent-omni-channel-identity:
44+
request-timeout-ms: 2000
45+
identity-resolution-endpoint: "https://u.liveintent.com/idx"
46+
auth-token: "secret-token"
47+
treatment-rate: 0.9
4548
```
4649

extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java

Lines changed: 86 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import org.apache.commons.collections4.CollectionUtils;
1111
import org.apache.commons.collections4.ListUtils;
1212
import org.apache.commons.collections4.SetUtils;
13+
import org.apache.commons.lang3.StringUtils;
1314
import org.prebid.server.activity.Activity;
1415
import org.prebid.server.activity.ComponentType;
1516
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
@@ -58,6 +59,8 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements
5859

5960
private static final String CODE = "liveintent-omni-channel-identity-enrichment-hook";
6061

62+
private static final String INSERTER = "s2s.liveintent.com";
63+
6164
private final LiveIntentOmniChannelProperties config;
6265
private final JacksonMapper mapper;
6366
private final HttpClient httpClient;
@@ -161,7 +164,18 @@ private MultiMap headers() {
161164
}
162165

163166
private IdResResponse processResponse(HttpClientResponse response) {
164-
return mapper.decodeValue(response.getBody(), IdResResponse.class);
167+
final IdResResponse res = mapper.decodeValue(response.getBody(), IdResResponse.class);
168+
final List<Eid> eids = res.getEids();
169+
170+
if (CollectionUtils.isEmpty(eids)) {
171+
return res;
172+
}
173+
174+
final List<Eid> modifiedEids = eids.stream()
175+
.map(eid -> eid.toBuilder().inserter(INSERTER).build())
176+
.toList();
177+
178+
return IdResResponse.of(modifiedEids);
165179
}
166180

167181
private static Future<InvocationResult<AuctionRequestPayload>> noAction() {
@@ -189,80 +203,109 @@ private InvocationResultImpl<AuctionRequestPayload> update(IdResResponse resolut
189203
}
190204

191205
private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayload, List<Eid> resolvedEids) {
192-
final List<Eid> eids = ListUtils.emptyIfNull(resolvedEids);
193-
final BidRequest bidRequest = updateAllowedBidders(requestPayload.bidRequest(), resolvedEids);
194-
final User updatedUser = Optional.ofNullable(bidRequest.getUser())
195-
.map(user -> user.toBuilder().eids(ListUtil.union(ListUtils.emptyIfNull(user.getEids()), eids)))
196-
.orElseGet(() -> User.builder().eids(eids))
197-
.build();
206+
return CollectionUtils.isNotEmpty(resolvedEids)
207+
? AuctionRequestPayloadImpl.of(updateBidRequest(requestPayload.bidRequest(), resolvedEids))
208+
: requestPayload;
209+
}
198210

199-
return AuctionRequestPayloadImpl.of(bidRequest.toBuilder().user(updatedUser).build());
211+
private BidRequest updateBidRequest(BidRequest bidRequest, List<Eid> resolvedEids) {
212+
return bidRequest.toBuilder()
213+
.ext(updateExtRequest(bidRequest.getExt(), resolvedEids))
214+
.user(updateUser(bidRequest.getUser(), resolvedEids))
215+
.build();
200216
}
201217

202-
private BidRequest updateAllowedBidders(BidRequest bidRequest, List<Eid> resolvedEids) {
203-
if (CollectionUtils.isEmpty(targetBidders) || CollectionUtils.isEmpty(resolvedEids)) {
204-
return bidRequest;
205-
}
218+
private ExtRequest updateExtRequest(ExtRequest ext, List<Eid> resolvedEids) {
219+
final Set<String> uniqueSources = CollectionUtils.emptyIfNull(resolvedEids).stream()
220+
.map(Eid::getSource)
221+
.filter(StringUtils::isNotEmpty)
222+
.collect(Collectors.toSet());
206223

207-
final ExtRequest ext = bidRequest.getExt();
208224
final ExtRequestPrebid extPrebid = ext != null ? ext.getPrebid() : null;
209225
final ExtRequestPrebidData extPrebidData = extPrebid != null ? extPrebid.getData() : null;
226+
final List<ExtRequestPrebidDataEidPermissions> eidPermissions =
227+
extPrebidData != null ? extPrebidData.getEidPermissions() : null;
210228

211-
final List<ExtRequestPrebidDataEidPermissions> existingPerms = extPrebidData != null
212-
? extPrebidData.getEidPermissions()
213-
: null;
214-
215-
if (CollectionUtils.isEmpty(existingPerms)) {
216-
return bidRequest;
217-
}
229+
final List<ExtRequestPrebidDataEidPermissions> modifiedEidPermissions = CollectionUtils.isEmpty(eidPermissions)
230+
? createEidPermissions(uniqueSources)
231+
: modifyEidPermissions(eidPermissions, uniqueSources);
218232

219233
final ExtRequestPrebid updatedExtPrebid = Optional.ofNullable(extPrebid)
220234
.map(ExtRequestPrebid::toBuilder)
221235
.orElseGet(ExtRequestPrebid::builder)
222-
.data(updatePrebidData(extPrebidData, resolvedEids))
236+
.data(updatePrebidData(extPrebidData, modifiedEidPermissions))
223237
.build();
224238

225239
final ExtRequest updatedExtRequest = ExtRequest.of(updatedExtPrebid);
226240
if (ext != null) {
227241
mapper.fillExtension(updatedExtRequest, ext.getProperties());
228242
}
229243

230-
return bidRequest.toBuilder().ext(updatedExtRequest).build();
244+
return updatedExtRequest;
231245
}
232246

233-
private ExtRequestPrebidData updatePrebidData(ExtRequestPrebidData extPrebidData, List<Eid> resolvedEids) {
234-
final List<String> originalBidders = extPrebidData != null ? extPrebidData.getBidders() : null;
247+
private static User updateUser(User user, List<Eid> resolvedEids) {
248+
final List<Eid> updatedEids = Optional.ofNullable(user)
249+
.map(User::getEids)
250+
.map(eids -> ListUtil.union(eids, resolvedEids))
251+
.orElse(resolvedEids);
235252

236-
final Set<String> resolvedSources = resolvedEids.stream()
237-
.map(Eid::getSource)
238-
.collect(Collectors.toSet());
253+
return Optional.ofNullable(user)
254+
.map(User::toBuilder)
255+
.orElseGet(User::builder)
256+
.eids(updatedEids)
257+
.build();
258+
}
239259

240-
final List<ExtRequestPrebidDataEidPermissions> updatedPermissions = extPrebidData.getEidPermissions().stream()
241-
.map(permission -> restrictEidPermission(permission, resolvedSources))
242-
.filter(Objects::nonNull)
260+
private List<ExtRequestPrebidDataEidPermissions> createEidPermissions(Set<String> sources) {
261+
return sources.stream()
262+
.map(source -> ExtRequestPrebidDataEidPermissions.builder()
263+
.source(source)
264+
.inserter(INSERTER)
265+
.bidders(targetBidders.stream().toList())
266+
.build())
243267
.toList();
268+
}
244269

245-
return ExtRequestPrebidData.of(originalBidders, updatedPermissions);
270+
private List<ExtRequestPrebidDataEidPermissions> modifyEidPermissions(
271+
List<ExtRequestPrebidDataEidPermissions> eidPermissions,
272+
Set<String> sources) {
273+
final List<ExtRequestPrebidDataEidPermissions> modifiedEidPermissions = eidPermissions.stream()
274+
.map(it -> updateEidPermission(it, sources))
275+
.filter(Objects::nonNull)
276+
.toList();
277+
final List<ExtRequestPrebidDataEidPermissions> defaultEidPermissions = createEidPermissions(sources);
278+
return ListUtils.union(modifiedEidPermissions, defaultEidPermissions);
246279
}
247280

248-
private ExtRequestPrebidDataEidPermissions restrictEidPermission(ExtRequestPrebidDataEidPermissions permission,
249-
Set<String> resolvedSources) {
281+
private ExtRequestPrebidData updatePrebidData(ExtRequestPrebidData extPrebidData,
282+
List<ExtRequestPrebidDataEidPermissions> eidPermissions) {
283+
284+
final List<String> originalBidders = extPrebidData != null ? extPrebidData.getBidders() : null;
285+
286+
return ExtRequestPrebidData.of(originalBidders, eidPermissions);
287+
}
250288

251-
if (!resolvedSources.contains(permission.getSource())) {
252-
return permission;
289+
private ExtRequestPrebidDataEidPermissions updateEidPermission(ExtRequestPrebidDataEidPermissions eidPermission,
290+
Set<String> sources) {
291+
if (!sources.contains(eidPermission.getSource()) || !INSERTER.equals(eidPermission.getInserter())) {
292+
return eidPermission;
253293
}
254294

255-
final List<String> finalBidders = ListUtils.emptyIfNull(permission.getBidders()).stream()
295+
final List<String> allowedBidders = ListUtils.emptyIfNull(eidPermission.getBidders());
296+
final List<String> finalBidders = allowedBidders.stream()
256297
.filter(targetBidders::contains)
257298
.toList();
258299

259-
return CollectionUtils.isEmpty(finalBidders)
260-
? null
261-
: ExtRequestPrebidDataEidPermissions
262-
.builder()
263-
.bidders(finalBidders)
264-
.source(permission.getSource())
265-
.build();
300+
if (CollectionUtils.isEmpty(allowedBidders) || allowedBidders.contains("*")) {
301+
return eidPermission.toBuilder().bidders(targetBidders.stream().toList()).build();
302+
}
303+
304+
if (CollectionUtils.isEmpty(finalBidders)) {
305+
return null;
306+
}
307+
308+
return eidPermission.toBuilder().bidders(finalBidders).build();
266309
}
267310

268311
@Override

extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
3333
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidData;
3434
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions;
35+
import org.prebid.server.util.ListUtil;
3536
import org.prebid.server.vertx.httpclient.HttpClient;
3637
import org.prebid.server.vertx.httpclient.model.HttpClientResponse;
3738

@@ -77,6 +78,8 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest {
7778

7879
private Set<String> configuredBidders;
7980

81+
private ExtRequestPrebidDataEidPermissions defaultPermissions;
82+
8083
@BeforeEach
8184
public void setUp() {
8285
configuredBidders = Set.of("bidder1", "bidder2");
@@ -86,6 +89,12 @@ public void setUp() {
8689
given(properties.getTreatmentRate()).willReturn(1.0f);
8790
given(properties.getTargetBidders()).willReturn(configuredBidders);
8891

92+
defaultPermissions = ExtRequestPrebidDataEidPermissions.builder()
93+
.inserter("s2s.liveintent.com")
94+
.bidders(configuredBidders.stream().toList())
95+
.source("liveintent.com")
96+
.build();
97+
8998
target = new LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(
9099
properties, userFpdActivityMask, MAPPER, httpClient, 0.01d);
91100
}
@@ -248,6 +257,7 @@ public void callShouldEnrichUserEidsWithRequestedEids() {
248257
final Eid expectedEid = Eid.builder()
249258
.source("liveintent.com")
250259
.uids(singletonList(Uid.builder().id("id2").atype(3).build()))
260+
.matcher("liveintent.com")
251261
.build();
252262

253263
final String responseBody = MAPPER.encodeToString(IdResResponse.of(List.of(expectedEid)));
@@ -273,7 +283,7 @@ public void callShouldEnrichUserEidsWithRequestedEids() {
273283
.extracting(AuctionRequestPayload::bidRequest)
274284
.extracting(BidRequest::getUser)
275285
.extracting(User::getEids)
276-
.isEqualTo(List.of(givenEid, expectedEid));
286+
.isEqualTo(List.of(givenEid, expectedEid.toBuilder().inserter("s2s.liveintent.com").build()));
277287

278288
verify(httpClient).post(
279289
eq("https://test.com/idres"),
@@ -290,6 +300,7 @@ public void callShouldCreateUserAndUseRequestedEidsWhenUserIsAbsent() {
290300
final Eid expectedEid = Eid.builder()
291301
.source("liveintent.com")
292302
.uids(singletonList(Uid.builder().id("id2").atype(3).build()))
303+
.matcher("liveintent.com")
293304
.build();
294305

295306
final String responseBody = MAPPER.encodeToString(IdResResponse.of(List.of(expectedEid)));
@@ -315,7 +326,7 @@ public void callShouldCreateUserAndUseRequestedEidsWhenUserIsAbsent() {
315326
.extracting(AuctionRequestPayload::bidRequest)
316327
.extracting(BidRequest::getUser)
317328
.extracting(User::getEids)
318-
.isEqualTo(List.of(expectedEid));
329+
.isEqualTo(List.of(expectedEid.toBuilder().inserter("s2s.liveintent.com").build()));
319330

320331
verify(httpClient).post(
321332
eq("https://test.com/idres"),
@@ -384,15 +395,18 @@ public void shouldRestrictExistingEidPermissionsByIntersectionAndKeepGlobalBidde
384395

385396
final ExtRequestPrebidDataEidPermissions otherBidder = ExtRequestPrebidDataEidPermissions.builder()
386397
.source("some.other-source.com")
398+
.inserter("some.other-inserter.com")
387399
.bidders(singletonList("bidderY"))
388400
.build();
389401

390402
final ExtRequestPrebidDataEidPermissions liBidder2 = ExtRequestPrebidDataEidPermissions.builder()
391403
.source("liveintent.com")
404+
.inserter("s2s.liveintent.com")
392405
.bidders(singletonList("bidder2"))
393406
.build();
394407
final ExtRequestPrebidDataEidPermissions liBidder23 = ExtRequestPrebidDataEidPermissions.builder()
395408
.source("liveintent.com")
409+
.inserter("s2s.liveintent.com")
396410
.bidders(List.of("bidder2", "bidder3"))
397411
.build();
398412

@@ -407,7 +421,7 @@ public void shouldRestrictExistingEidPermissionsByIntersectionAndKeepGlobalBidde
407421

408422
final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(
409423
List.of("bidderX"),
410-
List.of(otherBidder, liBidder2));
424+
ListUtil.union(List.of(otherBidder, liBidder2), List.of(defaultPermissions)));
411425

412426
final Eid expectedEid = Eid.builder().source("liveintent.com").build();
413427

@@ -450,10 +464,12 @@ public void shouldNotAddNewEidPermissionsOrModifyGlobalBiddersWhenSourceNotPrese
450464
final User givenUser = User.builder().eids(singletonList(givenEid)).build();
451465
final ExtRequestPrebidDataEidPermissions bidder1 = ExtRequestPrebidDataEidPermissions.builder()
452466
.source("some.other-source.com")
467+
.inserter("some.other-inserter.com")
453468
.bidders(singletonList("bidder3"))
454469
.build();
455470
final ExtRequestPrebidDataEidPermissions bidder2 = ExtRequestPrebidDataEidPermissions.builder()
456471
.source("some.source.com")
472+
.inserter("s2s.liveintent.com")
457473
.bidders(singletonList("bidder3"))
458474
.build();
459475

@@ -467,7 +483,8 @@ public void shouldNotAddNewEidPermissionsOrModifyGlobalBiddersWhenSourceNotPrese
467483
.build()))
468484
.build();
469485

470-
final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(List.of("bidder3"), bidders);
486+
final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(List.of("bidder3"),
487+
ListUtil.union(bidders, List.of(defaultPermissions)));
471488

472489
final Eid expectedEid = Eid.builder().source("liveintent.com").build();
473490

@@ -514,6 +531,7 @@ public void shouldRemovePermissionWhenIntersectionIsEmpty() {
514531
List.of(
515532
ExtRequestPrebidDataEidPermissions.builder()
516533
.source("liveintent.com")
534+
.inserter("s2s.liveintent.com")
517535
.bidders(singletonList("not-allowed"))
518536
.build(),
519537
ExtRequestPrebidDataEidPermissions.builder()
@@ -548,9 +566,10 @@ public void shouldRemovePermissionWhenIntersectionIsEmpty() {
548566
final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(
549567
List.of("bidderGlobal"),
550568
List.of(ExtRequestPrebidDataEidPermissions.builder()
551-
.source("keep.com")
552-
.bidders(singletonList("bidderGlobal"))
553-
.build()));
569+
.source("keep.com")
570+
.bidders(singletonList("bidderGlobal"))
571+
.build(),
572+
defaultPermissions));
554573

555574
assertThat(result.status()).isEqualTo(InvocationStatus.success);
556575
assertThat(result.payloadUpdate().apply(AuctionRequestPayloadImpl.of(givenBidRequest)))

0 commit comments

Comments
 (0)