diff --git a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/LiveIntentOmniChannelProperties.java b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/LiveIntentOmniChannelProperties.java index b6a61b0ca8f..01d37eabda0 100644 --- a/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/LiveIntentOmniChannelProperties.java +++ b/extra/modules/live-intent-omni-channel-identity/src/main/java/org/prebid/server/hooks/modules/liveintent/omni/channel/identity/model/config/LiveIntentOmniChannelProperties.java @@ -2,6 +2,8 @@ import lombok.Data; +import java.util.List; + @Data public final class LiveIntentOmniChannelProperties { @@ -12,4 +14,6 @@ public final class LiveIntentOmniChannelProperties { String authToken; float treatmentRate; + + List targetBidders; } 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 005ac13b223..32b608ce21c 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 @@ -7,6 +7,7 @@ import com.iab.openrtb.request.User; import io.vertx.core.Future; import io.vertx.core.MultiMap; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; import org.prebid.server.activity.Activity; import org.prebid.server.activity.ComponentType; @@ -33,15 +34,24 @@ import org.prebid.server.json.JacksonMapper; import org.prebid.server.log.ConditionalLogger; import org.prebid.server.log.LoggerFactory; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidData; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions; import org.prebid.server.util.HttpUtil; import org.prebid.server.util.ListUtil; +import org.prebid.server.util.StreamUtil; 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; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; +import java.util.stream.Collectors; +import java.util.stream.Stream; public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements ProcessedAuctionRequestHook { @@ -55,6 +65,7 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements private final HttpClient httpClient; private final UserFpdActivityMask userFpdActivityMask; private final double logSamplingRate; + private final List targetBidders; public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(LiveIntentOmniChannelProperties config, UserFpdActivityMask userFpdActivityMask, @@ -68,6 +79,7 @@ public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(LiveIntentOmniCh this.httpClient = Objects.requireNonNull(httpClient); this.logSamplingRate = logSamplingRate; this.userFpdActivityMask = Objects.requireNonNull(userFpdActivityMask); + this.targetBidders = ListUtils.emptyIfNull(config.getTargetBidders()); } @Override @@ -170,17 +182,17 @@ private InvocationResultImpl update(IdResResponse resolut ActivityImpl.of( "liveintent-enriched", "success", List.of( - ResultImpl.of( - "", - mapper.mapper().createObjectNode() - .put("treatmentRate", config.getTreatmentRate()), - null)))))) + ResultImpl.of( + "", + mapper.mapper().createObjectNode() + .put("treatmentRate", config.getTreatmentRate()), + null)))))) .build(); } private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayload, List resolvedEids) { final List eids = ListUtils.emptyIfNull(resolvedEids); - final BidRequest bidRequest = requestPayload.bidRequest(); + 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))) .orElseGet(() -> User.builder().eids(eids)) @@ -189,6 +201,61 @@ private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayloa return AuctionRequestPayloadImpl.of(bidRequest.toBuilder().user(updatedUser).build()); } + private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolvedEids) { + if (targetBidders.isEmpty()) { + return bidRequest; + } + + final ExtRequest ext = bidRequest.getExt(); + final ExtRequestPrebid extPrebid = ext != null ? ext.getPrebid() : null; + final ExtRequestPrebidData extPrebidData = extPrebid != null ? extPrebid.getData() : null; + + final ExtRequestPrebid updatedExtPrebid = Optional.ofNullable(extPrebid) + .map(ExtRequestPrebid::toBuilder) + .orElseGet(ExtRequestPrebid::builder) + .data(updatePrebidData(extPrebidData, resolvedEids)) + .build(); + + final ExtRequest updatedExtRequest = ExtRequest.of(updatedExtPrebid); + if (ext != null) { + mapper.fillExtension(updatedExtRequest, ext.getProperties()); + } + + return bidRequest.toBuilder().ext(updatedExtRequest).build(); + } + + private ExtRequestPrebidData updatePrebidData(ExtRequestPrebidData extPrebidData, List resolvedEids) { + final List prebidDataBidders = extPrebidData != null ? extPrebidData.getBidders() : null; + final List updatedPrebidDataBidders = prebidDataBidders != null + ? (List) CollectionUtils.union(targetBidders, prebidDataBidders) + : targetBidders; + + final Set resolvedSources = resolvedEids.stream().map(Eid::getSource).collect(Collectors.toSet()); + + final List initialPermissions = Optional.ofNullable(extPrebidData) + .map(ExtRequestPrebidData::getEidPermissions) + .orElse(Collections.emptyList()); + final List updatedPermissions = Stream.concat( + initialPermissions.stream() + .map(permission -> updateEidPermission(permission, resolvedSources)), + resolvedSources.stream() + .map(source -> ExtRequestPrebidDataEidPermissions.of(source, targetBidders))) + .filter(StreamUtil.distinctBy(ExtRequestPrebidDataEidPermissions::getSource)) + .toList(); + + return ExtRequestPrebidData.of(updatedPrebidDataBidders, updatedPermissions); + } + + private ExtRequestPrebidDataEidPermissions updateEidPermission(ExtRequestPrebidDataEidPermissions permission, + Set resolvedSources) { + + return resolvedSources.contains(permission.getSource()) + ? ExtRequestPrebidDataEidPermissions.of( + permission.getSource(), + (List) CollectionUtils.union(permission.getBidders(), targetBidders)) + : permission; + } + @Override public String code() { return CODE; 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 0558f9126f2..7b05ec6072d 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 @@ -28,6 +28,10 @@ import org.prebid.server.hooks.v1.auction.AuctionRequestPayload; import org.prebid.server.json.JacksonMapper; import org.prebid.server.json.ObjectMapperProvider; +import org.prebid.server.proto.openrtb.ext.request.ExtRequest; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidData; +import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions; import org.prebid.server.vertx.httpclient.HttpClient; import org.prebid.server.vertx.httpclient.model.HttpClientResponse; @@ -70,12 +74,16 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest { private LiveIntentOmniChannelIdentityProcessedAuctionRequestHook target; + private List configuredBidders; + @BeforeEach public void setUp() { + configuredBidders = List.of("bidder1", "bidder2"); given(properties.getRequestTimeoutMs()).willReturn(5L); given(properties.getIdentityResolutionEndpoint()).willReturn("https://test.com/idres"); given(properties.getAuthToken()).willReturn("auth_token"); given(properties.getTreatmentRate()).willReturn(1.0f); + given(properties.getTargetBidders()).willReturn(configuredBidders); target = new LiveIntentOmniChannelIdentityProcessedAuctionRequestHook( properties, userFpdActivityMask, MAPPER, httpClient, 0.01d); @@ -365,4 +373,99 @@ public void callShouldReturnFailureWhenRequestingEidsIsFailed() { .isInstanceOf(TimeoutException.class) .hasMessage("Timeout exceeded"); } + + @Test + public void biddersConfiguredRestrictionShouldBeRespected() { + final Uid givenUid = Uid.builder().id("id1").atype(2).build(); + final Eid givenEid = Eid.builder().source("some.source.com").uids(singletonList(givenUid)).build(); + final User givenUser = User.builder().eids(singletonList(givenEid)).build(); + final BidRequest givenBidRequest = BidRequest.builder().id("request").user(givenUser).build(); + + final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(configuredBidders, List.of( + ExtRequestPrebidDataEidPermissions.of("liveintent.com", configuredBidders))); + + final Eid expectedEid = Eid.builder().source("liveintent.com").build(); + + final String responseBody = MAPPER.encodeToString(IdResResponse.of(List.of(expectedEid))); + given(httpClient.post(any(), any(), any(), anyLong())) + .willReturn(Future.succeededFuture(HttpClientResponse.of(200, null, responseBody))); + + given(auctionInvocationContext.auctionContext()).willReturn(auctionContext); + given(auctionContext.getActivityInfrastructure()).willReturn(activityInfrastructure); + given(activityInfrastructure.isAllowed(any(), any())).willReturn(true); + given(userFpdActivityMask.maskUser(any(), eq(false), eq(false))) + .willAnswer(invocation -> invocation.getArgument(0)); + given(userFpdActivityMask.maskDevice(any(), eq(false), eq(false))) + .willAnswer(invocation -> invocation.getArgument(0)); + + // when + final InvocationResult result = + target.call(AuctionRequestPayloadImpl.of(givenBidRequest), auctionInvocationContext).result(); + // then + assertThat(result.status()).isEqualTo(InvocationStatus.success); + assertThat(result.payloadUpdate().apply(AuctionRequestPayloadImpl.of(givenBidRequest))) + .extracting(AuctionRequestPayload::bidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getData) + .isEqualTo(expectedData); + + verify(httpClient).post( + eq("https://test.com/idres"), + argThat(headers -> headers.contains("Authorization", "Bearer auth_token", true)), + eq(MAPPER.encodeToString(givenBidRequest)), + eq(5L)); + } + + @Test + public void biddersConfiguredRestrictionShouldBeMergedWithProvided() { + // given + final Uid givenUid = Uid.builder().id("id1").atype(2).build(); + final Eid givenEid = Eid.builder().source("some.source.com").uids(singletonList(givenUid)).build(); + final User givenUser = User.builder().eids(singletonList(givenEid)).build(); + final BidRequest givenBidRequest = BidRequest.builder().id("request").user(givenUser).ext(ExtRequest.of( + ExtRequestPrebid.builder().data(ExtRequestPrebidData.of(List.of("bidder3"), List.of( + ExtRequestPrebidDataEidPermissions.of("some.other-source.com", List.of("bidder3")), + ExtRequestPrebidDataEidPermissions.of("some.source.com", List.of("bidder3")))) + ).build())).build(); + + final List expectedBidders = List.of("bidder3", "bidder2", "bidder1"); + + final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(expectedBidders, List.of( + ExtRequestPrebidDataEidPermissions.of("some.other-source.com", List.of("bidder3")), + ExtRequestPrebidDataEidPermissions.of("some.source.com", List.of("bidder3")), + ExtRequestPrebidDataEidPermissions.of("liveintent.com", configuredBidders))); + + final Eid expectedEid = Eid.builder().source("liveintent.com").build(); + + final String responseBody = MAPPER.encodeToString(IdResResponse.of(List.of(expectedEid))); + given(httpClient.post(any(), any(), any(), anyLong())) + .willReturn(Future.succeededFuture(HttpClientResponse.of(200, null, responseBody))); + + given(auctionInvocationContext.auctionContext()).willReturn(auctionContext); + given(auctionContext.getActivityInfrastructure()).willReturn(activityInfrastructure); + given(activityInfrastructure.isAllowed(any(), any())).willReturn(true); + given(userFpdActivityMask.maskUser(any(), eq(false), eq(false))) + .willAnswer(invocation -> invocation.getArgument(0)); + given(userFpdActivityMask.maskDevice(any(), eq(false), eq(false))) + .willAnswer(invocation -> invocation.getArgument(0)); + + // when + final InvocationResult result = + target.call(AuctionRequestPayloadImpl.of(givenBidRequest), auctionInvocationContext).result(); + // then + assertThat(result.status()).isEqualTo(InvocationStatus.success); + assertThat(result.payloadUpdate().apply(AuctionRequestPayloadImpl.of(givenBidRequest))) + .extracting(AuctionRequestPayload::bidRequest) + .extracting(BidRequest::getExt) + .extracting(ExtRequest::getPrebid) + .extracting(ExtRequestPrebid::getData) + .isEqualTo(expectedData); + + verify(httpClient).post( + eq("https://test.com/idres"), + argThat(headers -> headers.contains("Authorization", "Bearer auth_token", true)), + eq(MAPPER.encodeToString(givenBidRequest)), + eq(5L)); + } }