diff --git a/extra/modules/live-intent-omni-channel-identity/README.md b/extra/modules/live-intent-omni-channel-identity/README.md index be5ad801ec1..a8a3ce5b113 100644 --- a/extra/modules/live-intent-omni-channel-identity/README.md +++ b/extra/modules/live-intent-omni-channel-identity/README.md @@ -2,9 +2,12 @@ This module enriches bid requests with user EIDs. -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`. +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`. -`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 +`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 ## Configuration @@ -12,35 +15,35 @@ To start using the LiveIntent Omni Channel Identity module you have to enable it ```yaml hooks: - liveintent-omni-channel-identity: - enabled: true - host-execution-plan: > - { - "endpoints": { - "/openrtb2/auction": { - "stages": { - "processed-auction-request": { - "groups": [ - { - "timeout": 100, - "hook-sequence": [ + liveintent-omni-channel-identity: + enabled: true + host-execution-plan: > + { + "endpoints": { + "/openrtb2/auction": { + "stages": { + "processed-auction-request": { + "groups": [ { - "module-code": "liveintent-omni-channel-identity", - "hook-impl-code": "liveintent-omni-channel-identity-enrichment-hook" + "timeout": 100, + "hook-sequence": [ + { + "module-code": "liveintent-omni-channel-identity", + "hook-impl-code": "liveintent-omni-channel-identity-enrichment-hook" + } + ] } ] } - ] + } } } } - } - } - modules: - liveintent-omni-channel-identity: - request-timeout-ms: 2000 - identity-resolution-endpoint: "https://liveintent.com/idx" - auth-token: "secret-token" - treatment-rate: 0.9 + modules: + liveintent-omni-channel-identity: + request-timeout-ms: 2000 + identity-resolution-endpoint: "https://u.liveintent.com/idx" + auth-token: "secret-token" + treatment-rate: 0.9 ``` diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/FullSource.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/FullSource.java new file mode 100644 index 00000000000..ac6d393755c --- /dev/null +++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/FullSource.java @@ -0,0 +1,11 @@ +package org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model; + +import lombok.Value; + +@Value(staticConstructor = "of") +public class FullSource { + + String source; + + String inserter; +} diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java index 3afdb146018..e03698f6a62 100644 --- a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java +++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/hooks/LiveIntentOmniChannelIdentityProcessedAuctionRequestHook.java @@ -23,6 +23,7 @@ import org.prebid.server.hooks.execution.v1.analytics.ResultImpl; import org.prebid.server.hooks.execution.v1.analytics.TagsImpl; import org.prebid.server.hooks.execution.v1.auction.AuctionRequestPayloadImpl; +import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.FullSource; import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.IdResResponse; import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.model.config.LiveIntentOmniChannelProperties; import org.prebid.server.hooks.modules.liveintent.omni.channel.identity.v1.LiveIntentOmniChannelIdentityModule; @@ -44,6 +45,7 @@ import org.prebid.server.vertx.httpclient.HttpClient; import org.prebid.server.vertx.httpclient.model.HttpClientResponse; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -58,6 +60,8 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements private static final String CODE = "liveintent-omni-channel-identity-enrichment-hook"; + private static final String INSERTER = "s2s.liveintent.com"; + private final LiveIntentOmniChannelProperties config; private final JacksonMapper mapper; private final HttpClient httpClient; @@ -161,7 +165,11 @@ private MultiMap headers() { } private IdResResponse processResponse(HttpClientResponse response) { - return mapper.decodeValue(response.getBody(), IdResResponse.class); + final IdResResponse res = mapper.decodeValue(response.getBody(), IdResResponse.class); + final List eids = res.getEids().stream() + .map(eid -> eid.toBuilder().inserter(INSERTER).build()) + .toList(); + return IdResResponse.of(eids); } private static Future> noAction() { @@ -190,6 +198,9 @@ private InvocationResultImpl update(IdResResponse resolut private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayload, List resolvedEids) { final List eids = ListUtils.emptyIfNull(resolvedEids); + if (CollectionUtils.isEmpty(eids)) { + return requestPayload; + } final BidRequest bidRequest = updateAllowedBidders(requestPayload.bidRequest(), resolvedEids); final User updatedUser = Optional.ofNullable(bidRequest.getUser()) .map(user -> user.toBuilder().eids(ListUtil.union(ListUtils.emptyIfNull(user.getEids()), eids))) @@ -200,7 +211,7 @@ private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayloa } private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolvedEids) { - if (CollectionUtils.isEmpty(targetBidders) || CollectionUtils.isEmpty(resolvedEids)) { + if (CollectionUtils.isEmpty(targetBidders)) { return bidRequest; } @@ -208,14 +219,6 @@ private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolve final ExtRequestPrebid extPrebid = ext != null ? ext.getPrebid() : null; final ExtRequestPrebidData extPrebidData = extPrebid != null ? extPrebid.getData() : null; - final List existingPerms = extPrebidData != null - ? extPrebidData.getEidPermissions() - : null; - - if (CollectionUtils.isEmpty(existingPerms)) { - return bidRequest; - } - final ExtRequestPrebid updatedExtPrebid = Optional.ofNullable(extPrebid) .map(ExtRequestPrebid::toBuilder) .orElseGet(ExtRequestPrebid::builder) @@ -233,36 +236,56 @@ private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolve private ExtRequestPrebidData updatePrebidData(ExtRequestPrebidData extPrebidData, List resolvedEids) { final List originalBidders = extPrebidData != null ? extPrebidData.getBidders() : null; - final Set resolvedSources = resolvedEids.stream() - .map(Eid::getSource) + final Set resolvedSources = resolvedEids.stream() + .map(eid -> FullSource.of(eid.getSource(), eid.getInserter())) .collect(Collectors.toSet()); - final List updatedPermissions = extPrebidData.getEidPermissions().stream() + final List eidPermissions = extPrebidData != null + ? ListUtils.emptyIfNull(extPrebidData.getEidPermissions()) + : Collections.emptyList(); + + final List updatedPermissions = eidPermissions.stream() .map(permission -> restrictEidPermission(permission, resolvedSources)) .filter(Objects::nonNull) .toList(); - return ExtRequestPrebidData.of(originalBidders, updatedPermissions); + return CollectionUtils.isEmpty(updatedPermissions) + ? ExtRequestPrebidData.of( + originalBidders, + resolvedEids.stream() + .map(eid -> + ExtRequestPrebidDataEidPermissions.builder() + .bidders(targetBidders.stream().toList()) + .inserter(INSERTER) + .source(eid.getSource()) + .build()) + .toList()) + : ExtRequestPrebidData.of(originalBidders, updatedPermissions); } private ExtRequestPrebidDataEidPermissions restrictEidPermission(ExtRequestPrebidDataEidPermissions permission, - Set resolvedSources) { + Set resolvedSources) { - if (!resolvedSources.contains(permission.getSource())) { + final FullSource permFullSource = FullSource.of(permission.getSource(), permission.getInserter()); + if (!resolvedSources.contains(permFullSource)) { return permission; } + final List permittedBidders = ListUtils.emptyIfNull(permission.getBidders()); + + if (CollectionUtils.isEmpty(permittedBidders) || permittedBidders.contains("*")) { + return permission.toBuilder() + .bidders(targetBidders.stream().toList()) + .build(); + } + final List finalBidders = ListUtils.emptyIfNull(permission.getBidders()).stream() .filter(targetBidders::contains) .toList(); return CollectionUtils.isEmpty(finalBidders) ? null - : ExtRequestPrebidDataEidPermissions - .builder() - .bidders(finalBidders) - .source(permission.getSource()) - .build(); + : permission.toBuilder().bidders(finalBidders).build(); } @Override diff --git a/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java index 4dfaa8c8df4..af1f3ddb807 100644 --- a/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java +++ b/extra/modules/live-intent-omni-channel-identity/src/test/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/v1/LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest.java @@ -248,6 +248,7 @@ public void callShouldEnrichUserEidsWithRequestedEids() { final Eid expectedEid = Eid.builder() .source("liveintent.com") .uids(singletonList(Uid.builder().id("id2").atype(3).build())) + .matcher("liveintent.com") .build(); final String responseBody = MAPPER.encodeToString(IdResResponse.of(List.of(expectedEid))); @@ -273,7 +274,7 @@ public void callShouldEnrichUserEidsWithRequestedEids() { .extracting(AuctionRequestPayload::bidRequest) .extracting(BidRequest::getUser) .extracting(User::getEids) - .isEqualTo(List.of(givenEid, expectedEid)); + .isEqualTo(List.of(givenEid, expectedEid.toBuilder().inserter("s2s.liveintent.com").build())); verify(httpClient).post( eq("https://test.com/idres"), @@ -290,6 +291,7 @@ public void callShouldCreateUserAndUseRequestedEidsWhenUserIsAbsent() { final Eid expectedEid = Eid.builder() .source("liveintent.com") .uids(singletonList(Uid.builder().id("id2").atype(3).build())) + .matcher("liveintent.com") .build(); final String responseBody = MAPPER.encodeToString(IdResResponse.of(List.of(expectedEid))); @@ -315,7 +317,7 @@ public void callShouldCreateUserAndUseRequestedEidsWhenUserIsAbsent() { .extracting(AuctionRequestPayload::bidRequest) .extracting(BidRequest::getUser) .extracting(User::getEids) - .isEqualTo(List.of(expectedEid)); + .isEqualTo(List.of(expectedEid.toBuilder().inserter("s2s.liveintent.com").build())); verify(httpClient).post( eq("https://test.com/idres"), @@ -384,15 +386,18 @@ public void shouldRestrictExistingEidPermissionsByIntersectionAndKeepGlobalBidde final ExtRequestPrebidDataEidPermissions otherBidder = ExtRequestPrebidDataEidPermissions.builder() .source("some.other-source.com") + .inserter("some.other-inserter.com") .bidders(singletonList("bidderY")) .build(); final ExtRequestPrebidDataEidPermissions liBidder2 = ExtRequestPrebidDataEidPermissions.builder() .source("liveintent.com") + .inserter("s2s.liveintent.com") .bidders(singletonList("bidder2")) .build(); final ExtRequestPrebidDataEidPermissions liBidder23 = ExtRequestPrebidDataEidPermissions.builder() .source("liveintent.com") + .inserter("s2s.liveintent.com") .bidders(List.of("bidder2", "bidder3")) .build(); @@ -450,10 +455,12 @@ public void shouldNotAddNewEidPermissionsOrModifyGlobalBiddersWhenSourceNotPrese final User givenUser = User.builder().eids(singletonList(givenEid)).build(); final ExtRequestPrebidDataEidPermissions bidder1 = ExtRequestPrebidDataEidPermissions.builder() .source("some.other-source.com") + .inserter("some.other-inserter.com") .bidders(singletonList("bidder3")) .build(); final ExtRequestPrebidDataEidPermissions bidder2 = ExtRequestPrebidDataEidPermissions.builder() .source("some.source.com") + .inserter("s2s.liveintent.com") .bidders(singletonList("bidder3")) .build(); @@ -514,6 +521,7 @@ public void shouldRemovePermissionWhenIntersectionIsEmpty() { List.of( ExtRequestPrebidDataEidPermissions.builder() .source("liveintent.com") + .inserter("s2s.liveintent.com") .bidders(singletonList("not-allowed")) .build(), ExtRequestPrebidDataEidPermissions.builder() diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidDataEidPermissions.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidDataEidPermissions.java index 2373c40e886..0e717619cdb 100644 --- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidDataEidPermissions.java +++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/ExtRequestPrebidDataEidPermissions.java @@ -7,7 +7,7 @@ import java.util.List; @Value -@Builder +@Builder(toBuilder = true) public class ExtRequestPrebidDataEidPermissions { /**