diff --git a/.github/workflows/code-path-changes.yml b/.github/workflows/code-path-changes.yml
index 23e0c19db6d..f818d867441 100644
--- a/.github/workflows/code-path-changes.yml
+++ b/.github/workflows/code-path-changes.yml
@@ -2,10 +2,13 @@ name: Notify Code Path Changes
on:
pull_request_target:
- types: [opened, synchronize]
+ types: [ opened, synchronize ]
paths:
- '**'
+permissions:
+ contents: read
+
env:
OAUTH2_CLIENT_ID: ${{ secrets.OAUTH2_CLIENT_ID }}
OAUTH2_CLIENT_SECRET: ${{ secrets.OAUTH2_CLIENT_SECRET }}
@@ -14,18 +17,15 @@ env:
GITHUB_PR_NUMBER: ${{ github.event.pull_request.number }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
-permissions:
- contents: read
-
jobs:
notify:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
- uses: actions/checkout@v3
+ uses: actions/checkout@v5
- name: Set up Node.js
- uses: actions/setup-node@v3
+ uses: actions/setup-node@v6
with:
node-version: '18'
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index a86f0b144a5..a6852ae7c92 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -1,48 +1,60 @@
-name: "CodeQL"
+name: CodeQL
on:
- push:
- branches: [ "master" ]
pull_request:
- branches: [ "master" ]
+ branches: [ 'master' ]
+ schedule:
+ - cron: '0 3 * * 1'
+
+permissions:
+ security-events: write
+ packages: read
+ actions: read
+ contents: read
jobs:
analyze:
- name: Analyze
+ name: Analyze (${{ matrix.language }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
- language: [ 'java' ]
+ include:
+ - language: actions
+ build-mode: none
+ - language: java-kotlin
+ build-mode: manual
steps:
- - name: Checkout repository
- uses: actions/checkout@v4
-
- - name: Set up JDK
- uses: actions/setup-java@v3
- with:
- distribution: 'temurin'
- java-version: 21
-
- - name: Cache Maven packages
- uses: actions/cache@v3
- with:
- path: ~/.m2/repository
- key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
- restore-keys: |
- ${{ runner.os }}-maven-
-
- - name: Initialize CodeQL
- uses: github/codeql-action/init@v1
- with:
- languages: ${{ matrix.language }}
-
- - name: Build with Maven
- run: mvn -B package --file extra/pom.xml
-
- - name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v1
- with:
- category: "/language:${{ matrix.language }}"
+ - name: Checkout repository
+ uses: actions/checkout@v5
+
+ - name: Set up JDK
+ uses: actions/setup-java@v5
+ with:
+ distribution: 'temurin'
+ java-version: 21
+
+ - name: Cache Maven packages
+ uses: actions/cache@v5
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v4
+ with:
+ languages: ${{ matrix.language }}
+ build-mode: ${{ matrix.build-mode }}
+
+ - name: Build with Maven
+ if: matrix.build-mode == 'manual'
+ run: mvn -B package --file extra/pom.xml
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v4
+ with:
+ category: '/language:${{ matrix.language }}'
diff --git a/.github/workflows/cross-repo-issue.yml b/.github/workflows/cross-repo-issue.yml
index c2288da271a..5d2e512d4c6 100644
--- a/.github/workflows/cross-repo-issue.yml
+++ b/.github/workflows/cross-repo-issue.yml
@@ -2,9 +2,12 @@ name: Cross-repo Issue Creation
on:
pull_request_target:
- types: [closed]
+ types: [ closed ]
branches:
- - "master"
+ - 'master'
+
+permissions:
+ contents: read
jobs:
cross-repo:
@@ -12,7 +15,7 @@ jobs:
steps:
- name: Generate token
id: generate_token
- uses: tibdex/github-app-token@v1
+ uses: tibdex/github-app-token@v2.1.0
with:
app_id: ${{ secrets.XREPO_APP_ID }}
private_key: ${{ secrets.XREPO_PEM }}
@@ -23,9 +26,10 @@ jobs:
github.event.pull_request.merged
env:
GITHUB_TOKEN: ${{ steps.generate_token.outputs.token }}
+ PR_TITLE: ${{ github.event.pull_request.title }}
run: |
echo -e "A PR was merged over on PBS-Java\n\n- [https://github.com/prebid/prebid-server-java/pull/${{github.event.number}}](https://github.com/prebid/prebid-server-java/pull/${{github.event.number}})\n- timestamp: ${{ github.event.pull_request.merged_at}}" > msg
export msg=$(cat msg)
- gh issue create --repo prebid/prebid-server --title "Port PR from PBS-Java: ${{ github.event.pull_request.title }}" \
+ gh issue create --repo prebid/prebid-server --title "Port PR from PBS-Java: $PR_TITLE" \
--body "$msg" \
--label auto
diff --git a/.github/workflows/docker-image-publish.yml b/.github/workflows/docker-image-publish.yml
index 39964eb69aa..7f993ade73d 100644
--- a/.github/workflows/docker-image-publish.yml
+++ b/.github/workflows/docker-image-publish.yml
@@ -5,6 +5,10 @@ on:
tags:
- '*'
+permissions:
+ contents: read
+ packages: write
+
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
@@ -13,13 +17,10 @@ jobs:
build:
name: Publish Docker image for new tag/release
runs-on: ubuntu-latest
- permissions:
- contents: read
- packages: write
strategy:
matrix:
java: [ 21 ]
- dockerfile-path: [Dockerfile, Dockerfile-modules]
+ dockerfile-path: [ Dockerfile, Dockerfile-modules ]
include:
- dockerfile-path: Dockerfile
build-cmd: mvn clean package -Dcheckstyle.skip -Dmaven.test.skip=true
@@ -30,10 +31,10 @@ jobs:
package-name: ghcr.io/${{ github.repository }}-bundle
steps:
- name: Check out Repository
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
cache: 'maven'
@@ -56,13 +57,13 @@ jobs:
images: ${{ matrix.package-name }}
- name: Set up QEMU
- uses: docker/setup-qemu-action@v2
+ uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v2
+ uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
context: .
file: ${{ matrix.dockerfile-path }}
diff --git a/.github/workflows/issue_prioritization.yml b/.github/workflows/issue_prioritization.yml
index fa56f9ee2ee..7b4df73b80b 100644
--- a/.github/workflows/issue_prioritization.yml
+++ b/.github/workflows/issue_prioritization.yml
@@ -1,9 +1,11 @@
name: Issue tracking
+
on:
issues:
types:
- opened
- pinned
+
jobs:
track_issue:
runs-on: ubuntu-latest
diff --git a/.github/workflows/pr-functional-tests.yml b/.github/workflows/pr-functional-tests.yml
index 0cf032e3e23..d512022413a 100644
--- a/.github/workflows/pr-functional-tests.yml
+++ b/.github/workflows/pr-functional-tests.yml
@@ -11,6 +11,11 @@ on:
types:
- created
+permissions:
+ contents: read
+ actions: read
+ checks: write
+
jobs:
build:
runs-on: ubuntu-latest
@@ -20,10 +25,10 @@ jobs:
java: [ 21 ]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
cache: 'maven'
@@ -42,7 +47,7 @@ jobs:
- name: Emitting run result of functional test
if: always()
- uses: dorny/test-reporter@v2.1.1
+ uses: dorny/test-reporter@v2.5.0
with:
name: 'Functional tests'
working-directory: 'target/failsafe-reports'
diff --git a/.github/workflows/pr-java-ci.yml b/.github/workflows/pr-java-ci.yml
index 1a198dac001..d69d222592f 100644
--- a/.github/workflows/pr-java-ci.yml
+++ b/.github/workflows/pr-java-ci.yml
@@ -11,6 +11,11 @@ on:
types:
- created
+permissions:
+ contents: read
+ actions: read
+ checks: write
+
jobs:
build:
runs-on: ubuntu-latest
@@ -20,10 +25,10 @@ jobs:
java: [ 21 ]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK
- uses: actions/setup-java@v4
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
cache: 'maven'
@@ -32,12 +37,3 @@ jobs:
- name: Build with Maven
run: mvn -B package --file extra/pom.xml
-
- - name: Publish JUnit Report
- uses: mikepenz/action-junit-report@v5
- if: always()
- with:
- check_name: 'JUnit Test Report'
- report_paths: '**/target/surefire-reports/TEST-*.xml'
- fail_on_failure: true
- annotate_only: true
diff --git a/.github/workflows/pr-module-functional-tests.yml b/.github/workflows/pr-module-functional-tests.yml
index e61814093b0..c3b04858677 100644
--- a/.github/workflows/pr-module-functional-tests.yml
+++ b/.github/workflows/pr-module-functional-tests.yml
@@ -11,6 +11,11 @@ on:
types:
- created
+permissions:
+ contents: read
+ actions: read
+ checks: write
+
jobs:
build:
runs-on: ubuntu-latest
@@ -20,10 +25,10 @@ jobs:
java: [ 21 ]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
cache: 'maven'
@@ -46,7 +51,7 @@ jobs:
- name: Emitting run result of functional test
if: always()
- uses: dorny/test-reporter@v2.1.1
+ uses: dorny/test-reporter@v2.5.0
with:
name: 'Module functional tests'
working-directory: 'target/failsafe-reports'
diff --git a/.github/workflows/release-asset-publish.yml b/.github/workflows/release-asset-publish.yml
index fb1057d8ee8..bfa938bebe9 100644
--- a/.github/workflows/release-asset-publish.yml
+++ b/.github/workflows/release-asset-publish.yml
@@ -2,7 +2,7 @@ name: Publish release .jar
on:
workflow_run:
- workflows: [Publish release]
+ workflows: [ Publish release ]
types:
- completed
@@ -14,9 +14,9 @@ jobs:
matrix:
java: [ 21 ]
steps:
- - uses: actions/checkout@v4
+ - uses: actions/checkout@v5
- name: Set up JDK
- uses: actions/setup-java@v3
+ uses: actions/setup-java@v5
with:
distribution: 'temurin'
cache: 'maven'
diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml
index c1ee08ab668..75ea23441de 100644
--- a/.github/workflows/release-drafter.yml
+++ b/.github/workflows/release-drafter.yml
@@ -5,17 +5,22 @@ on:
tags:
- '*'
+permissions:
+ contents: read
+
jobs:
update_release_draft:
name: Publish release with notes
+ permissions:
+ contents: write
runs-on: ubuntu-latest
steps:
- name: Create and publish release
- uses: release-drafter/release-drafter@v5
+ uses: release-drafter/release-drafter@v6
with:
config-name: release-drafter-config.yml
publish: true
- name: "v${{ github.ref_name }}"
+ name: 'v${{ github.ref_name }}'
tag: ${{ github.ref_name }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/slack-stale-pr.yml b/.github/workflows/slack-stale-pr.yml
deleted file mode 100644
index a610c3e7de9..00000000000
--- a/.github/workflows/slack-stale-pr.yml
+++ /dev/null
@@ -1,27 +0,0 @@
-name: Post Stale PRs To Slack
-
-on:
- # run Monday 9am and on-demand
- workflow_dispatch:
- schedule:
- - cron: '0 9 * * 1'
-
-jobs:
- fetch-PRs:
- runs-on: ubuntu-latest
- steps:
- - name: Fetch pull requests
- id: local
- uses: paritytech/stale-pr-finder@v0.3.0
- with:
- GITHUB_TOKEN: ${{ github.token }}
- days-stale: 14
- ignoredLabels: "blocked"
- - name: Post to a Slack channel
- id: slack
- uses: slackapi/slack-github-action@v1.27.1
- with:
- channel-id: ${{ secrets.SLACK_CHANNEL_ID }}
- slack-message: "${{ steps.local.outputs.message }}"
- env:
- SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }}
diff --git a/.github/workflows/trivy-security-check.yml b/.github/workflows/trivy-security-check.yml
index 044b7e39af6..b73eda3b40d 100644
--- a/.github/workflows/trivy-security-check.yml
+++ b/.github/workflows/trivy-security-check.yml
@@ -1,27 +1,35 @@
-name: Security Check
+name: Trivy Security Scan
on:
pull_request:
- branches: [master]
+ branches: [ 'master' ]
+ schedule:
+ - cron: '0 3 * * 1'
+
+permissions:
+ contents: read
jobs:
build:
name: Trivy security check
+ permissions:
+ security-events: write
runs-on: ubuntu-latest
steps:
- name: Checkout Code
- uses: actions/checkout@v4
+ uses: actions/checkout@v5
- name: Run Trivy vulnerability scanner
- uses: aquasecurity/trivy-action@master
+ uses: aquasecurity/trivy-action@0.33.1
with:
scan-type: 'fs'
+ scan-ref: '.'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
- name: Upload Trivy scan results to GitHub Security tab
- uses: github/codeql-action/upload-sarif@v2
+ uses: github/codeql-action/upload-sarif@v4
with:
sarif_file: 'trivy-results.sarif'
diff --git a/extra/bundle/pom.xml b/extra/bundle/pom.xml
index e5ecd041937..a210a0919ee 100644
--- a/extra/bundle/pom.xml
+++ b/extra/bundle/pom.xml
@@ -5,7 +5,7 @@
org.prebid
prebid-server-aggregator
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
../../extra/pom.xml
diff --git a/extra/modules/confiant-ad-quality/pom.xml b/extra/modules/confiant-ad-quality/pom.xml
index 62be75c4bd3..e2f82de7886 100644
--- a/extra/modules/confiant-ad-quality/pom.xml
+++ b/extra/modules/confiant-ad-quality/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
confiant-ad-quality
diff --git a/extra/modules/fiftyone-devicedetection/pom.xml b/extra/modules/fiftyone-devicedetection/pom.xml
index 04447c1e2e8..5c1dbb9ccc0 100644
--- a/extra/modules/fiftyone-devicedetection/pom.xml
+++ b/extra/modules/fiftyone-devicedetection/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
fiftyone-devicedetection
diff --git a/extra/modules/greenbids-real-time-data/pom.xml b/extra/modules/greenbids-real-time-data/pom.xml
index bcacf785cc8..f5ab8eb4b54 100644
--- a/extra/modules/greenbids-real-time-data/pom.xml
+++ b/extra/modules/greenbids-real-time-data/pom.xml
@@ -4,7 +4,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
greenbids-real-time-data
diff --git a/extra/modules/live-intent-omni-channel-identity/pom.xml b/extra/modules/live-intent-omni-channel-identity/pom.xml
index 1e16b624733..e846ccbdad6 100644
--- a/extra/modules/live-intent-omni-channel-identity/pom.xml
+++ b/extra/modules/live-intent-omni-channel-identity/pom.xml
@@ -4,7 +4,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
live-intent-omni-channel-identity
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 01d37eabda0..383aacea3a9 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,7 +2,7 @@
import lombok.Data;
-import java.util.List;
+import java.util.Set;
@Data
public final class LiveIntentOmniChannelProperties {
@@ -15,5 +15,5 @@ public final class LiveIntentOmniChannelProperties {
float treatmentRate;
- List targetBidders;
+ Set 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 32b608ce21c..3afdb146018 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
@@ -9,6 +9,7 @@
import io.vertx.core.MultiMap;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.ListUtils;
+import org.apache.commons.collections4.SetUtils;
import org.prebid.server.activity.Activity;
import org.prebid.server.activity.ComponentType;
import org.prebid.server.activity.infrastructure.ActivityInfrastructure;
@@ -40,18 +41,15 @@
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 {
@@ -65,7 +63,7 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHook implements
private final HttpClient httpClient;
private final UserFpdActivityMask userFpdActivityMask;
private final double logSamplingRate;
- private final List targetBidders;
+ private final Set targetBidders;
public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(LiveIntentOmniChannelProperties config,
UserFpdActivityMask userFpdActivityMask,
@@ -79,7 +77,7 @@ public LiveIntentOmniChannelIdentityProcessedAuctionRequestHook(LiveIntentOmniCh
this.httpClient = Objects.requireNonNull(httpClient);
this.logSamplingRate = logSamplingRate;
this.userFpdActivityMask = Objects.requireNonNull(userFpdActivityMask);
- this.targetBidders = ListUtils.emptyIfNull(config.getTargetBidders());
+ this.targetBidders = SetUtils.emptyIfNull(config.getTargetBidders());
}
@Override
@@ -202,7 +200,7 @@ private AuctionRequestPayload updatedPayload(AuctionRequestPayload requestPayloa
}
private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolvedEids) {
- if (targetBidders.isEmpty()) {
+ if (CollectionUtils.isEmpty(targetBidders) || CollectionUtils.isEmpty(resolvedEids)) {
return bidRequest;
}
@@ -210,6 +208,14 @@ 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)
@@ -225,35 +231,38 @@ private BidRequest updateAllowedBidders(BidRequest bidRequest, List resolve
}
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))
+ final List originalBidders = extPrebidData != null ? extPrebidData.getBidders() : null;
+
+ final Set resolvedSources = resolvedEids.stream()
+ .map(Eid::getSource)
+ .collect(Collectors.toSet());
+
+ final List updatedPermissions = extPrebidData.getEidPermissions().stream()
+ .map(permission -> restrictEidPermission(permission, resolvedSources))
+ .filter(Objects::nonNull)
.toList();
- return ExtRequestPrebidData.of(updatedPrebidDataBidders, updatedPermissions);
+ return ExtRequestPrebidData.of(originalBidders, updatedPermissions);
}
- private ExtRequestPrebidDataEidPermissions updateEidPermission(ExtRequestPrebidDataEidPermissions permission,
- Set resolvedSources) {
+ private ExtRequestPrebidDataEidPermissions restrictEidPermission(ExtRequestPrebidDataEidPermissions permission,
+ Set resolvedSources) {
- return resolvedSources.contains(permission.getSource())
- ? ExtRequestPrebidDataEidPermissions.of(
- permission.getSource(),
- (List) CollectionUtils.union(permission.getBidders(), targetBidders))
- : permission;
+ if (!resolvedSources.contains(permission.getSource())) {
+ return permission;
+ }
+
+ 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();
}
@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 7b05ec6072d..4dfaa8c8df4 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
@@ -36,6 +36,7 @@
import org.prebid.server.vertx.httpclient.model.HttpClientResponse;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeoutException;
import static java.util.Collections.singletonList;
@@ -74,11 +75,11 @@ public class LiveIntentOmniChannelIdentityProcessedAuctionRequestHookTest {
private LiveIntentOmniChannelIdentityProcessedAuctionRequestHook target;
- private List configuredBidders;
+ private Set configuredBidders;
@BeforeEach
public void setUp() {
- configuredBidders = List.of("bidder1", "bidder2");
+ configuredBidders = Set.of("bidder1", "bidder2");
given(properties.getRequestTimeoutMs()).willReturn(5L);
given(properties.getIdentityResolutionEndpoint()).willReturn("https://test.com/idres");
given(properties.getAuthToken()).willReturn("auth_token");
@@ -375,14 +376,38 @@ public void callShouldReturnFailureWhenRequestingEidsIsFailed() {
}
@Test
- public void biddersConfiguredRestrictionShouldBeRespected() {
+ public void shouldRestrictExistingEidPermissionsByIntersectionAndKeepGlobalBiddersUnchanged() {
+ // 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).build();
- final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(configuredBidders, List.of(
- ExtRequestPrebidDataEidPermissions.of("liveintent.com", configuredBidders)));
+ final ExtRequestPrebidDataEidPermissions otherBidder = ExtRequestPrebidDataEidPermissions.builder()
+ .source("some.other-source.com")
+ .bidders(singletonList("bidderY"))
+ .build();
+
+ final ExtRequestPrebidDataEidPermissions liBidder2 = ExtRequestPrebidDataEidPermissions.builder()
+ .source("liveintent.com")
+ .bidders(singletonList("bidder2"))
+ .build();
+ final ExtRequestPrebidDataEidPermissions liBidder23 = ExtRequestPrebidDataEidPermissions.builder()
+ .source("liveintent.com")
+ .bidders(List.of("bidder2", "bidder3"))
+ .build();
+
+ final ExtRequestPrebidData givenData = ExtRequestPrebidData.of(singletonList("bidderX"),
+ List.of(otherBidder, liBidder23));
+
+ final BidRequest givenBidRequest = BidRequest.builder()
+ .id("request")
+ .user(givenUser)
+ .ext(ExtRequest.of(ExtRequestPrebid.builder().data(givenData).build()))
+ .build();
+
+ final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(
+ List.of("bidderX"),
+ List.of(otherBidder, liBidder2));
final Eid expectedEid = Eid.builder().source("liveintent.com").build();
@@ -418,23 +443,31 @@ public void biddersConfiguredRestrictionShouldBeRespected() {
}
@Test
- public void biddersConfiguredRestrictionShouldBeMergedWithProvided() {
+ public void shouldNotAddNewEidPermissionsOrModifyGlobalBiddersWhenSourceNotPresent() {
// 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 ExtRequestPrebidDataEidPermissions bidder1 = ExtRequestPrebidDataEidPermissions.builder()
+ .source("some.other-source.com")
+ .bidders(singletonList("bidder3"))
+ .build();
+ final ExtRequestPrebidDataEidPermissions bidder2 = ExtRequestPrebidDataEidPermissions.builder()
+ .source("some.source.com")
+ .bidders(singletonList("bidder3"))
+ .build();
- final List expectedBidders = List.of("bidder3", "bidder2", "bidder1");
+ final List bidders = List.of(bidder1, bidder2);
- 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 BidRequest givenBidRequest = BidRequest.builder()
+ .id("request")
+ .user(givenUser)
+ .ext(ExtRequest.of(ExtRequestPrebid.builder()
+ .data(ExtRequestPrebidData.of(singletonList("bidder3"), bidders))
+ .build()))
+ .build();
+
+ final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(List.of("bidder3"), bidders);
final Eid expectedEid = Eid.builder().source("liveintent.com").build();
@@ -468,4 +501,69 @@ public void biddersConfiguredRestrictionShouldBeMergedWithProvided() {
eq(MAPPER.encodeToString(givenBidRequest)),
eq(5L));
}
+
+ @Test
+ public void shouldRemovePermissionWhenIntersectionIsEmpty() {
+ // 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 ExtRequestPrebidData givenData = ExtRequestPrebidData.of(
+ List.of("bidderGlobal"),
+ List.of(
+ ExtRequestPrebidDataEidPermissions.builder()
+ .source("liveintent.com")
+ .bidders(singletonList("not-allowed"))
+ .build(),
+ ExtRequestPrebidDataEidPermissions.builder()
+ .source("keep.com")
+ .bidders(singletonList("bidderGlobal"))
+ .build()));
+
+ final BidRequest givenBidRequest = BidRequest.builder()
+ .id("request")
+ .user(givenUser)
+ .ext(ExtRequest.of(ExtRequestPrebid.builder().data(givenData).build()))
+ .build();
+
+ 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
+ final ExtRequestPrebidData expectedData = ExtRequestPrebidData.of(
+ List.of("bidderGlobal"),
+ List.of(ExtRequestPrebidDataEidPermissions.builder()
+ .source("keep.com")
+ .bidders(singletonList("bidderGlobal"))
+ .build()));
+
+ 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));
+ }
}
diff --git a/extra/modules/optable-targeting/pom.xml b/extra/modules/optable-targeting/pom.xml
index 0e6b0622299..34278806fc8 100644
--- a/extra/modules/optable-targeting/pom.xml
+++ b/extra/modules/optable-targeting/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
optable-targeting
diff --git a/extra/modules/ortb2-blocking/pom.xml b/extra/modules/ortb2-blocking/pom.xml
index 32c26829d83..6aa8f9b2792 100644
--- a/extra/modules/ortb2-blocking/pom.xml
+++ b/extra/modules/ortb2-blocking/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
ortb2-blocking
diff --git a/extra/modules/pb-request-correction/pom.xml b/extra/modules/pb-request-correction/pom.xml
index 0e6297e934b..e1deb0518b8 100644
--- a/extra/modules/pb-request-correction/pom.xml
+++ b/extra/modules/pb-request-correction/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
pb-request-correction
diff --git a/extra/modules/pb-response-correction/pom.xml b/extra/modules/pb-response-correction/pom.xml
index 6fda39fe4ec..09f08ce6d67 100644
--- a/extra/modules/pb-response-correction/pom.xml
+++ b/extra/modules/pb-response-correction/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
pb-response-correction
diff --git a/extra/modules/pb-richmedia-filter/pom.xml b/extra/modules/pb-richmedia-filter/pom.xml
index 27a17ea0009..735704921f1 100644
--- a/extra/modules/pb-richmedia-filter/pom.xml
+++ b/extra/modules/pb-richmedia-filter/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
pb-richmedia-filter
diff --git a/extra/modules/pb-rule-engine/pom.xml b/extra/modules/pb-rule-engine/pom.xml
index a8b4caa752f..3fe12665358 100644
--- a/extra/modules/pb-rule-engine/pom.xml
+++ b/extra/modules/pb-rule-engine/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
pb-rule-engine
diff --git a/extra/modules/pom.xml b/extra/modules/pom.xml
index 50eb19cdee5..d55994fd58c 100644
--- a/extra/modules/pom.xml
+++ b/extra/modules/pom.xml
@@ -5,7 +5,7 @@
org.prebid
prebid-server-aggregator
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
../../extra/pom.xml
diff --git a/extra/modules/wurfl-devicedetection/pom.xml b/extra/modules/wurfl-devicedetection/pom.xml
index 17a99a407cf..dde02bed3ed 100644
--- a/extra/modules/wurfl-devicedetection/pom.xml
+++ b/extra/modules/wurfl-devicedetection/pom.xml
@@ -5,7 +5,7 @@
org.prebid.server.hooks.modules
all-modules
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
wurfl-devicedetection
diff --git a/extra/pom.xml b/extra/pom.xml
index 6e310f2dedb..2cb81b6464a 100644
--- a/extra/pom.xml
+++ b/extra/pom.xml
@@ -4,7 +4,7 @@
org.prebid
prebid-server-aggregator
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
pom
@@ -33,7 +33,7 @@
10.17.0
- 3.5.5
+ 3.5.10
4.5.20
2.0.1.Final
4.4
diff --git a/pom.xml b/pom.xml
index 750ce6efa65..73513000b95 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
org.prebid
prebid-server-aggregator
- 3.37.0-SNAPSHOT
+ 3.40.0-SNAPSHOT
extra/pom.xml
diff --git a/src/main/java/org/prebid/server/auction/EidPermissionResolver.java b/src/main/java/org/prebid/server/auction/EidPermissionResolver.java
new file mode 100644
index 00000000000..6a19fd7c82b
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/EidPermissionResolver.java
@@ -0,0 +1,81 @@
+package org.prebid.server.auction;
+
+import com.iab.openrtb.request.Eid;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class EidPermissionResolver {
+
+ private static final String WILDCARD_BIDDER = "*";
+
+ private static final ExtRequestPrebidDataEidPermissions DEFAULT_RULE = ExtRequestPrebidDataEidPermissions.builder()
+ .bidders(Collections.singletonList(WILDCARD_BIDDER))
+ .build();
+
+ private static final EidPermissionResolver EMPTY = new EidPermissionResolver(Collections.emptyList());
+
+ private final List eidPermissions;
+
+ private EidPermissionResolver(List eidPermissions) {
+ this.eidPermissions = new ArrayList<>(eidPermissions);
+ this.eidPermissions.add(DEFAULT_RULE);
+ }
+
+ public static EidPermissionResolver of(List eidPermissions) {
+ return new EidPermissionResolver(eidPermissions);
+ }
+
+ public static EidPermissionResolver empty() {
+ return EMPTY;
+ }
+
+ public List resolveAllowedEids(List userEids, String bidder) {
+ return CollectionUtils.emptyIfNull(userEids)
+ .stream()
+ .filter(userEid -> isAllowed(userEid, bidder))
+ .toList();
+ }
+
+ private boolean isAllowed(Eid eid, String bidder) {
+ final Map> matchingRulesBySpecificity = eidPermissions
+ .stream()
+ .filter(rule -> isRuleMatched(eid, rule))
+ .collect(Collectors.groupingBy(this::getRuleSpecificity));
+
+ final int highestSpecificityMatchingRules = Collections.max(matchingRulesBySpecificity.keySet());
+ return matchingRulesBySpecificity.get(highestSpecificityMatchingRules).stream()
+ .anyMatch(eidPermission -> isBidderAllowed(bidder, eidPermission.getBidders()));
+ }
+
+ private int getRuleSpecificity(ExtRequestPrebidDataEidPermissions eidPermission) {
+ return (int) Stream.of(
+ eidPermission.getInserter(),
+ eidPermission.getSource(),
+ eidPermission.getMatcher(),
+ eidPermission.getMm())
+ .filter(Objects::nonNull)
+ .count();
+ }
+
+ private boolean isRuleMatched(Eid eid, ExtRequestPrebidDataEidPermissions eidPermission) {
+ return (eidPermission.getInserter() == null || eidPermission.getInserter().equals(eid.getInserter()))
+ && (eidPermission.getSource() == null || eidPermission.getSource().equals(eid.getSource()))
+ && (eidPermission.getMatcher() == null || eidPermission.getMatcher().equals(eid.getMatcher()))
+ && (eidPermission.getMm() == null || eidPermission.getMm().equals(eid.getMm()));
+ }
+
+ private boolean isBidderAllowed(String bidder, List ruleBidders) {
+ return ruleBidders == null || ruleBidders.stream()
+ .anyMatch(allowedBidder -> StringUtils.equalsIgnoreCase(allowedBidder, bidder)
+ || WILDCARD_BIDDER.equals(allowedBidder));
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/ExchangeService.java b/src/main/java/org/prebid/server/auction/ExchangeService.java
index 8bea0bc315e..428510be069 100644
--- a/src/main/java/org/prebid/server/auction/ExchangeService.java
+++ b/src/main/java/org/prebid/server/auction/ExchangeService.java
@@ -19,7 +19,6 @@
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import org.apache.commons.collections4.CollectionUtils;
-import org.apache.commons.collections4.ListUtils;
import org.apache.commons.collections4.map.CaseInsensitiveMap;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@@ -32,9 +31,9 @@
import org.prebid.server.activity.infrastructure.payload.impl.BidRequestActivityInvocationPayload;
import org.prebid.server.auction.aliases.AlternateBidderCodesConfig;
import org.prebid.server.auction.aliases.BidderAliases;
+import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPostProcessor;
+import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestRejectedException;
import org.prebid.server.auction.externalortb.StoredResponseProcessor;
-import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessingResult;
-import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor;
import org.prebid.server.auction.model.AuctionContext;
import org.prebid.server.auction.model.AuctionParticipation;
import org.prebid.server.auction.model.BidRejectionReason;
@@ -51,7 +50,6 @@
import org.prebid.server.auction.versionconverter.OrtbVersion;
import org.prebid.server.bidder.Bidder;
import org.prebid.server.bidder.BidderCatalog;
-import org.prebid.server.bidder.BidderInfo;
import org.prebid.server.bidder.HttpBidderRequester;
import org.prebid.server.bidder.Usersyncer;
import org.prebid.server.bidder.model.BidderBid;
@@ -88,7 +86,6 @@
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidBidderConfig;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidCache;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidData;
-import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidMultiBid;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidSchain;
import org.prebid.server.proto.openrtb.ext.request.ExtRequestTargeting;
@@ -131,7 +128,6 @@ public class ExchangeService {
private static final String ALL_BIDDERS_CONFIG = "*";
private static final Integer DEFAULT_MULTIBID_LIMIT_MIN = 1;
private static final Integer DEFAULT_MULTIBID_LIMIT_MAX = 9;
- private static final String EID_ALLOWED_FOR_ALL_BIDDERS = "*";
private static final BigDecimal THOUSAND = BigDecimal.valueOf(1000);
private static final Set BIDDER_FIELDS_EXCEPTION_LIST = Set.of(
"adunitcode", "storedrequest", "options", "is_rewarded_inventory");
@@ -144,7 +140,7 @@ public class ExchangeService {
private final ImpAdjuster impAdjuster;
private final SupplyChainResolver supplyChainResolver;
private final DebugResolver debugResolver;
- private final MediaTypeProcessor mediaTypeProcessor;
+ private final BidderRequestPostProcessor bidderRequestPostProcessor;
private final UidUpdater uidUpdater;
private final TimeoutResolver timeoutResolver;
private final TimeoutFactory timeoutFactory;
@@ -171,7 +167,7 @@ public ExchangeService(double logSamplingRate,
ImpAdjuster impAdjuster,
SupplyChainResolver supplyChainResolver,
DebugResolver debugResolver,
- MediaTypeProcessor mediaTypeProcessor,
+ BidderRequestPostProcessor bidderRequestPostProcessor,
UidUpdater uidUpdater,
TimeoutResolver timeoutResolver,
TimeoutFactory timeoutFactory,
@@ -198,7 +194,7 @@ public ExchangeService(double logSamplingRate,
this.impAdjuster = Objects.requireNonNull(impAdjuster);
this.supplyChainResolver = Objects.requireNonNull(supplyChainResolver);
this.debugResolver = Objects.requireNonNull(debugResolver);
- this.mediaTypeProcessor = Objects.requireNonNull(mediaTypeProcessor);
+ this.bidderRequestPostProcessor = Objects.requireNonNull(bidderRequestPostProcessor);
this.uidUpdater = Objects.requireNonNull(uidUpdater);
this.timeoutResolver = Objects.requireNonNull(timeoutResolver);
this.timeoutFactory = Objects.requireNonNull(timeoutFactory);
@@ -435,7 +431,7 @@ private void removeInvalidBidRejectionTrackers(Map
BidderAliases aliases) {
final Set bidderNames = new HashSet<>(bidRejectionTrackers.keySet());
- for (String bidder: bidderNames) {
+ for (String bidder : bidderNames) {
if (!isValidBidder(bidder, aliases)) {
bidRejectionTrackers.remove(bidder);
logger.warn("Pre-rejected impressions of the bidder {} have been removed. "
@@ -492,13 +488,12 @@ private Future> extractAuctionParticipations(
.filter(bidder -> isBidderCallActivityAllowed(bidder, context))
.distinct()
.toList();
- final Map> impBidderToStoredBidResponse =
- storedResponseResult.getImpBidderToStoredBidResponse();
+
return makeAuctionParticipation(
bidders,
context,
aliases,
- impBidderToStoredBidResponse,
+ storedResponseResult.getImpBidderToStoredBidResponse(),
imps,
bidderToMultiBid);
}
@@ -539,11 +534,11 @@ private Future> makeAuctionParticipation(
final BidRequest bidRequest = context.getBidRequest();
final ExtRequest requestExt = bidRequest.getExt();
- final ExtRequestPrebid prebid = requestExt == null ? null : requestExt.getPrebid();
+ final ExtRequestPrebid prebid = requestExt != null ? requestExt.getPrebid() : null;
final Map biddersToConfigs = getBiddersToConfigs(prebid);
- final Map> eidPermissions = getEidPermissions(prebid);
+ final EidPermissionResolver eidPermissionResolver = getEidPermissions(prebid);
final Map> bidderToUserAndDevice =
- prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissions);
+ prepareUsersAndDevices(bidders, context, aliases, biddersToConfigs, eidPermissionResolver);
return privacyEnforcementService.mask(context, bidderToUserAndDevice, aliases)
.map(bidderToPrivacyResult -> getAuctionParticipation(
@@ -581,14 +576,12 @@ private Map getBiddersToConfigs(ExtRequestPrebid pr
return bidderToConfig;
}
- private Map> getEidPermissions(ExtRequestPrebid prebid) {
- final ExtRequestPrebidData prebidData = prebid != null ? prebid.getData() : null;
- final List eidPermissions = prebidData != null
- ? prebidData.getEidPermissions()
- : null;
- return CollectionUtils.emptyIfNull(eidPermissions).stream()
- .collect(Collectors.toMap(ExtRequestPrebidDataEidPermissions::getSource,
- ExtRequestPrebidDataEidPermissions::getBidders));
+ private EidPermissionResolver getEidPermissions(ExtRequestPrebid prebid) {
+ return Optional.ofNullable(prebid)
+ .map(ExtRequestPrebid::getData)
+ .map(ExtRequestPrebidData::getEidPermissions)
+ .map(EidPermissionResolver::of)
+ .orElse(EidPermissionResolver.empty());
}
private static List firstPartyDataBidders(ExtRequest requestExt) {
@@ -597,11 +590,12 @@ private static List firstPartyDataBidders(ExtRequest requestExt) {
return data == null ? null : data.getBidders();
}
- private Map> prepareUsersAndDevices(List bidders,
- AuctionContext context,
- BidderAliases aliases,
- Map biddersToConfigs,
- Map> eidPermissions) {
+ private Map> prepareUsersAndDevices(
+ List bidders,
+ AuctionContext context,
+ BidderAliases aliases,
+ Map biddersToConfigs,
+ EidPermissionResolver eidPermissionResolver) {
final BidRequest bidRequest = context.getBidRequest();
final List firstPartyDataBidders = firstPartyDataBidders(bidRequest.getExt());
@@ -613,7 +607,7 @@ private Map> prepareUsersAndDevices(List bidd
final boolean useFirstPartyData = firstPartyDataBidders == null || firstPartyDataBidders.stream()
.anyMatch(fpdBidder -> StringUtils.equalsIgnoreCase(fpdBidder, bidder));
final User preparedUser = prepareUser(
- bidder, context, aliases, useFirstPartyData, fpdConfig, eidPermissions);
+ bidder, context, aliases, useFirstPartyData, fpdConfig, eidPermissionResolver);
final Device preparedDevice = prepareDevice(
bidRequest.getDevice(), fpdConfig, useFirstPartyData);
bidderToUserAndDevice.put(bidder, Pair.of(preparedUser, preparedDevice));
@@ -626,13 +620,13 @@ private User prepareUser(String bidder,
BidderAliases aliases,
boolean useFirstPartyData,
ExtBidderConfigOrtb fpdConfig,
- Map> eidPermissions) {
+ EidPermissionResolver eidPermissionResolver) {
final User user = context.getBidRequest().getUser();
final ExtUser extUser = user != null ? user.getExt() : null;
final UpdateResult buyerUidUpdateResult = uidUpdater.updateUid(bidder, context, aliases);
final List userEids = extractUserEids(user);
- final List allowedUserEids = resolveAllowedEids(userEids, bidder, eidPermissions);
+ final List allowedUserEids = eidPermissionResolver.resolveAllowedEids(userEids, bidder);
final boolean shouldUpdateUserEids = allowedUserEids.size() != CollectionUtils.emptyIfNull(userEids).size();
final boolean shouldCleanExtPrebid = extUser != null && extUser.getPrebid() != null;
final boolean shouldCleanExtData = extUser != null && extUser.getData() != null && !useFirstPartyData;
@@ -672,20 +666,6 @@ private List extractUserEids(User user) {
return user != null ? user.getEids() : null;
}
- private List resolveAllowedEids(List userEids, String bidder, Map> eidPermissions) {
- return CollectionUtils.emptyIfNull(userEids)
- .stream()
- .filter(userEid -> isUserEidAllowed(userEid.getSource(), eidPermissions, bidder))
- .toList();
- }
-
- private boolean isUserEidAllowed(String source, Map> eidPermissions, String bidder) {
- final List allowedBidders = eidPermissions.get(source);
- return CollectionUtils.isEmpty(allowedBidders) || allowedBidders.stream()
- .anyMatch(allowedBidder -> StringUtils.equalsIgnoreCase(allowedBidder, bidder)
- || EID_ALLOWED_FOR_ALL_BIDDERS.equals(allowedBidder));
- }
-
private List getAuctionParticipation(
List bidderPrivacyResults,
BidRequest bidRequest,
@@ -1157,69 +1137,39 @@ private Future processAndRequestBids(AuctionContext auctionConte
Timeout timeout,
BidderAliases aliases) {
- final String bidderName = bidderRequest.getBidder();
- final MediaTypeProcessingResult mediaTypeProcessingResult = mediaTypeProcessor.process(
- bidderRequest.getBidRequest(), bidderName, aliases, auctionContext.getAccount());
- final List mediaTypeProcessingErrors = mediaTypeProcessingResult.getErrors();
- if (mediaTypeProcessingResult.isRejected()) {
- return processReject(
- auctionContext,
- BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE,
- mediaTypeProcessingErrors,
- bidderName);
- }
- if (isUnacceptableCurrency(auctionContext, aliases.resolveBidder(bidderName))) {
- return processReject(
- auctionContext,
- BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY,
- List.of(BidderError.generic("No match between the configured currencies and bidRequest.cur")),
- bidderName);
- }
-
- return Future.succeededFuture(mediaTypeProcessingResult.getBidRequest())
- .map(bidderRequest::with)
- .compose(modifiedBidderRequest -> invokeHooksAndRequestBids(
- auctionContext, modifiedBidderRequest, timeout, aliases))
- .map(bidderResponse -> bidderResponse.with(
- addWarnings(bidderResponse.getSeatBid(), mediaTypeProcessingErrors)));
- }
-
- private boolean isUnacceptableCurrency(AuctionContext auctionContext, String originalBidderName) {
- final List requestCurrencies = auctionContext.getBidRequest().getCur();
- final List bidAcceptableCurrencies =
- Optional.ofNullable(bidderCatalog.bidderInfoByName(originalBidderName))
- .map(BidderInfo::getCurrencyAccepted)
- .orElse(null);
-
- if (CollectionUtils.isEmpty(requestCurrencies) || CollectionUtils.isEmpty(bidAcceptableCurrencies)) {
- return false;
- }
-
- return !CollectionUtils.containsAny(requestCurrencies, bidAcceptableCurrencies);
- }
-
- private static Future processReject(AuctionContext auctionContext,
- BidRejectionReason bidRejectionReason,
- List warnings,
- String bidderName) {
-
- auctionContext.getBidRejectionTrackers()
- .get(bidderName)
- .rejectAll(bidRejectionReason);
- final BidderSeatBid bidderSeatBid = BidderSeatBid.builder()
- .warnings(warnings)
- .build();
- return Future.succeededFuture(BidderResponse.of(bidderName, bidderSeatBid, 0));
+ return bidderRequestPostProcessor.process(bidderRequest, aliases, auctionContext)
+ .compose(result -> invokeHooksAndRequestBids(auctionContext, result.getValue(), timeout, aliases)
+ .map(response -> response.with(addWarnings(response.getSeatBid(), result.getErrors()))))
+ .recover(throwable -> recoverBidderRequestRejection(
+ auctionContext, bidderRequest.getBidder(), throwable));
}
private static BidderSeatBid addWarnings(BidderSeatBid seatBid, List warnings) {
return CollectionUtils.isNotEmpty(warnings)
? seatBid.toBuilder()
- .warnings(ListUtils.union(warnings, seatBid.getWarnings()))
+ .warnings(ListUtil.union(warnings, seatBid.getWarnings()))
.build()
: seatBid;
}
+ private static Future recoverBidderRequestRejection(AuctionContext auctionContext,
+ String bidderName,
+ Throwable throwable) {
+
+ if (throwable instanceof BidderRequestRejectedException rejection) {
+ auctionContext.getBidRejectionTrackers()
+ .get(bidderName)
+ .rejectAll(rejection.getRejectionReason());
+ final BidderSeatBid bidderSeatBid = BidderSeatBid.builder()
+ .warnings(rejection.getErrors())
+ .build();
+
+ return Future.succeededFuture(BidderResponse.of(bidderName, bidderSeatBid, 0));
+ }
+
+ return Future.failedFuture(throwable);
+ }
+
private Future invokeHooksAndRequestBids(AuctionContext auctionContext,
BidderRequest bidderRequest,
Timeout timeout,
diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java
new file mode 100644
index 00000000000..e470fcbbdd0
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCleaner.java
@@ -0,0 +1,37 @@
+package org.prebid.server.auction.bidderrequestpostprocessor;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iab.openrtb.request.BidRequest;
+import io.vertx.core.Future;
+import org.prebid.server.auction.aliases.BidderAliases;
+import org.prebid.server.auction.model.AuctionContext;
+import org.prebid.server.auction.model.BidderRequest;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
+
+public class BidderRequestCleaner implements BidderRequestPostProcessor {
+
+ @Override
+ public Future process(BidderRequest bidderRequest,
+ BidderAliases aliases,
+ AuctionContext auctionContext) {
+
+ final BidRequest bidRequest = bidderRequest.getBidRequest();
+ final ExtRequest ext = bidRequest.getExt();
+ final ExtRequestPrebid extPrebid = ext != null ? ext.getPrebid() : null;
+ final ObjectNode bidderControls = extPrebid != null ? extPrebid.getBiddercontrols() : null;
+
+ if (bidderControls == null) {
+ return resultOf(bidderRequest);
+ }
+
+ final ExtRequest cleanedExt = ExtRequest.of(extPrebid.toBuilder().biddercontrols(null).build());
+ cleanedExt.addProperties(ext.getProperties());
+
+ return resultOf(bidderRequest.with(bidRequest.toBuilder().ext(cleanedExt).build()));
+ }
+
+ private static Future resultOf(BidderRequest bidderRequest) {
+ return Future.succeededFuture(BidderRequestPostProcessingResult.withValue(bidderRequest));
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java
new file mode 100644
index 00000000000..a131d8a6a50
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestCurrencyBlocker.java
@@ -0,0 +1,54 @@
+package org.prebid.server.auction.bidderrequestpostprocessor;
+
+import com.iab.openrtb.request.BidRequest;
+import io.vertx.core.Future;
+import org.apache.commons.collections4.CollectionUtils;
+import org.prebid.server.auction.aliases.BidderAliases;
+import org.prebid.server.auction.model.AuctionContext;
+import org.prebid.server.auction.model.BidRejectionReason;
+import org.prebid.server.auction.model.BidderRequest;
+import org.prebid.server.bidder.BidderCatalog;
+import org.prebid.server.bidder.BidderInfo;
+import org.prebid.server.bidder.model.BidderError;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+
+public class BidderRequestCurrencyBlocker implements BidderRequestPostProcessor {
+
+ private final BidderCatalog bidderCatalog;
+
+ public BidderRequestCurrencyBlocker(BidderCatalog bidderCatalog) {
+ this.bidderCatalog = Objects.requireNonNull(bidderCatalog);
+ }
+
+ @Override
+ public Future process(BidderRequest bidderRequest,
+ BidderAliases aliases,
+ AuctionContext auctionContext) {
+
+ if (isAcceptableCurrency(bidderRequest.getBidRequest(), aliases.resolveBidder(bidderRequest.getBidder()))) {
+ return Future.succeededFuture(BidderRequestPostProcessingResult.withValue(bidderRequest));
+ }
+
+ return Future.failedFuture(new BidderRequestRejectedException(
+ BidRejectionReason.REQUEST_BLOCKED_UNACCEPTABLE_CURRENCY,
+ Collections.singletonList(
+ BidderError.generic("No match between the configured currencies and bidRequest.cur"))));
+ }
+
+ private boolean isAcceptableCurrency(BidRequest bidRequest, String originalBidderName) {
+ final List requestCurrencies = bidRequest.getCur();
+ final Set bidAcceptableCurrencies =
+ Optional.ofNullable(bidderCatalog.bidderInfoByName(originalBidderName))
+ .map(BidderInfo::getCurrencyAccepted)
+ .orElse(null);
+
+ return CollectionUtils.isEmpty(requestCurrencies)
+ || CollectionUtils.isEmpty(bidAcceptableCurrencies)
+ || requestCurrencies.stream().anyMatch(bidAcceptableCurrencies::contains);
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java
new file mode 100644
index 00000000000..b3946a11081
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestMediaFilter.java
@@ -0,0 +1,139 @@
+package org.prebid.server.auction.bidderrequestpostprocessor;
+
+import com.iab.openrtb.request.Audio;
+import com.iab.openrtb.request.Banner;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import com.iab.openrtb.request.Native;
+import com.iab.openrtb.request.Video;
+import io.vertx.core.Future;
+import org.apache.commons.lang3.ObjectUtils;
+import org.prebid.server.auction.aliases.BidderAliases;
+import org.prebid.server.auction.model.AuctionContext;
+import org.prebid.server.auction.model.BidRejectionReason;
+import org.prebid.server.auction.model.BidderRequest;
+import org.prebid.server.bidder.BidderCatalog;
+import org.prebid.server.bidder.BidderInfo;
+import org.prebid.server.bidder.model.BidderError;
+import org.prebid.server.spring.config.bidder.model.MediaType;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class BidderRequestMediaFilter implements BidderRequestPostProcessor {
+
+ private static final EnumSet NONE_OF_MEDIA_TYPES = EnumSet.noneOf(MediaType.class);
+
+ private final BidderCatalog bidderCatalog;
+
+ public BidderRequestMediaFilter(BidderCatalog bidderCatalog) {
+ this.bidderCatalog = Objects.requireNonNull(bidderCatalog);
+ }
+
+ @Override
+ public Future process(BidderRequest bidderRequest,
+ BidderAliases aliases,
+ AuctionContext auctionContext) {
+
+ final String resolvedBidderName = aliases.resolveBidder(bidderRequest.getBidder());
+ final BidRequest bidRequest = bidderRequest.getBidRequest();
+ final Set supportedMediaTypes = extractSupportedMediaTypes(bidRequest, resolvedBidderName);
+ if (supportedMediaTypes.isEmpty()) {
+ return rejected(Collections.singletonList(
+ BidderError.badInput("Bidder does not support any media types.")));
+ }
+
+ final List errors = new ArrayList<>();
+ final BidRequest modifiedBidRequest = processBidRequest(bidRequest, supportedMediaTypes, errors);
+ final BidderRequest modifiedBidderRequest = modifiedBidRequest != null
+ ? bidderRequest.with(modifiedBidRequest)
+ : null;
+
+ return modifiedBidderRequest != null
+ ? Future.succeededFuture(BidderRequestPostProcessingResult.of(modifiedBidderRequest, errors))
+ : rejected(errors);
+ }
+
+ private static Future rejected(List errors) {
+ return Future.failedFuture(
+ new BidderRequestRejectedException(BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, errors));
+ }
+
+ private Set extractSupportedMediaTypes(BidRequest bidRequest, String bidderName) {
+ final BidderInfo.CapabilitiesInfo capabilitiesInfo = bidderCatalog
+ .bidderInfoByName(bidderName)
+ .getCapabilities();
+
+ final BidderInfo.PlatformInfo site = bidRequest.getSite() != null ? capabilitiesInfo.getSite() : null;
+ final BidderInfo.PlatformInfo app = bidRequest.getApp() != null ? capabilitiesInfo.getApp() : null;
+ final BidderInfo.PlatformInfo dooh = bidRequest.getDooh() != null ? capabilitiesInfo.getDooh() : null;
+
+ return Stream.of(site, app, dooh)
+ .filter(Objects::nonNull)
+ .findFirst()
+ .map(BidderInfo.PlatformInfo::getMediaTypes)
+ .filter(mediaTypes -> !mediaTypes.isEmpty())
+ .map(EnumSet::copyOf)
+ .orElse(NONE_OF_MEDIA_TYPES);
+ }
+
+ private static BidRequest processBidRequest(BidRequest bidRequest,
+ Set supportedMediaTypes,
+ List errors) {
+
+ final List modifiedImps = bidRequest.getImp().stream()
+ .map(imp -> processImp(imp, supportedMediaTypes, errors))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ if (modifiedImps.isEmpty()) {
+ errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering."));
+ return null;
+ }
+
+ return bidRequest.toBuilder().imp(modifiedImps).build();
+ }
+
+ private static Imp processImp(Imp imp, Set supportedMediaTypes, List errors) {
+ final Set impMediaTypes = getMediaTypes(imp);
+ if (supportedMediaTypes.containsAll(impMediaTypes)) {
+ return imp;
+ }
+
+ final Banner banner = supportedMediaTypes.contains(MediaType.BANNER) ? imp.getBanner() : null;
+ final Video video = supportedMediaTypes.contains(MediaType.VIDEO) ? imp.getVideo() : null;
+ final Audio audio = supportedMediaTypes.contains(MediaType.AUDIO) ? imp.getAudio() : null;
+ final Native xNative = supportedMediaTypes.contains(MediaType.NATIVE) ? imp.getXNative() : null;
+
+ if (ObjectUtils.allNull(banner, video, audio, xNative)) {
+ errors.add(BidderError.badInput("""
+ Imp %s does not have a supported media type \
+ and has been removed from the request for this bidder.""".formatted(imp.getId())));
+
+ return null;
+ }
+
+ return imp.toBuilder()
+ .banner(banner)
+ .video(video)
+ .audio(audio)
+ .xNative(xNative)
+ .build();
+ }
+
+ private static Set getMediaTypes(Imp imp) {
+ return Stream.of(
+ imp.getBanner() != null ? MediaType.BANNER : null,
+ imp.getVideo() != null ? MediaType.VIDEO : null,
+ imp.getAudio() != null ? MediaType.AUDIO : null,
+ imp.getXNative() != null ? MediaType.NATIVE : null)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toCollection(() -> EnumSet.noneOf(MediaType.class)));
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessingResult.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessingResult.java
new file mode 100644
index 00000000000..f38d9a1a423
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessingResult.java
@@ -0,0 +1,20 @@
+package org.prebid.server.auction.bidderrequestpostprocessor;
+
+import lombok.Value;
+import org.prebid.server.auction.model.BidderRequest;
+import org.prebid.server.bidder.model.BidderError;
+
+import java.util.Collections;
+import java.util.List;
+
+@Value(staticConstructor = "of")
+public class BidderRequestPostProcessingResult {
+
+ BidderRequest value;
+
+ List errors;
+
+ public static BidderRequestPostProcessingResult withValue(BidderRequest bidderRequest) {
+ return BidderRequestPostProcessingResult.of(bidderRequest, Collections.emptyList());
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java
new file mode 100644
index 00000000000..e2fc97f225f
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPostProcessor.java
@@ -0,0 +1,13 @@
+package org.prebid.server.auction.bidderrequestpostprocessor;
+
+import io.vertx.core.Future;
+import org.prebid.server.auction.aliases.BidderAliases;
+import org.prebid.server.auction.model.AuctionContext;
+import org.prebid.server.auction.model.BidderRequest;
+
+public interface BidderRequestPostProcessor {
+
+ Future process(BidderRequest bidderRequest,
+ BidderAliases aliases,
+ AuctionContext auctionContext);
+}
diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java
new file mode 100644
index 00000000000..fb685298a4a
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestPreferredMediaProcessor.java
@@ -0,0 +1,155 @@
+package org.prebid.server.auction.bidderrequestpostprocessor;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.iab.openrtb.request.Audio;
+import com.iab.openrtb.request.Banner;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import com.iab.openrtb.request.Native;
+import com.iab.openrtb.request.Video;
+import io.vertx.core.Future;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.prebid.server.auction.aliases.BidderAliases;
+import org.prebid.server.auction.model.AuctionContext;
+import org.prebid.server.auction.model.BidRejectionReason;
+import org.prebid.server.auction.model.BidderRequest;
+import org.prebid.server.bidder.BidderCatalog;
+import org.prebid.server.bidder.model.BidderError;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
+import org.prebid.server.settings.model.Account;
+import org.prebid.server.settings.model.AccountAuctionConfig;
+import org.prebid.server.spring.config.bidder.model.MediaType;
+import org.prebid.server.util.StreamUtil;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class BidderRequestPreferredMediaProcessor implements BidderRequestPostProcessor {
+
+ private static final String PREF_MTYPE_FIELD = "prefmtype";
+
+ private final BidderCatalog bidderCatalog;
+
+ public BidderRequestPreferredMediaProcessor(BidderCatalog bidderCatalog) {
+ this.bidderCatalog = Objects.requireNonNull(bidderCatalog);
+ }
+
+ @Override
+ public Future process(BidderRequest bidderRequest,
+ BidderAliases aliases,
+ AuctionContext auctionContext) {
+
+ final String bidderName = bidderRequest.getBidder();
+ final BidRequest bidRequest = bidderRequest.getBidRequest();
+
+ final String resolvedBidderName = aliases.resolveBidder(bidderName);
+ if (isMultiFormatSupported(resolvedBidderName)) {
+ return noAction(bidderRequest);
+ }
+
+ final Optional preferredMediaType = preferredMediaType(bidRequest, bidderName)
+ .or(() -> preferredMediaType(auctionContext.getAccount(), resolvedBidderName));
+ if (preferredMediaType.isEmpty()) {
+ return noAction(bidderRequest);
+ }
+
+ final List errors = new ArrayList<>();
+ final BidRequest modifiedBidRequest = processBidRequest(bidRequest, preferredMediaType.get(), errors);
+ final BidderRequest modifiedBidderRequest = modifiedBidRequest != null
+ ? bidderRequest.with(modifiedBidRequest)
+ : null;
+
+ return modifiedBidderRequest != null
+ ? Future.succeededFuture(BidderRequestPostProcessingResult.of(modifiedBidderRequest, errors))
+ : Future.failedFuture(new BidderRequestRejectedException(
+ BidRejectionReason.REQUEST_BLOCKED_UNSUPPORTED_MEDIA_TYPE, errors));
+ }
+
+ private static Future noAction(BidderRequest bidderRequest) {
+ return Future.succeededFuture(BidderRequestPostProcessingResult.withValue(bidderRequest));
+ }
+
+ private boolean isMultiFormatSupported(String bidder) {
+ return bidderCatalog.bidderInfoByName(bidder).getOrtb().isMultiFormatSupported();
+ }
+
+ private static Optional preferredMediaType(BidRequest bidRequest, String bidderName) {
+ return Optional.ofNullable(bidRequest.getExt())
+ .map(ExtRequest::getPrebid)
+ .map(ExtRequestPrebid::getBiddercontrols)
+ .flatMap(bidders -> getBidder(bidderName, bidders))
+ .map(bidder -> bidder.get(PREF_MTYPE_FIELD))
+ .filter(JsonNode::isTextual)
+ .map(JsonNode::textValue)
+ .map(MediaType::of);
+ }
+
+ private static Optional preferredMediaType(Account account, String bidderName) {
+ return Optional.ofNullable(account.getAuction())
+ .map(AccountAuctionConfig::getPreferredMediaTypes)
+ .map(preferredMediaTypes -> preferredMediaTypes.get(bidderName));
+ }
+
+ private static Optional getBidder(String bidderName, JsonNode biddersNode) {
+ return StreamUtil.asStream(biddersNode.fieldNames())
+ .filter(fieldName -> StringUtils.equalsIgnoreCase(bidderName, fieldName))
+ .map(biddersNode::get)
+ .findAny();
+ }
+
+ private static BidRequest processBidRequest(BidRequest bidRequest,
+ MediaType preferredMediaType,
+ List errors) {
+
+ final List modifiedImps = bidRequest.getImp().stream()
+ .map(imp -> processImp(imp, preferredMediaType, errors))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ if (modifiedImps.isEmpty()) {
+ errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering."));
+ return null;
+ }
+
+ return bidRequest.toBuilder().imp(modifiedImps).build();
+ }
+
+ private static Imp processImp(Imp imp, MediaType preferredMediaType, List errors) {
+ if (!isMultiFormat(imp)) {
+ return imp;
+ }
+
+ final Banner banner = preferredMediaType == MediaType.BANNER ? imp.getBanner() : null;
+ final Video video = preferredMediaType == MediaType.VIDEO ? imp.getVideo() : null;
+ final Audio audio = preferredMediaType == MediaType.AUDIO ? imp.getAudio() : null;
+ final Native xNative = preferredMediaType == MediaType.NATIVE ? imp.getXNative() : null;
+
+ if (ObjectUtils.allNull(banner, video, audio, xNative)) {
+ errors.add(BidderError.badInput("""
+ Imp %s does not have a media type after filtering \
+ and has been removed from the request for this bidder.""".formatted(imp.getId())));
+
+ return null;
+ }
+
+ return imp.toBuilder()
+ .banner(banner)
+ .video(video)
+ .audio(audio)
+ .xNative(xNative)
+ .build();
+ }
+
+ private static boolean isMultiFormat(Imp imp) {
+ int count = 0;
+ return (imp.getBanner() != null && ++count > 1)
+ || (imp.getVideo() != null && ++count > 1)
+ || (imp.getAudio() != null && ++count > 1)
+ || (imp.getXNative() != null && ++count > 1);
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java
new file mode 100644
index 00000000000..6beeaa5069f
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/BidderRequestRejectedException.java
@@ -0,0 +1,20 @@
+package org.prebid.server.auction.bidderrequestpostprocessor;
+
+import lombok.Getter;
+import org.prebid.server.auction.model.BidRejectionReason;
+import org.prebid.server.bidder.model.BidderError;
+
+import java.util.List;
+import java.util.Objects;
+
+@Getter
+public class BidderRequestRejectedException extends RuntimeException {
+
+ private final BidRejectionReason rejectionReason;
+ private final List errors;
+
+ public BidderRequestRejectedException(BidRejectionReason bidRejectionReason, List errors) {
+ this.rejectionReason = Objects.requireNonNull(bidRejectionReason);
+ this.errors = Objects.requireNonNull(errors);
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java
new file mode 100644
index 00000000000..b483c2ad63e
--- /dev/null
+++ b/src/main/java/org/prebid/server/auction/bidderrequestpostprocessor/CompositeBidderRequestPostProcessor.java
@@ -0,0 +1,46 @@
+package org.prebid.server.auction.bidderrequestpostprocessor;
+
+import io.vertx.core.Future;
+import org.prebid.server.auction.aliases.BidderAliases;
+import org.prebid.server.auction.model.AuctionContext;
+import org.prebid.server.auction.model.BidderRequest;
+import org.prebid.server.util.ListUtil;
+
+import java.util.List;
+import java.util.Objects;
+
+public class CompositeBidderRequestPostProcessor implements BidderRequestPostProcessor {
+
+ private final List bidderRequestPostProcessors;
+
+ public CompositeBidderRequestPostProcessor(List bidderRequestPostProcessors) {
+ this.bidderRequestPostProcessors = Objects.requireNonNull(bidderRequestPostProcessors);
+ }
+
+ @Override
+ public Future process(BidderRequest bidderRequest,
+ BidderAliases aliases,
+ AuctionContext auctionContext) {
+
+ Future result = initialResult(bidderRequest);
+ for (BidderRequestPostProcessor bidderRequestPostProcessor : bidderRequestPostProcessors) {
+ result = result.compose(previous ->
+ bidderRequestPostProcessor.process(previous.getValue(), aliases, auctionContext)
+ .map(current -> mergeErrors(previous, current)));
+ }
+
+ return result;
+ }
+
+ private static Future initialResult(BidderRequest bidderRequest) {
+ return Future.succeededFuture(BidderRequestPostProcessingResult.withValue(bidderRequest));
+ }
+
+ private static BidderRequestPostProcessingResult mergeErrors(BidderRequestPostProcessingResult previous,
+ BidderRequestPostProcessingResult current) {
+
+ return BidderRequestPostProcessingResult.of(
+ current.getValue(),
+ ListUtil.union(previous.getErrors(), current.getErrors()));
+ }
+}
diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java
deleted file mode 100644
index 17828059175..00000000000
--- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/BidderMediaTypeProcessor.java
+++ /dev/null
@@ -1,134 +0,0 @@
-package org.prebid.server.auction.mediatypeprocessor;
-
-import com.iab.openrtb.request.BidRequest;
-import com.iab.openrtb.request.Imp;
-import org.apache.commons.collections4.SetUtils;
-import org.prebid.server.auction.aliases.BidderAliases;
-import org.prebid.server.bidder.BidderCatalog;
-import org.prebid.server.bidder.BidderInfo;
-import org.prebid.server.bidder.model.BidderError;
-import org.prebid.server.settings.model.Account;
-import org.prebid.server.spring.config.bidder.model.MediaType;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.function.Supplier;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-/**
- * {@link BidderMediaTypeProcessor} is an implementation of {@link MediaTypeProcessor} that
- * can be used to remove media types from {@link Imp} unsupported by specific bidder.
- */
-public class BidderMediaTypeProcessor implements MediaTypeProcessor {
-
- private static final EnumSet NONE_OF_MEDIA_TYPES = EnumSet.noneOf(MediaType.class);
-
- private final BidderCatalog bidderCatalog;
-
- public BidderMediaTypeProcessor(BidderCatalog bidderCatalog) {
- this.bidderCatalog = Objects.requireNonNull(bidderCatalog);
- }
-
- @Override
- public MediaTypeProcessingResult process(BidRequest bidRequest,
- String bidderName,
- BidderAliases aliases,
- Account account) {
- final String resolvedBidderName = aliases.resolveBidder(bidderName);
- final Set supportedMediaTypes = extractSupportedMediaTypes(bidRequest, resolvedBidderName);
- if (supportedMediaTypes.isEmpty()) {
- return MediaTypeProcessingResult.rejected(Collections.singletonList(
- BidderError.badInput("Bidder does not support any media types.")));
- }
-
- final List errors = new ArrayList<>();
- final BidRequest modifiedBidRequest = processBidRequest(bidRequest, supportedMediaTypes, errors);
-
- return modifiedBidRequest != null
- ? MediaTypeProcessingResult.succeeded(modifiedBidRequest, errors)
- : MediaTypeProcessingResult.rejected(errors);
- }
-
- private Set extractSupportedMediaTypes(BidRequest bidRequest, String bidderName) {
- final BidderInfo.CapabilitiesInfo capabilitiesInfo = bidderCatalog.bidderInfoByName(bidderName)
- .getCapabilities();
-
- final Supplier fetchSupportedMediaTypes;
- if (bidRequest.getSite() != null) {
- fetchSupportedMediaTypes = capabilitiesInfo::getSite;
- } else if (bidRequest.getApp() != null) {
- fetchSupportedMediaTypes = capabilitiesInfo::getApp;
- } else {
- fetchSupportedMediaTypes = capabilitiesInfo::getDooh;
- }
-
- return Optional.ofNullable(fetchSupportedMediaTypes.get())
- .map(BidderInfo.PlatformInfo::getMediaTypes)
- .filter(mediaTypes -> !mediaTypes.isEmpty())
- .map(EnumSet::copyOf)
- .orElse(NONE_OF_MEDIA_TYPES);
- }
-
- private BidRequest processBidRequest(BidRequest bidRequest,
- Set supportedMediaTypes,
- List errors) {
-
- final List modifiedImps = bidRequest.getImp().stream()
- .map(imp -> processImp(imp, supportedMediaTypes, errors))
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
-
- if (modifiedImps.isEmpty()) {
- errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering."));
- return null;
- }
-
- return bidRequest.toBuilder().imp(modifiedImps).build();
- }
-
- private static Imp processImp(Imp imp, Set supportedMediaTypes, List errors) {
- final Set impMediaTypes = getMediaTypes(imp);
- final Set unsupportedMediaTypes = SetUtils.difference(impMediaTypes, supportedMediaTypes);
-
- if (unsupportedMediaTypes.isEmpty()) {
- return imp;
- }
-
- if (impMediaTypes.equals(unsupportedMediaTypes)) {
- errors.add(BidderError.badInput("Imp " + imp.getId() + " does not have a supported media type "
- + "and has been removed from the request for this bidder."));
-
- return null;
- }
-
- final Imp.ImpBuilder impBuilder = imp.toBuilder();
- unsupportedMediaTypes.forEach(unsupportedMediaType -> removeMediaType(impBuilder, unsupportedMediaType));
-
- return impBuilder.build();
- }
-
- private static Set getMediaTypes(Imp imp) {
- return Stream.of(
- imp.getBanner() != null ? MediaType.BANNER : null,
- imp.getVideo() != null ? MediaType.VIDEO : null,
- imp.getAudio() != null ? MediaType.AUDIO : null,
- imp.getXNative() != null ? MediaType.NATIVE : null)
- .filter(Objects::nonNull)
- .collect(Collectors.toCollection(() -> EnumSet.noneOf(MediaType.class)));
- }
-
- private static void removeMediaType(Imp.ImpBuilder impBuilder, MediaType mediaType) {
- switch (mediaType) {
- case BANNER -> impBuilder.banner(null);
- case VIDEO -> impBuilder.video(null);
- case AUDIO -> impBuilder.audio(null);
- case NATIVE -> impBuilder.xNative(null);
- }
- }
-}
diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java
deleted file mode 100644
index 5dbd2bb3ae6..00000000000
--- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/CompositeMediaTypeProcessor.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package org.prebid.server.auction.mediatypeprocessor;
-
-import com.iab.openrtb.request.BidRequest;
-import org.prebid.server.auction.aliases.BidderAliases;
-import org.prebid.server.bidder.model.BidderError;
-import org.prebid.server.settings.model.Account;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-public class CompositeMediaTypeProcessor implements MediaTypeProcessor {
-
- private final List mediaTypeProcessors;
-
- public CompositeMediaTypeProcessor(List mediaTypeProcessors) {
- this.mediaTypeProcessors = Objects.requireNonNull(mediaTypeProcessors);
- }
-
- @Override
- public MediaTypeProcessingResult process(BidRequest originalBidRequest,
- String bidderName,
- BidderAliases aliases,
- Account account) {
- BidRequest bidRequest = originalBidRequest;
- final List errors = new ArrayList<>();
-
- for (MediaTypeProcessor mediaTypeProcessor : mediaTypeProcessors) {
- final MediaTypeProcessingResult result = mediaTypeProcessor.process(
- bidRequest,
- bidderName,
- aliases,
- account);
-
- bidRequest = result.getBidRequest();
- errors.addAll(result.getErrors());
-
- if (result.isRejected()) {
- return MediaTypeProcessingResult.rejected(errors);
- }
- }
-
- return MediaTypeProcessingResult.succeeded(bidRequest, errors);
- }
-}
diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java
deleted file mode 100644
index b9a6780d2e8..00000000000
--- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessingResult.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package org.prebid.server.auction.mediatypeprocessor;
-
-import com.iab.openrtb.request.BidRequest;
-import lombok.Value;
-import org.prebid.server.bidder.model.BidderError;
-
-import java.util.List;
-
-@Value(staticConstructor = "of")
-public class MediaTypeProcessingResult {
-
- BidRequest bidRequest;
-
- List errors;
-
- boolean rejected;
-
- public static MediaTypeProcessingResult succeeded(BidRequest bidRequest, List errors) {
- return MediaTypeProcessingResult.of(bidRequest, errors, false);
- }
-
- public static MediaTypeProcessingResult rejected(List errors) {
- return MediaTypeProcessingResult.of(null, errors, true);
- }
-}
diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java
deleted file mode 100644
index 0cf5f19bd6a..00000000000
--- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MediaTypeProcessor.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.prebid.server.auction.mediatypeprocessor;
-
-import com.iab.openrtb.request.BidRequest;
-import org.prebid.server.auction.aliases.BidderAliases;
-import org.prebid.server.settings.model.Account;
-
-public interface MediaTypeProcessor {
-
- MediaTypeProcessingResult process(BidRequest bidRequest, String bidderName, BidderAliases aliases, Account account);
-}
diff --git a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java b/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java
deleted file mode 100644
index 0fda09f919e..00000000000
--- a/src/main/java/org/prebid/server/auction/mediatypeprocessor/MultiFormatMediaTypeProcessor.java
+++ /dev/null
@@ -1,143 +0,0 @@
-package org.prebid.server.auction.mediatypeprocessor;
-
-import com.fasterxml.jackson.databind.JsonNode;
-import com.iab.openrtb.request.BidRequest;
-import com.iab.openrtb.request.Imp;
-import org.apache.commons.lang3.StringUtils;
-import org.prebid.server.auction.aliases.BidderAliases;
-import org.prebid.server.bidder.BidderCatalog;
-import org.prebid.server.bidder.model.BidderError;
-import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
-import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebid;
-import org.prebid.server.settings.model.Account;
-import org.prebid.server.settings.model.AccountAuctionConfig;
-import org.prebid.server.spring.config.bidder.model.MediaType;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-import java.util.Optional;
-
-public class MultiFormatMediaTypeProcessor implements MediaTypeProcessor {
-
- private static final String PREF_MTYPE_FIELD = "prefmtype";
-
- private final BidderCatalog bidderCatalog;
-
- public MultiFormatMediaTypeProcessor(BidderCatalog bidderCatalog) {
- this.bidderCatalog = Objects.requireNonNull(bidderCatalog);
- }
-
- @Override
- public MediaTypeProcessingResult process(BidRequest bidRequest,
- String bidderName,
- BidderAliases aliases,
- Account account) {
- final String resolvedBidderName = aliases.resolveBidder(bidderName);
- //todo: ext.prebid.biddercontrols clean-up should NOT be here
- // Suggestion: keep biddercontrols in the Auction Context
- // and clean it up on the extraction auction participants step
- final BidRequest.BidRequestBuilder bidRequestBuilder = Optional.ofNullable(bidRequest.getExt())
- .map(ExtRequest::getPrebid)
- .map(prebid -> prebid.toBuilder().biddercontrols(null).build())
- .map(prebid -> {
- final ExtRequest extRequest = ExtRequest.of(prebid);
- extRequest.addProperties(bidRequest.getExt().getProperties());
- return extRequest;
- })
- .map(extRequest -> bidRequest.toBuilder().ext(extRequest))
- .orElse(bidRequest.toBuilder());
- if (isMultiFormatSupported(resolvedBidderName)) {
- return MediaTypeProcessingResult.succeeded(bidRequestBuilder.build(), Collections.emptyList());
- }
-
- final MediaType preferredMediaType = preferredMediaType(bidRequest, account, bidderName, resolvedBidderName);
- if (preferredMediaType == null) {
- return MediaTypeProcessingResult.succeeded(bidRequestBuilder.build(), Collections.emptyList());
- }
-
- final List errors = new ArrayList<>();
- final List updatedImps = bidRequest.getImp().stream()
- .map(imp -> processImp(imp, preferredMediaType, errors))
- .filter(Objects::nonNull)
- .toList();
-
- if (updatedImps.isEmpty()) {
- errors.add(BidderError.badInput("Bid request contains 0 impressions after filtering."));
- return MediaTypeProcessingResult.rejected(errors);
- }
-
- return MediaTypeProcessingResult.succeeded(bidRequestBuilder.imp(updatedImps).build(), errors);
- }
-
- private boolean isMultiFormatSupported(String bidder) {
- return bidderCatalog.bidderInfoByName(bidder).getOrtb().isMultiFormatSupported();
- }
-
- private MediaType preferredMediaType(BidRequest bidRequest,
- Account account,
- String originalBidderName,
- String resolvedBidderName) {
-
- return Optional.ofNullable(bidRequest.getExt())
- .map(ExtRequest::getPrebid)
- .map(ExtRequestPrebid::getBiddercontrols)
- .map(bidders -> getBidder(originalBidderName, bidders))
- .map(bidder -> bidder.get(PREF_MTYPE_FIELD))
- .filter(JsonNode::isTextual)
- .map(JsonNode::textValue)
- .map(MediaType::of)
- .or(() -> Optional.ofNullable(account.getAuction())
- .map(AccountAuctionConfig::getPreferredMediaTypes)
- .map(preferredMediaTypes -> preferredMediaTypes.get(resolvedBidderName)))
- .orElse(null);
- }
-
- private static JsonNode getBidder(String bidderName, JsonNode biddersNode) {
- final Iterator fieldNames = biddersNode.fieldNames();
- while (fieldNames.hasNext()) {
- final String fieldName = fieldNames.next();
- if (StringUtils.equalsIgnoreCase(bidderName, fieldName)) {
- return biddersNode.get(fieldName);
- }
- }
- return null;
- }
-
- private static Imp processImp(Imp imp, MediaType preferredMediaType, List errors) {
- if (!isMultiFormat(imp)) {
- return imp;
- }
-
- final Imp updatedImp = switch (preferredMediaType) {
- case BANNER -> imp.getBanner() != null
- ? imp.toBuilder().video(null).audio(null).xNative(null).build()
- : null;
- case VIDEO -> imp.getVideo() != null
- ? imp.toBuilder().banner(null).audio(null).xNative(null).build()
- : null;
- case AUDIO -> imp.getAudio() != null
- ? imp.toBuilder().banner(null).video(null).xNative(null).build()
- : null;
- case NATIVE -> imp.getXNative() != null
- ? imp.toBuilder().banner(null).video(null).audio(null).build()
- : null;
- };
-
- if (updatedImp == null) {
- errors.add(BidderError.badInput("Imp " + imp.getId() + " does not have a media type after filtering "
- + "and has been removed from the request for this bidder."));
- }
- return updatedImp;
- }
-
- private static boolean isMultiFormat(Imp imp) {
- int count = 0;
- return (imp.getBanner() != null && ++count > 1)
- || (imp.getVideo() != null && ++count > 1)
- || (imp.getAudio() != null && ++count > 1)
- || (imp.getXNative() != null && ++count > 1);
- }
-}
diff --git a/src/main/java/org/prebid/server/bidder/BidderInfo.java b/src/main/java/org/prebid/server/bidder/BidderInfo.java
index 1ff8f323701..d26cc2c4b2c 100644
--- a/src/main/java/org/prebid/server/bidder/BidderInfo.java
+++ b/src/main/java/org/prebid/server/bidder/BidderInfo.java
@@ -7,7 +7,9 @@
import org.prebid.server.spring.config.bidder.model.CompressionType;
import org.prebid.server.spring.config.bidder.model.MediaType;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
@Value(staticConstructor = "of")
public class BidderInfo {
@@ -28,7 +30,7 @@ public class BidderInfo {
List vendors;
- List currencyAccepted;
+ Set currencyAccepted;
GdprInfo gdpr;
@@ -72,7 +74,7 @@ public static BidderInfo create(boolean enabled,
platformInfo(siteMediaTypes),
platformInfo(doohMediaTypes)),
supportedVendors,
- currencyAccepted,
+ currencyAccepted != null ? new HashSet<>(currencyAccepted) : null,
new GdprInfo(vendorId),
ccpaEnforced,
modifyingVastXmlAllowed,
diff --git a/src/main/java/org/prebid/server/bidder/adagio/AdagioBidder.java b/src/main/java/org/prebid/server/bidder/adagio/AdagioBidder.java
index 460d6ac2e4e..a211f6267b4 100644
--- a/src/main/java/org/prebid/server/bidder/adagio/AdagioBidder.java
+++ b/src/main/java/org/prebid/server/bidder/adagio/AdagioBidder.java
@@ -59,12 +59,12 @@ private List extractBids(BidResponse bidResponse, List e
.filter(Objects::nonNull)
.flatMap(seatBid -> seatBid.getBid().stream()
.filter(Objects::nonNull)
- .map(bid -> makeBid(bid, bidResponse.getCur(), seatBid.getSeat(), errors)))
+ .map(bid -> makeBid(bid, bidResponse.getCur(), errors)))
.filter(Objects::nonNull)
.toList();
}
- private BidderBid makeBid(Bid bid, String currency, String seat, List errors) {
+ private BidderBid makeBid(Bid bid, String currency, List errors) {
try {
final ExtBidPrebid extBidPrebid = parseBidExt(bid);
return BidderBid.builder()
@@ -72,7 +72,6 @@ private BidderBid makeBid(Bid bid, String currency, String seat, List {
-
- private static final TypeReference> ADOCEAN_EXT_TYPE_REFERENCE =
- new TypeReference<>() {
- };
- private static final String VERSION = "1.3.0";
- private static final int MAX_URI_LENGTH = 8000;
- private static final String MEASUREMENT_CODE_TEMPLATE = """
- """;
-
- private final String endpointUrl;
- private final JacksonMapper mapper;
-
- public AdoceanBidder(String endpointUrl, JacksonMapper mapper) {
- this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
- this.mapper = Objects.requireNonNull(mapper);
- }
-
- @Override
- public Result>> makeHttpRequests(BidRequest request) {
- final List errors = new ArrayList<>();
- final User user = request.getUser();
- final ExtUser extUser = user != null ? user.getExt() : null;
- final String consentString = extUser != null ? extUser.getConsent() : "";
-
- final List> httpRequests = new ArrayList<>();
- for (Imp imp : request.getImp()) {
- try {
- final ExtImpAdocean extImpAdocean = parseImpExt(imp);
- validateImpExt(extImpAdocean);
-
- final Map slaveSizes = new HashMap<>();
- slaveSizes.put(extImpAdocean.getSlaveId(), getImpSizes(imp));
- if (addRequestAndCheckIfDuplicates(httpRequests, extImpAdocean, imp.getId(), slaveSizes,
- request.getTest())) {
- continue;
- }
- httpRequests.add(createSingleRequest(request, imp, extImpAdocean, consentString, slaveSizes));
- } catch (PreBidException e) {
- errors.add(BidderError.badInput(e.getMessage()));
- }
- }
-
- return Result.of(httpRequests, errors);
- }
-
- private ExtImpAdocean parseImpExt(Imp imp) {
- try {
- return mapper.mapper().convertValue(imp.getExt(), ADOCEAN_EXT_TYPE_REFERENCE).getBidder();
- } catch (IllegalArgumentException e) {
- throw new PreBidException(
- "Error parsing adOceanExt parameters, in imp with id : " + imp.getId());
- }
- }
-
- private static void validateImpExt(ExtImpAdocean impExt) {
- if (StringUtils.isEmpty(impExt.getEmitterPrefix())) {
- throw new PreBidException("No emitterPrefix param");
- }
- }
-
- private boolean addRequestAndCheckIfDuplicates(List> httpRequests, ExtImpAdocean extImpAdocean,
- String impid, Map slaveSizes, Integer test) {
- for (HttpRequest request : httpRequests) {
- try {
- final URIBuilder uriBuilder = new URIBuilder(request.getUri());
- final List queryParams = uriBuilder.getQueryParams();
-
- final String masterId = queryParams.stream()
- .filter(param -> "id".equals(param.getName()))
- .findFirst()
- .map(NameValuePair::getValue)
- .orElse(null);
-
- if (masterId != null && masterId.equals(extImpAdocean.getMasterId())) {
- final boolean isExistingSlaveId = queryParams.stream()
- .filter(param -> "aid".equals(param.getName()))
- .map(param -> param.getValue().split(":")[0])
- .anyMatch(slaveId -> slaveId.equals(extImpAdocean.getSlaveId()));
- if (isExistingSlaveId) {
- continue;
- }
-
- queryParams.add(new BasicNameValuePair("aid", extImpAdocean.getSlaveId() + ":" + impid));
- final List sizeValues = setSlaveSizesParam(slaveSizes, Objects.equals(test, 1));
- if (CollectionUtils.isNotEmpty(sizeValues)) {
- queryParams.add(new BasicNameValuePair("aosspsizes", String.join("-", sizeValues)));
- }
- uriBuilder.setParameters(queryParams);
-
- final String url = uriBuilder.toString();
- if (url.length() < MAX_URI_LENGTH) {
- final HttpRequest updatedRequest = HttpRequest.builder()
- .method(HttpMethod.GET)
- .uri(url)
- .headers(request.getHeaders())
- .build();
- httpRequests.remove(request);
- httpRequests.add(updatedRequest);
- return true;
- }
- }
- } catch (URISyntaxException e) {
- throw new PreBidException(e.getMessage());
- }
- }
- return false;
- }
-
- private String getImpSizes(Imp imp) {
- final Banner banner = imp.getBanner();
- if (banner == null) {
- return "";
- }
-
- final List format = banner.getFormat();
- if (CollectionUtils.isNotEmpty(format)) {
- final List sizes = new ArrayList<>();
- format.forEach(singleFormat -> sizes.add(
- "%sx%s".formatted(getIntOrElseZero(singleFormat.getW()), getIntOrElseZero(singleFormat.getH()))));
- return String.join("_", sizes);
- }
-
- final Integer w = banner.getW();
- final Integer h = banner.getH();
- if (w != null && h != null) {
- return "%sx%s".formatted(w, h);
- }
-
- return StringUtils.EMPTY;
- }
-
- private int getIntOrElseZero(Integer number) {
- return number != null ? number : 0;
- }
-
- private HttpRequest createSingleRequest(BidRequest request, Imp imp, ExtImpAdocean extImpAdocean,
- String consentString, Map slaveSizes) {
-
- return HttpRequest.builder()
- .method(HttpMethod.GET)
- .uri(buildUrl(imp.getId(), extImpAdocean, consentString, request, slaveSizes))
- .headers(getHeaders(request))
- .build();
- }
-
- private String buildUrl(String impId, ExtImpAdocean extImpAdocean, String consentString, BidRequest bidRequest,
- Map slaveSizes) {
-
- final Integer test = bidRequest.getTest();
- final String resolvedUrl = resolveEndpointUrl(extImpAdocean, test);
-
- final URIBuilder uriBuilder;
- try {
- uriBuilder = new URIBuilder(resolvedUrl);
- } catch (URISyntaxException e) {
- throw new PreBidException("Invalid url: %s, error: %s".formatted(resolvedUrl, e.getMessage()));
- }
-
- uriBuilder
- .addParameter("pbsrv_v", VERSION)
- .addParameter("id", extImpAdocean.getMasterId())
- .addParameter("nc", "1")
- .addParameter("nosecure", "1")
- .addParameter("aid", extImpAdocean.getSlaveId() + ":" + impId);
-
- if (StringUtils.isNotEmpty(consentString)) {
- uriBuilder.addParameter("gdpr_consent", consentString);
- uriBuilder.addParameter("gdpr", "1");
- }
-
- final User user = bidRequest.getUser();
- if (user != null && StringUtils.isNotEmpty(user.getBuyeruid())) {
- uriBuilder.addParameter("hcuserid", user.getBuyeruid());
- }
-
- final App app = bidRequest.getApp();
- if (app != null) {
- uriBuilder.addParameter("app", "1");
- uriBuilder.addParameter("appname", app.getName());
- uriBuilder.addParameter("appbundle", app.getBundle());
- uriBuilder.addParameter("appdomain", app.getDomain());
- }
-
- final Device device = bidRequest.getDevice();
- if (device != null) {
- if (StringUtils.isNotEmpty(device.getIfa())) {
- uriBuilder.addParameter("ifa", device.getIfa());
- } else {
- uriBuilder.addParameter("dpidmd5", device.getDpidmd5());
- }
-
- uriBuilder.addParameter("devos", device.getOs());
- uriBuilder.addParameter("devosv", device.getOsv());
- uriBuilder.addParameter("devmodel", device.getModel());
- uriBuilder.addParameter("devmake", device.getMake());
- }
-
- final List sizeValues = setSlaveSizesParam(slaveSizes, Objects.equals(test, 1));
-
- if (CollectionUtils.isNotEmpty(sizeValues)) {
- uriBuilder.addParameter("aosspsizes", String.join("-", sizeValues));
- }
-
- return uriBuilder.toString();
- }
-
- private String resolveEndpointUrl(ExtImpAdocean extImpAdocean, Integer test) {
- final String url = endpointUrl.replace("{{Host}}", extImpAdocean.getEmitterPrefix());
- final int randomizedPart = Objects.equals(test, 1) ? 10000000 : 10000000 + (int) (Math.random() * 89999999);
- return "%s/_%s/ad.json".formatted(url, randomizedPart);
- }
-
- private List setSlaveSizesParam(Map slaveSizes, boolean orderByKey) {
- final Set slaveIDs = orderByKey ? new TreeSet<>(slaveSizes.keySet()) : slaveSizes.keySet();
-
- return slaveIDs.stream()
- .filter(slaveId -> StringUtils.isNotEmpty(slaveSizes.get(slaveId)))
- .map(rawSlaveID -> "%s~%s".formatted(
- rawSlaveID.replaceFirst("adocean", ""),
- slaveSizes.get(rawSlaveID)))
- .toList();
- }
-
- private static MultiMap getHeaders(BidRequest request) {
- final MultiMap headers = HttpUtil.headers();
-
- final Device device = request.getDevice();
- if (device != null) {
- HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER, device.getUa());
- HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIpv6());
- HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER, device.getIp());
- }
-
- final Site site = request.getSite();
- if (site != null) {
- HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.REFERER_HEADER, site.getPage());
- }
-
- return headers;
- }
-
- @Override
- public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) {
- final List params;
- try {
- params = URLEncodedUtils.parse(new URI(httpCall.getRequest().getUri()), StandardCharsets.UTF_8);
- } catch (URISyntaxException e) {
- return Result.withError(BidderError.badInput(e.getMessage()));
- }
-
- final Map auctionIds = params != null ? params.stream()
- .filter(param -> "aid".equals(param.getName()))
- .map(param -> param.getValue().split(":"))
- .collect(Collectors.toMap(name -> name[0], value -> value[1])) : null;
-
- final List adoceanResponses;
- try {
- adoceanResponses = getAdoceanResponseAdUnitList(httpCall.getResponse().getBody());
- } catch (PreBidException e) {
- return Result.withError(BidderError
- .badServerResponse("Failed to decode: No content to map due to end-of-input"));
- }
-
- final List bidderBids = adoceanResponses.stream()
- .filter(adoceanResponse -> !"true".equals(adoceanResponse.getError()))
- .filter(adoceanResponse ->
- StringUtils.isNotBlank(MapUtils.getString(auctionIds, adoceanResponse.getId())))
- .map(adoceanResponse -> BidderBid.of(createBid(auctionIds, adoceanResponse), BidType.banner,
- adoceanResponse.getCurrency()))
- .toList();
-
- return Result.withValues(bidderBids);
- }
-
- private static Bid createBid(Map auctionIds, AdoceanResponseAdUnit adoceanResponse) {
- final String adm = MEASUREMENT_CODE_TEMPLATE
- .formatted(adoceanResponse.getWinUrl(), adoceanResponse.getStatsUrl())
- + HttpUtil.decodeUrl(adoceanResponse.getCode());
- final String bidPrice = adoceanResponse.getPrice();
-
- return Bid.builder()
- .id(adoceanResponse.getId())
- .impid(auctionIds.get(adoceanResponse.getId()))
- .adm(adm)
- .price(NumberUtils.isParsable(bidPrice) ? new BigDecimal(bidPrice) : BigDecimal.ZERO)
- .w(NumberUtils.toInt(adoceanResponse.getWidth(), 0))
- .h(NumberUtils.toInt(adoceanResponse.getHeight(), 0))
- .crid(adoceanResponse.getCrid())
- .build();
- }
-
- private List getAdoceanResponseAdUnitList(String responseBody) {
- try {
- return mapper.mapper().readValue(
- responseBody,
- mapper.mapper().getTypeFactory().constructCollectionType(List.class, AdoceanResponseAdUnit.class));
- } catch (IOException e) {
- throw new PreBidException(e.getMessage());
- }
- }
-}
diff --git a/src/main/java/org/prebid/server/bidder/adocean/model/AdoceanResponseAdUnit.java b/src/main/java/org/prebid/server/bidder/adocean/model/AdoceanResponseAdUnit.java
deleted file mode 100644
index b2bd0142cee..00000000000
--- a/src/main/java/org/prebid/server/bidder/adocean/model/AdoceanResponseAdUnit.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package org.prebid.server.bidder.adocean.model;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.Builder;
-import lombok.Value;
-
-@Builder
-@Value
-public class AdoceanResponseAdUnit {
-
- String id;
-
- String crid;
-
- String currency;
-
- String price;
-
- String width;
-
- String height;
-
- String code;
-
- @JsonProperty("winurl")
- String winUrl;
-
- @JsonProperty("statsUrl")
- String statsUrl;
-
- String error;
-}
diff --git a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseAdsExt.java b/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseAdsExt.java
deleted file mode 100644
index d773617f068..00000000000
--- a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseAdsExt.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.prebid.server.bidder.adoppler.model;
-
-import lombok.Value;
-
-@Value(staticConstructor = "of")
-public class AdopplerResponseAdsExt {
-
- AdopplerResponseVideoAdsExt video;
-}
diff --git a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseExt.java b/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseExt.java
deleted file mode 100644
index 090ad5faaed..00000000000
--- a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseExt.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.prebid.server.bidder.adoppler.model;
-
-import lombok.Value;
-
-@Value(staticConstructor = "of")
-public class AdopplerResponseExt {
-
- AdopplerResponseAdsExt ads;
-}
diff --git a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseVideoAdsExt.java b/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseVideoAdsExt.java
deleted file mode 100644
index db64e1447e4..00000000000
--- a/src/main/java/org/prebid/server/bidder/adoppler/model/AdopplerResponseVideoAdsExt.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package org.prebid.server.bidder.adoppler.model;
-
-import lombok.Value;
-
-@Value(staticConstructor = "of")
-public class AdopplerResponseVideoAdsExt {
-
- Integer duration;
-}
diff --git a/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java b/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java
new file mode 100644
index 00000000000..ca7b04ee32f
--- /dev/null
+++ b/src/main/java/org/prebid/server/bidder/clydo/ClydoBidder.java
@@ -0,0 +1,182 @@
+package org.prebid.server.bidder.clydo;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Device;
+import com.iab.openrtb.request.Imp;
+import com.iab.openrtb.response.Bid;
+import com.iab.openrtb.response.BidResponse;
+import com.iab.openrtb.response.SeatBid;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.vertx.core.MultiMap;
+import org.prebid.server.bidder.Bidder;
+import org.prebid.server.bidder.model.BidderBid;
+import org.prebid.server.bidder.model.BidderCall;
+import org.prebid.server.bidder.model.BidderError;
+import org.prebid.server.bidder.model.HttpRequest;
+import org.prebid.server.bidder.model.Result;
+import org.prebid.server.exception.PreBidException;
+import org.prebid.server.json.DecodeException;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.proto.openrtb.ext.ExtPrebid;
+import org.prebid.server.proto.openrtb.ext.request.clydo.ExtImpClydo;
+import org.prebid.server.proto.openrtb.ext.response.BidType;
+import org.prebid.server.util.BidderUtil;
+import org.prebid.server.util.HttpUtil;
+import org.prebid.server.util.ObjectUtil;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+public class ClydoBidder implements Bidder {
+
+ private static final TypeReference> CLYDO_EXT_TYPE_REFERENCE =
+ new TypeReference<>() {
+ };
+
+ private static final String REGION_MACRO = "{{Region}}";
+ private static final String PARTNER_ID_MACRO = "{{PartnerId}}";
+ private static final String DEFAULT_REGION = "us";
+ private static final String X_OPENRTB_VERSION = "2.5";
+
+ private final String endpointUrl;
+ private final JacksonMapper mapper;
+
+ public ClydoBidder(String endpointUrl, JacksonMapper mapper) {
+ this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
+ this.mapper = Objects.requireNonNull(mapper);
+ }
+
+ @Override
+ public Result>> makeHttpRequests(BidRequest request) {
+ final List errors = new ArrayList<>();
+ final List> httpRequests = new ArrayList<>();
+
+ for (Imp imp : request.getImp()) {
+ try {
+ final ExtImpClydo extImpClydo = parseExtImp(imp);
+ final HttpRequest httpRequest = makeHttpRequest(request, imp, extImpClydo);
+ httpRequests.add(httpRequest);
+ } catch (PreBidException e) {
+ errors.add(BidderError.badInput(e.getMessage()));
+ }
+ }
+
+ if (httpRequests.isEmpty()) {
+ return Result.withError(BidderError.badInput("found no valid impressions"));
+ }
+
+ return Result.of(httpRequests, errors);
+ }
+
+ private ExtImpClydo parseExtImp(Imp imp) {
+ try {
+ return mapper.mapper().convertValue(imp.getExt(), CLYDO_EXT_TYPE_REFERENCE).getBidder();
+ } catch (IllegalArgumentException e) {
+ throw new PreBidException("Cannot deserialize ExtImpClydo: " + e.getMessage());
+ }
+ }
+
+ private HttpRequest makeHttpRequest(BidRequest request, Imp imp, ExtImpClydo extImpClydo) {
+ final BidRequest outgoingRequest = request.toBuilder().imp(List.of(imp)).build();
+
+ return BidderUtil.defaultRequest(
+ outgoingRequest,
+ constructHeaders(request),
+ resolveUrl(endpointUrl, extImpClydo), mapper);
+ }
+
+ private static MultiMap constructHeaders(BidRequest bidRequest) {
+ final Device device = bidRequest.getDevice();
+ final MultiMap headers = HttpUtil.headers();
+
+ headers.set(HttpUtil.X_OPENRTB_VERSION_HEADER, X_OPENRTB_VERSION);
+ headers.set(HttpUtil.ACCEPT_HEADER, HttpHeaderValues.APPLICATION_JSON);
+ headers.set(HttpUtil.CONTENT_TYPE_HEADER, HttpUtil.APPLICATION_JSON_CONTENT_TYPE);
+ HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER,
+ ObjectUtil.getIfNotNull(device, Device::getIpv6));
+ HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.X_FORWARDED_FOR_HEADER,
+ ObjectUtil.getIfNotNull(device, Device::getIp));
+ HttpUtil.addHeaderIfValueIsNotEmpty(headers, HttpUtil.USER_AGENT_HEADER,
+ ObjectUtil.getIfNotNull(device, Device::getUa));
+
+ return headers;
+ }
+
+ private static String resolveUrl(String endpoint, ExtImpClydo extImp) {
+ return endpoint
+ .replace(REGION_MACRO, getRegionInfo(extImp))
+ .replace(PARTNER_ID_MACRO, HttpUtil.encodeUrl(extImp.getPartnerId()));
+ }
+
+ private static String getRegionInfo(ExtImpClydo extImp) {
+ final String region = extImp.getRegion();
+
+ return switch (region) {
+ case "us", "usw", "eu", "apac" -> region;
+ case null, default -> DEFAULT_REGION;
+ };
+ }
+
+ @Override
+ public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) {
+ try {
+ final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
+ final List bidderBids = extractBids(bidRequest, bidResponse);
+ return Result.withValues(bidderBids);
+ } catch (DecodeException | PreBidException e) {
+ return Result.withError(BidderError.badServerResponse(e.getMessage()));
+ }
+ }
+
+ private List extractBids(BidRequest bidRequest, BidResponse bidResponse) {
+ if (bidResponse == null || bidResponse.getSeatbid() == null || bidResponse.getSeatbid().isEmpty()) {
+ return Collections.emptyList();
+ }
+
+ final Map impIdToBidTypeMap = buildImpIdToBidTypeMap(bidRequest);
+
+ return bidResponse.getSeatbid().stream()
+ .filter(Objects::nonNull)
+ .map(SeatBid::getBid)
+ .filter(Objects::nonNull)
+ .flatMap(List::stream)
+ .filter(Objects::nonNull)
+ .map(bid -> BidderBid.of(bid, resolveBidType(bid, impIdToBidTypeMap), bidResponse.getCur()))
+ .collect(Collectors.toList());
+ }
+
+ private static Map buildImpIdToBidTypeMap(BidRequest bidRequest) {
+ final Map impIdToBidTypeMap = new HashMap<>();
+ for (Imp imp : bidRequest.getImp()) {
+ final String impId = imp.getId();
+ final BidType bidType = determineBidType(imp);
+ impIdToBidTypeMap.put(impId, bidType);
+ }
+
+ return impIdToBidTypeMap;
+ }
+
+ private static BidType determineBidType(Imp imp) {
+ if (imp.getAudio() != null) {
+ return BidType.audio;
+ } else if (imp.getVideo() != null) {
+ return BidType.video;
+ } else if (imp.getXNative() != null) {
+ return BidType.xNative;
+ } else if (imp.getBanner() != null) {
+ return BidType.banner;
+ }
+
+ throw new PreBidException("Failed to get media type");
+ }
+
+ private static BidType resolveBidType(Bid bid, Map impIdToBidTypeMap) {
+ return impIdToBidTypeMap.getOrDefault(bid.getImpid(), BidType.banner);
+ }
+}
diff --git a/src/main/java/org/prebid/server/bidder/adoppler/AdopplerBidder.java b/src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java
similarity index 77%
rename from src/main/java/org/prebid/server/bidder/adoppler/AdopplerBidder.java
rename to src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java
index 761a2e21bc4..17cd2950b7a 100644
--- a/src/main/java/org/prebid/server/bidder/adoppler/AdopplerBidder.java
+++ b/src/main/java/org/prebid/server/bidder/elementaltv/ElementalTVBidder.java
@@ -1,4 +1,4 @@
-package org.prebid.server.bidder.adoppler;
+package org.prebid.server.bidder.elementaltv;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
@@ -12,9 +12,9 @@
import io.vertx.core.http.HttpMethod;
import org.apache.commons.lang3.StringUtils;
import org.prebid.server.bidder.Bidder;
-import org.prebid.server.bidder.adoppler.model.AdopplerResponseAdsExt;
-import org.prebid.server.bidder.adoppler.model.AdopplerResponseExt;
-import org.prebid.server.bidder.adoppler.model.AdopplerResponseVideoAdsExt;
+import org.prebid.server.bidder.elementaltv.model.ElementalTVResponseAdsExt;
+import org.prebid.server.bidder.elementaltv.model.ElementalTVResponseExt;
+import org.prebid.server.bidder.elementaltv.model.ElementalTVResponseVideoAdsExt;
import org.prebid.server.bidder.model.BidderBid;
import org.prebid.server.bidder.model.BidderCall;
import org.prebid.server.bidder.model.BidderError;
@@ -24,7 +24,7 @@
import org.prebid.server.json.DecodeException;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.proto.openrtb.ext.ExtPrebid;
-import org.prebid.server.proto.openrtb.ext.request.adoppler.ExtImpAdoppler;
+import org.prebid.server.proto.openrtb.ext.request.elementaltv.ExtImpElementalTV;
import org.prebid.server.proto.openrtb.ext.response.BidType;
import org.prebid.server.util.HttpUtil;
@@ -36,17 +36,16 @@
import java.util.Map;
import java.util.Objects;
-public class AdopplerBidder implements Bidder {
+public class ElementalTVBidder implements Bidder {
- private static final TypeReference> ADOPPLER_EXT_TYPE_REFERENCE =
+ private static final TypeReference> ELEMENTALTV_EXT_TYPE_REFERENCE =
new TypeReference<>() {
};
- private static final String DEFAULT_CLIENT = "app";
private final String endpointTemplate;
private final JacksonMapper mapper;
- public AdopplerBidder(String endpointTemplate, JacksonMapper mapper) {
+ public ElementalTVBidder(String endpointTemplate, JacksonMapper mapper) {
this.endpointTemplate = HttpUtil.validateUrl(Objects.requireNonNull(endpointTemplate));
this.mapper = Objects.requireNonNull(mapper);
}
@@ -58,7 +57,7 @@ public Result>> makeHttpRequests(BidRequest request
for (Imp imp : request.getImp()) {
try {
- final ExtImpAdoppler validExtImp = parseAndValidateImpExt(imp);
+ final ExtImpElementalTV validExtImp = parseAndValidateImpExt(imp);
final String updateRequestId = request.getId() + "-" + validExtImp.getAdunit();
final BidRequest updateRequest = request.toBuilder().id(updateRequestId).build();
final String url = resolveUrl(validExtImp);
@@ -71,29 +70,22 @@ public Result>> makeHttpRequests(BidRequest request
return Result.of(result, errors);
}
- private ExtImpAdoppler parseAndValidateImpExt(Imp imp) {
- final ExtImpAdoppler extImpAdoppler;
+ private ExtImpElementalTV parseAndValidateImpExt(Imp imp) {
+ final ExtImpElementalTV extImpElementalTV;
try {
- extImpAdoppler = mapper.mapper().convertValue(imp.getExt(), ADOPPLER_EXT_TYPE_REFERENCE).getBidder();
+ extImpElementalTV = mapper.mapper().convertValue(imp.getExt(), ELEMENTALTV_EXT_TYPE_REFERENCE).getBidder();
} catch (IllegalArgumentException e) {
throw new PreBidException(e.getMessage());
}
- if (StringUtils.isBlank(extImpAdoppler.getAdunit())) {
- throw new PreBidException("adunit parameter is required for adoppler bidder");
+ if (StringUtils.isBlank(extImpElementalTV.getAdunit())) {
+ throw new PreBidException("adunit parameter is required for elementaltv bidder");
}
- return extImpAdoppler;
+ return extImpElementalTV;
}
- private String resolveUrl(ExtImpAdoppler extImp) {
- final String client = extImp.getClient();
-
+ private String resolveUrl(ExtImpElementalTV extImp) {
try {
- final String accountIdMacro = StringUtils.isBlank(client)
- ? DEFAULT_CLIENT
- : HttpUtil.encodeUrl(client);
-
return endpointTemplate
- .replace("{{AccountID}}", accountIdMacro)
.replace("{{AdUnit}}", HttpUtil.encodeUrl(extImp.getAdunit()));
} catch (Exception e) {
throw new PreBidException(e.getMessage());
@@ -171,17 +163,17 @@ private BidderBid createBid(Bid bid, Map impTypes, String curre
private void validateVideoBidExt(Bid bid) {
final ObjectNode extNode = bid.getExt();
- final AdopplerResponseExt ext = extNode != null ? parseResponseExt(extNode) : null;
- final AdopplerResponseAdsExt adsExt = ext != null ? ext.getAds() : null;
- final AdopplerResponseVideoAdsExt videoAdsExt = adsExt != null ? adsExt.getVideo() : null;
+ final ElementalTVResponseExt ext = extNode != null ? parseResponseExt(extNode) : null;
+ final ElementalTVResponseAdsExt adsExt = ext != null ? ext.getAds() : null;
+ final ElementalTVResponseVideoAdsExt videoAdsExt = adsExt != null ? adsExt.getVideo() : null;
if (videoAdsExt == null) {
throw new PreBidException("$.seatbid.bid.ext.ads.video required");
}
}
- private AdopplerResponseExt parseResponseExt(ObjectNode ext) {
+ private ElementalTVResponseExt parseResponseExt(ObjectNode ext) {
try {
- return mapper.mapper().treeToValue(ext, AdopplerResponseExt.class);
+ return mapper.mapper().treeToValue(ext, ElementalTVResponseExt.class);
} catch (JsonProcessingException e) {
throw new PreBidException(e.getMessage(), e);
}
diff --git a/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseAdsExt.java b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseAdsExt.java
new file mode 100644
index 00000000000..1a23b70eaa9
--- /dev/null
+++ b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseAdsExt.java
@@ -0,0 +1,9 @@
+package org.prebid.server.bidder.elementaltv.model;
+
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ElementalTVResponseAdsExt {
+
+ ElementalTVResponseVideoAdsExt video;
+}
diff --git a/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseExt.java b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseExt.java
new file mode 100644
index 00000000000..935d32e01b6
--- /dev/null
+++ b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseExt.java
@@ -0,0 +1,9 @@
+package org.prebid.server.bidder.elementaltv.model;
+
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ElementalTVResponseExt {
+
+ ElementalTVResponseAdsExt ads;
+}
diff --git a/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseVideoAdsExt.java b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseVideoAdsExt.java
new file mode 100644
index 00000000000..76e3457b453
--- /dev/null
+++ b/src/main/java/org/prebid/server/bidder/elementaltv/model/ElementalTVResponseVideoAdsExt.java
@@ -0,0 +1,9 @@
+package org.prebid.server.bidder.elementaltv.model;
+
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ElementalTVResponseVideoAdsExt {
+
+ Integer duration;
+}
diff --git a/src/main/java/org/prebid/server/bidder/nexx360/Nexx360Bidder.java b/src/main/java/org/prebid/server/bidder/nexx360/Nexx360Bidder.java
index 180f545bf47..b4b643cc3eb 100644
--- a/src/main/java/org/prebid/server/bidder/nexx360/Nexx360Bidder.java
+++ b/src/main/java/org/prebid/server/bidder/nexx360/Nexx360Bidder.java
@@ -1,6 +1,7 @@
package org.prebid.server.bidder.nexx360;
import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Imp;
import com.iab.openrtb.response.Bid;
@@ -81,7 +82,9 @@ private ExtImpNexx360 parseImpExt(Imp imp) {
private Imp modifyImp(Imp imp) {
return imp.toBuilder()
- .ext(mapper.mapper().createObjectNode().set(BIDDER_NAME, imp.getExt().get("bidder")))
+ .ext(imp.getExt().deepCopy()
+ .without("bidder")
+ .set(BIDDER_NAME, imp.getExt().get("bidder")))
.build();
}
diff --git a/src/main/java/org/prebid/server/bidder/ogury/OguryBidder.java b/src/main/java/org/prebid/server/bidder/ogury/OguryBidder.java
index 3626f88b93b..4d14198e230 100644
--- a/src/main/java/org/prebid/server/bidder/ogury/OguryBidder.java
+++ b/src/main/java/org/prebid/server/bidder/ogury/OguryBidder.java
@@ -1,6 +1,7 @@
package org.prebid.server.bidder.ogury;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iab.openrtb.request.App;
import com.iab.openrtb.request.BidRequest;
import com.iab.openrtb.request.Device;
import com.iab.openrtb.request.Imp;
@@ -71,14 +72,10 @@ public Result>> makeHttpRequests(BidRequest bidRequ
}
if (CollectionUtils.isEmpty(impsWithOguryParams)) {
- if (bidRequest.getApp() != null) {
- errors.add(BidderError.badInput("Invalid request. assetKey/adUnitId required"));
- return Result.withErrors(errors);
- }
- // for "site" request we can serve ads with just publisher.id
+ // we can serve ads with just publisher.id
if (!hasPublisherId(bidRequest)) {
errors.add(BidderError.badInput(
- "Invalid request. assetKey/adUnitId or request.site.publisher.id required"));
+ "Invalid request. assetKey/adUnitId or request.site/app.publisher.id required"));
return Result.withErrors(errors);
}
}
@@ -145,12 +142,23 @@ private boolean hasOguryParams(Imp imp) {
}
private boolean hasPublisherId(BidRequest request) {
+ return hasSitePublisherId(request) || hasAppPublisherId(request);
+ }
+
+ private boolean hasSitePublisherId(BidRequest request) {
return Optional.ofNullable(request.getSite())
.map(Site::getPublisher)
.map(Publisher::getId)
.isPresent();
}
+ private boolean hasAppPublisherId(BidRequest request) {
+ return Optional.ofNullable(request.getApp())
+ .map(App::getPublisher)
+ .map(Publisher::getId)
+ .isPresent();
+ }
+
private MultiMap resolveHeaders(Device device) {
final MultiMap headers = HttpUtil.headers();
diff --git a/src/main/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidder.java b/src/main/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidder.java
index f2228778feb..8d45320cb9c 100644
--- a/src/main/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidder.java
+++ b/src/main/java/org/prebid/server/bidder/rtbhouse/RtbhouseBidder.java
@@ -94,6 +94,110 @@ public Result>> makeHttpRequests(BidRequest bidRequ
return Result.withValue(BidderUtil.defaultRequest(outgoingRequest, endpointUrl, mapper));
}
+ private ExtImpRtbhouse parseImpExt(Imp imp) {
+ try {
+ return mapper.mapper().convertValue(imp.getExt(), RTBHOUSE_EXT_TYPE_REFERENCE).getBidder();
+ } catch (IllegalArgumentException e) {
+ throw new PreBidException(e.getMessage());
+ }
+ }
+
+ private Price resolveBidFloor(Imp imp, ExtImpRtbhouse impExt, BidRequest bidRequest) {
+ final List brCur = bidRequest.getCur();
+ final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
+
+ final BigDecimal impExtBidFloor = impExt.getBidFloor();
+ final String impExtCurrency = impExtBidFloor != null && brCur != null && !brCur.isEmpty()
+ ? brCur.getFirst() : null;
+ final Price impExtBidFloorPrice = Price.of(impExtCurrency, impExtBidFloor);
+ final Price resolvedPrice = initialBidFloorPrice.getValue() == null
+ ? impExtBidFloorPrice : initialBidFloorPrice;
+
+ return BidderUtil.isValidPrice(resolvedPrice)
+ && !StringUtils.equalsIgnoreCase(resolvedPrice.getCurrency(), BIDDER_CURRENCY)
+ ? convertBidFloor(resolvedPrice, imp.getId(), bidRequest)
+ : resolvedPrice;
+ }
+
+ private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) {
+ final String bidFloorCur = bidFloorPrice.getCurrency();
+ try {
+ final BigDecimal convertedPrice = currencyConversionService
+ .convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, BIDDER_CURRENCY);
+
+ return Price.of(BIDDER_CURRENCY, convertedPrice);
+ } catch (PreBidException e) {
+ throw new PreBidException(String.format(
+ "Unable to convert provided bid floor currency from %s to %s for imp `%s`",
+ bidFloorCur, BIDDER_CURRENCY, impId));
+ }
+ }
+
+ private static Imp modifyImp(Imp imp, Price bidFloorPrice) {
+ return imp.toBuilder()
+ .tagid(extractTagId(imp))
+ .bidfloorcur(ObjectUtil.getIfNotNull(bidFloorPrice, Price::getCurrency))
+ .bidfloor(ObjectUtil.getIfNotNull(bidFloorPrice, Price::getValue))
+ .pmp(null)
+ .build();
+ }
+
+ private static String extractTagId(Imp imp) {
+ return Optional.ofNullable(imp.getTagid())
+ .filter(StringUtils::isNotBlank)
+ .or(() -> extractGpid(imp))
+ .or(() -> extractAdslot(imp))
+ .or(() -> extractPbadslot(imp))
+ .or(() -> Optional.ofNullable(imp.getId())
+ .filter(StringUtils::isNotBlank))
+ .orElse(null);
+ }
+
+ private static Optional extractGpid(Imp imp) {
+ return Optional.ofNullable(imp.getExt())
+ .map(ext -> ext.get("gpid"))
+ .map(JsonNode::textValue)
+ .filter(StringUtils::isNotBlank);
+ }
+
+ private static Optional extractAdslot(Imp imp) {
+ return Optional.ofNullable(imp.getExt())
+ .map(ext -> ext.get("data"))
+ .map(data -> data.get("adserver"))
+ .map(adserver -> adserver.get("adslot"))
+ .map(JsonNode::textValue)
+ .filter(StringUtils::isNotBlank);
+ }
+
+ private static Optional extractPbadslot(Imp imp) {
+ return Optional.ofNullable(imp.getExt())
+ .map(ext -> ext.get("data"))
+ .map(data -> data.get("pbadslot"))
+ .map(JsonNode::textValue)
+ .filter(StringUtils::isNotBlank);
+ }
+
+ private Site modifySite(Site site, String publisherId) {
+ final ObjectNode prebidNode = mapper.mapper().createObjectNode();
+ prebidNode.put("publisherId", publisherId);
+
+ final ExtPublisher extPublisher = ExtPublisher.empty();
+ extPublisher.addProperty("prebid", prebidNode);
+
+ final Publisher publisher = Optional.ofNullable(site)
+ .map(Site::getPublisher)
+ .map(Publisher::toBuilder)
+ .orElseGet(Publisher::builder)
+ .ext(extPublisher)
+ .build();
+
+ return Optional.ofNullable(site)
+ .map(Site::toBuilder)
+ .orElseGet(Site::builder)
+ .publisher(publisher)
+ .build();
+ }
+
@Override
public Result> makeBids(BidderCall httpCall, BidRequest bidRequest) {
try {
@@ -144,23 +248,6 @@ private BidderBid resolveBidderBid(Bid bid,
.build();
}
- private String resolveNativeAdm(String adm, List bidderErrors) {
- final JsonNode admNode;
- try {
- admNode = mapper.mapper().readTree(adm);
- } catch (JsonProcessingException e) {
- bidderErrors.add(BidderError.badServerResponse("Unable to parse native adm: %s".formatted(adm)));
- return null;
- }
-
- final JsonNode nativeNode = admNode.get("native");
- if (nativeNode != null) {
- return nativeNode.toString();
- }
-
- return adm;
- }
-
private static BidType getBidType(String impId, List imps) {
for (Imp imp : imps) {
if (imp.getId().equals(impId)) {
@@ -176,51 +263,21 @@ private static BidType getBidType(String impId, List imps) {
return BidType.banner;
}
- private ExtImpRtbhouse parseImpExt(Imp imp) {
+ private String resolveNativeAdm(String adm, List bidderErrors) {
+ final JsonNode admNode;
try {
- return mapper.mapper().convertValue(imp.getExt(), RTBHOUSE_EXT_TYPE_REFERENCE).getBidder();
- } catch (IllegalArgumentException e) {
- throw new PreBidException(e.getMessage());
+ admNode = mapper.mapper().readTree(adm);
+ } catch (JsonProcessingException e) {
+ bidderErrors.add(BidderError.badServerResponse("Unable to parse native adm: %s".formatted(adm)));
+ return null;
}
- }
- private static Imp modifyImp(Imp imp, Price bidFloorPrice) {
- return imp.toBuilder()
- .bidfloorcur(ObjectUtil.getIfNotNull(bidFloorPrice, Price::getCurrency))
- .bidfloor(ObjectUtil.getIfNotNull(bidFloorPrice, Price::getValue))
- .pmp(null)
- .build();
- }
-
- private Price resolveBidFloor(Imp imp, ExtImpRtbhouse impExt, BidRequest bidRequest) {
- final List brCur = bidRequest.getCur();
- final Price initialBidFloorPrice = Price.of(imp.getBidfloorcur(), imp.getBidfloor());
-
- final BigDecimal impExtBidFloor = impExt.getBidFloor();
- final String impExtCurrency = impExtBidFloor != null && brCur != null && !brCur.isEmpty()
- ? brCur.getFirst() : null;
- final Price impExtBidFloorPrice = Price.of(impExtCurrency, impExtBidFloor);
- final Price resolvedPrice = initialBidFloorPrice.getValue() == null
- ? impExtBidFloorPrice : initialBidFloorPrice;
-
- return BidderUtil.isValidPrice(resolvedPrice)
- && !StringUtils.equalsIgnoreCase(resolvedPrice.getCurrency(), BIDDER_CURRENCY)
- ? convertBidFloor(resolvedPrice, imp.getId(), bidRequest)
- : resolvedPrice;
- }
-
- private Price convertBidFloor(Price bidFloorPrice, String impId, BidRequest bidRequest) {
- final String bidFloorCur = bidFloorPrice.getCurrency();
- try {
- final BigDecimal convertedPrice = currencyConversionService
- .convertCurrency(bidFloorPrice.getValue(), bidRequest, bidFloorCur, BIDDER_CURRENCY);
-
- return Price.of(BIDDER_CURRENCY, convertedPrice);
- } catch (PreBidException e) {
- throw new PreBidException(String.format(
- "Unable to convert provided bid floor currency from %s to %s for imp `%s`",
- bidFloorCur, BIDDER_CURRENCY, impId));
+ final JsonNode nativeNode = admNode.get("native");
+ if (nativeNode != null) {
+ return nativeNode.toString();
}
+
+ return adm;
}
private static Bid resolveMacros(Bid bid) {
@@ -232,25 +289,4 @@ private static Bid resolveMacros(Bid bid) {
.adm(StringUtils.replace(bid.getAdm(), PRICE_MACRO, priceAsString))
.build();
}
-
- private Site modifySite(Site site, String publisherId) {
- final ObjectNode prebidNode = mapper.mapper().createObjectNode();
- prebidNode.put("publisherId", publisherId);
-
- final ExtPublisher extPublisher = ExtPublisher.empty();
- extPublisher.addProperty("prebid", prebidNode);
-
- final Publisher publisher = Optional.ofNullable(site)
- .map(Site::getPublisher)
- .map(Publisher::toBuilder)
- .orElseGet(Publisher::builder)
- .ext(extPublisher)
- .build();
-
- return Optional.ofNullable(site)
- .map(Site::toBuilder)
- .orElseGet(Site::builder)
- .publisher(publisher)
- .build();
- }
}
diff --git a/src/main/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidder.java b/src/main/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidder.java
index cff5866d027..fea452f059d 100644
--- a/src/main/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidder.java
+++ b/src/main/java/org/prebid/server/bidder/stroeercore/StroeerCoreBidder.java
@@ -106,10 +106,6 @@ private BidderBid toBidderBid(StroeerCoreBid stroeercoreBid, List e
return null;
}
- final ObjectNode bidExt = stroeercoreBid.getDsa() != null
- ? mapper.mapper().createObjectNode().set("dsa", stroeercoreBid.getDsa())
- : null;
-
return BidderBid.of(
Bid.builder()
.id(stroeercoreBid.getId())
@@ -121,12 +117,24 @@ private BidderBid toBidderBid(StroeerCoreBid stroeercoreBid, List e
.crid(stroeercoreBid.getCreativeId())
.adomain(stroeercoreBid.getAdomain())
.mtype(bidType.ordinal() + 1)
- .ext(bidExt)
+ .ext(getBidExt(stroeercoreBid))
.build(),
bidType,
BIDDER_CURRENCY);
}
+ private ObjectNode getBidExt(StroeerCoreBid stroeercoreBid) {
+ final ObjectNode dsa = stroeercoreBid.getDsa();
+ ObjectNode ext = stroeercoreBid.getExt();
+ if (dsa == null) {
+ return ext;
+ }
+ if (ext == null) {
+ ext = mapper.mapper().createObjectNode();
+ }
+ return ext.set("dsa", dsa);
+ }
+
private static BidType getBidType(String mtype) {
return switch (mtype) {
case "banner" -> BidType.banner;
diff --git a/src/main/java/org/prebid/server/bidder/stroeercore/model/StroeerCoreBid.java b/src/main/java/org/prebid/server/bidder/stroeercore/model/StroeerCoreBid.java
index 2c665a27ce5..980db6d1891 100644
--- a/src/main/java/org/prebid/server/bidder/stroeercore/model/StroeerCoreBid.java
+++ b/src/main/java/org/prebid/server/bidder/stroeercore/model/StroeerCoreBid.java
@@ -29,9 +29,15 @@ public class StroeerCoreBid {
@JsonProperty("crid")
String creativeId;
+ /**
+ * @deprecated The dsa will move to the bid's ext.
+ */
+ @Deprecated(forRemoval = true)
ObjectNode dsa;
String mtype;
List adomain;
+
+ ObjectNode ext;
}
diff --git a/src/main/java/org/prebid/server/bidder/teal/TealBidder.java b/src/main/java/org/prebid/server/bidder/teal/TealBidder.java
new file mode 100644
index 00000000000..c1869421dd8
--- /dev/null
+++ b/src/main/java/org/prebid/server/bidder/teal/TealBidder.java
@@ -0,0 +1,200 @@
+package org.prebid.server.bidder.teal;
+
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.iab.openrtb.request.App;
+import com.iab.openrtb.request.BidRequest;
+import com.iab.openrtb.request.Imp;
+import com.iab.openrtb.request.Publisher;
+import com.iab.openrtb.request.Site;
+import com.iab.openrtb.response.BidResponse;
+import com.iab.openrtb.response.SeatBid;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.prebid.server.bidder.Bidder;
+import org.prebid.server.bidder.model.BidderBid;
+import org.prebid.server.bidder.model.BidderCall;
+import org.prebid.server.bidder.model.BidderError;
+import org.prebid.server.bidder.model.HttpRequest;
+import org.prebid.server.bidder.model.Result;
+import org.prebid.server.exception.PreBidException;
+import org.prebid.server.json.DecodeException;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.proto.openrtb.ext.ExtPrebid;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequest;
+import org.prebid.server.proto.openrtb.ext.request.teal.ExtImpTeal;
+import org.prebid.server.proto.openrtb.ext.response.BidType;
+import org.prebid.server.util.BidderUtil;
+import org.prebid.server.util.HttpUtil;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+import java.util.Optional;
+
+public class TealBidder implements Bidder {
+
+ private static final TypeReference> TYPE_REFERENCE = new TypeReference<>() {
+ };
+
+ private final String endpointUrl;
+ private final JacksonMapper mapper;
+
+ public TealBidder(String endpointUrl, JacksonMapper mapper) {
+ this.endpointUrl = HttpUtil.validateUrl(Objects.requireNonNull(endpointUrl));
+ this.mapper = Objects.requireNonNull(mapper);
+ }
+
+ @Override
+ public Result>> makeHttpRequests(BidRequest request) {
+ final List modifiedImps = new ArrayList<>();
+ final List errors = new ArrayList<>();
+ String account = null;
+
+ for (Imp imp : request.getImp()) {
+ final ExtImpTeal extImpTeal;
+ try {
+ extImpTeal = parseImpExt(imp);
+ validateImpExt(extImpTeal);
+ } catch (PreBidException e) {
+ errors.add(BidderError.badInput(e.getMessage()));
+ continue;
+ }
+
+ account = account == null ? extImpTeal.getAccount() : account;
+ modifiedImps.add(modifyImp(imp, extImpTeal.getPlacement()));
+ }
+
+ if (modifiedImps.isEmpty()) {
+ return Result.withErrors(errors);
+ }
+
+ final BidRequest modifiedRequest = modifyBidRequest(request, account, modifiedImps);
+ return Result.of(
+ Collections.singletonList(BidderUtil.defaultRequest(modifiedRequest, endpointUrl, mapper)),
+ errors);
+ }
+
+ private ExtImpTeal parseImpExt(Imp imp) {
+ try {
+ return mapper.mapper().convertValue(imp.getExt(), TYPE_REFERENCE).getBidder();
+ } catch (IllegalArgumentException e) {
+ throw new PreBidException("Error parsing imp.ext for impression " + imp.getId());
+ }
+ }
+
+ private static void validateImpExt(ExtImpTeal extImpTeal) {
+ if (StringUtils.isBlank(extImpTeal.getAccount())) {
+ throw new PreBidException("account parameter failed validation");
+ }
+
+ final String placement = extImpTeal.getPlacement();
+ if (placement != null && StringUtils.isBlank(placement)) {
+ throw new PreBidException("placement parameter failed validation");
+ }
+ }
+
+ private static Imp modifyImp(Imp imp, String placement) {
+ if (placement == null) {
+ return imp;
+ }
+
+ final ObjectNode modifiedExt = imp.getExt().deepCopy();
+ getOrCreate(getOrCreate(modifiedExt, "prebid"), "storedrequest")
+ .put("id", placement);
+
+ return imp.toBuilder().ext(modifiedExt).build();
+ }
+
+ private static ObjectNode getOrCreate(ObjectNode parent, String field) {
+ final JsonNode child = parent.get(field);
+ return child != null && child.isObject()
+ ? (ObjectNode) child
+ : parent.putObject(field);
+ }
+
+ private BidRequest modifyBidRequest(BidRequest request, String account, List modifiedImps) {
+ final ExtRequest ext = ObjectUtils.defaultIfNull(request.getExt(), ExtRequest.empty());
+ ext.addProperty("bids", mapper.mapper().createObjectNode().put("pbs", 1));
+
+ return request.toBuilder()
+ .site(modifySite(request.getSite(), account))
+ .app(modifyApp(request.getApp(), account))
+ .imp(modifiedImps)
+ .ext(ext)
+ .build();
+ }
+
+ private static Site modifySite(Site site, String account) {
+ return site != null
+ ? site.toBuilder()
+ .publisher(modifyPublisher(site.getPublisher(), account))
+ .build()
+ : null;
+ }
+
+ private static App modifyApp(App app, String account) {
+ return app != null
+ ? app.toBuilder()
+ .publisher(modifyPublisher(app.getPublisher(), account))
+ .build()
+ : null;
+ }
+
+ private static Publisher modifyPublisher(Publisher publisher, String account) {
+ return Optional.ofNullable(publisher)
+ .map(Publisher::toBuilder)
+ .orElseGet(Publisher::builder)
+ .id(account)
+ .build();
+ }
+
+ @Override
+ public final Result> makeBids(BidderCall httpCall, BidRequest bidRequest) {
+ try {
+ final BidResponse bidResponse = mapper.decodeValue(httpCall.getResponse().getBody(), BidResponse.class);
+ return Result.withValues(extractBids(httpCall.getRequest().getPayload(), bidResponse));
+ } catch (DecodeException e) {
+ return Result.withError(BidderError.badServerResponse(e.getMessage()));
+ }
+ }
+
+ private static List extractBids(BidRequest bidRequest, BidResponse bidResponse) {
+ if (bidResponse == null || CollectionUtils.isEmpty(bidResponse.getSeatbid())) {
+ return Collections.emptyList();
+ }
+ return bidsFromResponse(bidRequest, bidResponse);
+ }
+
+ private static List bidsFromResponse(BidRequest bidRequest, BidResponse bidResponse) {
+ return bidResponse.getSeatbid().stream()
+ .filter(Objects::nonNull)
+ .map(SeatBid::getBid)
+ .filter(Objects::nonNull)
+ .flatMap(Collection::stream)
+ .filter(Objects::nonNull)
+ .map(bid -> BidderBid.of(bid, getBidType(bid.getImpid(), bidRequest.getImp()), bidResponse.getCur()))
+ .toList();
+ }
+
+ private static BidType getBidType(String impId, List imps) {
+ for (Imp imp : imps) {
+ if (imp.getId().equals(impId)) {
+ if (imp.getBanner() != null) {
+ return BidType.banner;
+ } else if (imp.getVideo() != null) {
+ return BidType.video;
+ } else if (imp.getAudio() != null) {
+ return BidType.audio;
+ } else if (imp.getXNative() != null) {
+ return BidType.xNative;
+ }
+ }
+ }
+ return BidType.banner;
+ }
+}
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 82b2e6007a1..2373c40e886 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
@@ -1,24 +1,43 @@
package org.prebid.server.proto.openrtb.ext.request;
import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Builder;
import lombok.Value;
import java.util.List;
-/**
- * Defines the contract for bidrequest.ext.prebid.data.eidPermissions
- */
-@Value(staticConstructor = "of")
+@Value
+@Builder
public class ExtRequestPrebidDataEidPermissions {
+ /**
+ * Defines the contract for bidrequest.ext.prebid.data.eidPermissions.inserter
+ */
+ String inserter;
+
/**
* Defines the contract for bidrequest.ext.prebid.data.eidPermissions.source
*/
String source;
+ /**
+ * Defines the contract for bidrequest.ext.prebid.data.eidPermissions.matcher
+ */
+ String matcher;
+
+ /**
+ * Defines the contract for bidrequest.ext.prebid.data.eidPermissions.mm
+ */
+ Integer mm;
+
/**
* Defines the contract for bidrequest.ext.prebid.data.eidPermissions.bidders
*/
@JsonFormat(without = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
List bidders;
+
+ @Deprecated
+ public static ExtRequestPrebidDataEidPermissions of(String source, List bidders) {
+ return new ExtRequestPrebidDataEidPermissions(null, source, null, null, bidders);
+ }
}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adocean/ExtImpAdocean.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adocean/ExtImpAdocean.java
deleted file mode 100644
index aa364d222ae..00000000000
--- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adocean/ExtImpAdocean.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package org.prebid.server.proto.openrtb.ext.request.adocean;
-
-import com.fasterxml.jackson.annotation.JsonProperty;
-import lombok.Value;
-
-/**
- * Defines the contract for bidrequest.imp[i].ext.adocean
- */
-@Value(staticConstructor = "of")
-public class ExtImpAdocean {
-
- @JsonProperty("emitterPrefix")
- String emitterPrefix;
-
- @JsonProperty("masterId")
- String masterId;
-
- @JsonProperty("slaveId")
- String slaveId;
-}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adoppler/ExtImpAdoppler.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/adoppler/ExtImpAdoppler.java
deleted file mode 100644
index a727c59b34b..00000000000
--- a/src/main/java/org/prebid/server/proto/openrtb/ext/request/adoppler/ExtImpAdoppler.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package org.prebid.server.proto.openrtb.ext.request.adoppler;
-
-import lombok.Value;
-
-@Value(staticConstructor = "of")
-public class ExtImpAdoppler {
-
- String adunit;
- String client;
-}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/clydo/ExtImpClydo.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/clydo/ExtImpClydo.java
new file mode 100644
index 00000000000..68338055de4
--- /dev/null
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/clydo/ExtImpClydo.java
@@ -0,0 +1,13 @@
+package org.prebid.server.proto.openrtb.ext.request.clydo;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ExtImpClydo {
+
+ @JsonProperty("partnerId")
+ String partnerId;
+
+ String region;
+}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/elementaltv/ExtImpElementalTV.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/elementaltv/ExtImpElementalTV.java
new file mode 100644
index 00000000000..bdd594996b4
--- /dev/null
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/elementaltv/ExtImpElementalTV.java
@@ -0,0 +1,9 @@
+package org.prebid.server.proto.openrtb.ext.request.elementaltv;
+
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ExtImpElementalTV {
+
+ String adunit;
+}
diff --git a/src/main/java/org/prebid/server/proto/openrtb/ext/request/teal/ExtImpTeal.java b/src/main/java/org/prebid/server/proto/openrtb/ext/request/teal/ExtImpTeal.java
new file mode 100644
index 00000000000..e52ec990e84
--- /dev/null
+++ b/src/main/java/org/prebid/server/proto/openrtb/ext/request/teal/ExtImpTeal.java
@@ -0,0 +1,11 @@
+package org.prebid.server.proto.openrtb.ext.request.teal;
+
+import lombok.Value;
+
+@Value(staticConstructor = "of")
+public class ExtImpTeal {
+
+ String account;
+
+ String placement;
+}
diff --git a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java
index 228d4702435..64a8dc7614a 100644
--- a/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java
+++ b/src/main/java/org/prebid/server/spring/config/ServiceConfiguration.java
@@ -33,6 +33,11 @@
import org.prebid.server.auction.VideoResponseFactory;
import org.prebid.server.auction.VideoStoredRequestProcessor;
import org.prebid.server.auction.WinningBidComparatorFactory;
+import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestCleaner;
+import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestCurrencyBlocker;
+import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestMediaFilter;
+import org.prebid.server.auction.bidderrequestpostprocessor.BidderRequestPreferredMediaProcessor;
+import org.prebid.server.auction.bidderrequestpostprocessor.CompositeBidderRequestPostProcessor;
import org.prebid.server.auction.categorymapping.BasicCategoryMappingService;
import org.prebid.server.auction.categorymapping.CategoryMappingService;
import org.prebid.server.auction.categorymapping.NoOpCategoryMappingService;
@@ -47,10 +52,6 @@
import org.prebid.server.auction.gpp.processor.GppContextProcessor;
import org.prebid.server.auction.gpp.processor.tcfeuv2.TcfEuV2ContextProcessor;
import org.prebid.server.auction.gpp.processor.uspv1.UspV1ContextProcessor;
-import org.prebid.server.auction.mediatypeprocessor.BidderMediaTypeProcessor;
-import org.prebid.server.auction.mediatypeprocessor.CompositeMediaTypeProcessor;
-import org.prebid.server.auction.mediatypeprocessor.MediaTypeProcessor;
-import org.prebid.server.auction.mediatypeprocessor.MultiFormatMediaTypeProcessor;
import org.prebid.server.auction.privacy.contextfactory.AmpPrivacyContextFactory;
import org.prebid.server.auction.privacy.contextfactory.AuctionPrivacyContextFactory;
import org.prebid.server.auction.privacy.contextfactory.CookieSyncPrivacyContextFactory;
@@ -145,6 +146,7 @@
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -791,20 +793,41 @@ BidderCatalog bidderCatalog(List bidderDeps) {
return new BidderCatalog(bidderDeps);
}
+ @Bean
+ BidderRequestCurrencyBlocker bidderRequestCurrencyBlocker(BidderCatalog bidderCatalog) {
+ return new BidderRequestCurrencyBlocker(bidderCatalog);
+ }
+
@Bean
@ConditionalOnProperty(prefix = "auction.filter-imp-media-type", name = "enabled", havingValue = "true")
- MediaTypeProcessor bidderMediaTypeProcessor(BidderCatalog bidderCatalog) {
- return new BidderMediaTypeProcessor(bidderCatalog);
+ BidderRequestMediaFilter bidderRequestMediaFilter(BidderCatalog bidderCatalog) {
+ return new BidderRequestMediaFilter(bidderCatalog);
}
@Bean
- MediaTypeProcessor multiFormatMediaTypeProcessor(BidderCatalog bidderCatalog) {
- return new MultiFormatMediaTypeProcessor(bidderCatalog);
+ BidderRequestPreferredMediaProcessor bidderRequestPreferredMediaProcessor(BidderCatalog bidderCatalog) {
+ return new BidderRequestPreferredMediaProcessor(bidderCatalog);
}
@Bean
- CompositeMediaTypeProcessor compositeMediaTypeProcessor(List mediaTypeProcessors) {
- return new CompositeMediaTypeProcessor(mediaTypeProcessors);
+ BidderRequestCleaner bidderRequestCleaner() {
+ return new BidderRequestCleaner();
+ }
+
+ @Bean
+ CompositeBidderRequestPostProcessor compositeBidderRequestPostProcessor(
+ BidderRequestCurrencyBlocker bidderRequestCurrencyBlocker,
+ @Autowired(required = false) BidderRequestMediaFilter bidderRequestMediaFilter,
+ BidderRequestPreferredMediaProcessor bidderRequestPreferredMediaProcessor,
+ BidderRequestCleaner bidderRequestCleaner) {
+
+ return new CompositeBidderRequestPostProcessor(Stream.of(
+ bidderRequestCurrencyBlocker,
+ bidderRequestMediaFilter,
+ bidderRequestPreferredMediaProcessor,
+ bidderRequestCleaner)
+ .filter(Objects::nonNull)
+ .toList());
}
@Bean
@@ -916,7 +939,7 @@ ExchangeService exchangeService(
ImpAdjuster impAdjuster,
SupplyChainResolver supplyChainResolver,
DebugResolver debugResolver,
- CompositeMediaTypeProcessor mediaTypeProcessor,
+ CompositeBidderRequestPostProcessor bidderRequestPostProcessor,
UidUpdater uidUpdater,
TimeoutResolver timeoutResolver,
TimeoutFactory timeoutFactory,
@@ -944,7 +967,7 @@ ExchangeService exchangeService(
impAdjuster,
supplyChainResolver,
debugResolver,
- mediaTypeProcessor,
+ bidderRequestPostProcessor,
uidUpdater,
timeoutResolver,
timeoutFactory,
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/AdoceanConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ClydoConfiguration.java
similarity index 59%
rename from src/main/java/org/prebid/server/spring/config/bidder/AdoceanConfiguration.java
rename to src/main/java/org/prebid/server/spring/config/bidder/ClydoConfiguration.java
index d16984e3c5e..98cedfc8d08 100644
--- a/src/main/java/org/prebid/server/spring/config/bidder/AdoceanConfiguration.java
+++ b/src/main/java/org/prebid/server/spring/config/bidder/ClydoConfiguration.java
@@ -1,7 +1,7 @@
package org.prebid.server.spring.config.bidder;
import org.prebid.server.bidder.BidderDeps;
-import org.prebid.server.bidder.adocean.AdoceanBidder;
+import org.prebid.server.bidder.clydo.ClydoBidder;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
@@ -16,26 +16,26 @@
import jakarta.validation.constraints.NotBlank;
@Configuration
-@PropertySource(value = "classpath:/bidder-config/adocean.yaml", factory = YamlPropertySourceFactory.class)
-public class AdoceanConfiguration {
+@PropertySource(value = "classpath:/bidder-config/clydo.yaml", factory = YamlPropertySourceFactory.class)
+public class ClydoConfiguration {
- private static final String BIDDER_NAME = "adocean";
+ private static final String BIDDER_NAME = "clydo";
- @Bean("adoceanConfigurationProperties")
- @ConfigurationProperties("adapters.adocean")
+ @Bean("clydoConfigurationProperties")
+ @ConfigurationProperties("adapters.clydo")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}
@Bean
- BidderDeps adoceanBidderDeps(BidderConfigurationProperties adoceanConfigurationProperties,
- @NotBlank @Value("${external-url}") String externalUrl,
- JacksonMapper mapper) {
+ BidderDeps clydoBidderDeps(BidderConfigurationProperties clydoConfigurationProperties,
+ @NotBlank @Value("${external-url}") String externalUrl,
+ JacksonMapper mapper) {
return BidderDepsAssembler.forBidder(BIDDER_NAME)
- .withConfig(adoceanConfigurationProperties)
+ .withConfig(clydoConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
- .bidderCreator(config -> new AdoceanBidder(config.getEndpoint(), mapper))
+ .bidderCreator(config -> new ClydoBidder(config.getEndpoint(), mapper))
.assemble();
}
}
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/ElementalTVConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/ElementalTVConfiguration.java
new file mode 100644
index 00000000000..8cece283dd5
--- /dev/null
+++ b/src/main/java/org/prebid/server/spring/config/bidder/ElementalTVConfiguration.java
@@ -0,0 +1,41 @@
+package org.prebid.server.spring.config.bidder;
+
+import org.prebid.server.bidder.BidderDeps;
+import org.prebid.server.bidder.elementaltv.ElementalTVBidder;
+import org.prebid.server.json.JacksonMapper;
+import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
+import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
+import org.prebid.server.spring.config.bidder.util.UsersyncerCreator;
+import org.prebid.server.spring.env.YamlPropertySourceFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.PropertySource;
+
+import jakarta.validation.constraints.NotBlank;
+
+@Configuration
+@PropertySource(value = "classpath:/bidder-config/elementaltv.yaml", factory = YamlPropertySourceFactory.class)
+public class ElementalTVConfiguration {
+
+ private static final String BIDDER_NAME = "elementaltv";
+
+ @Bean("elementalTVConfigurationProperties")
+ @ConfigurationProperties("adapters.elementaltv")
+ BidderConfigurationProperties configurationProperties() {
+ return new BidderConfigurationProperties();
+ }
+
+ @Bean
+ BidderDeps elementaltvBidderDeps(BidderConfigurationProperties elementalTVConfigurationProperties,
+ @NotBlank @Value("${external-url}") String externalUrl,
+ JacksonMapper mapper) {
+
+ return BidderDepsAssembler.forBidder(BIDDER_NAME)
+ .withConfig(elementalTVConfigurationProperties)
+ .usersyncerCreator(UsersyncerCreator.create(externalUrl))
+ .bidderCreator(config -> new ElementalTVBidder(config.getEndpoint(), mapper))
+ .assemble();
+ }
+}
diff --git a/src/main/java/org/prebid/server/spring/config/bidder/AdopplerConfiguration.java b/src/main/java/org/prebid/server/spring/config/bidder/TealConfiguration.java
similarity index 59%
rename from src/main/java/org/prebid/server/spring/config/bidder/AdopplerConfiguration.java
rename to src/main/java/org/prebid/server/spring/config/bidder/TealConfiguration.java
index 89163bceeab..cbbff678a7e 100644
--- a/src/main/java/org/prebid/server/spring/config/bidder/AdopplerConfiguration.java
+++ b/src/main/java/org/prebid/server/spring/config/bidder/TealConfiguration.java
@@ -1,7 +1,7 @@
package org.prebid.server.spring.config.bidder;
import org.prebid.server.bidder.BidderDeps;
-import org.prebid.server.bidder.adoppler.AdopplerBidder;
+import org.prebid.server.bidder.teal.TealBidder;
import org.prebid.server.json.JacksonMapper;
import org.prebid.server.spring.config.bidder.model.BidderConfigurationProperties;
import org.prebid.server.spring.config.bidder.util.BidderDepsAssembler;
@@ -16,26 +16,27 @@
import jakarta.validation.constraints.NotBlank;
@Configuration
-@PropertySource(value = "classpath:/bidder-config/adoppler.yaml", factory = YamlPropertySourceFactory.class)
-public class AdopplerConfiguration {
+@PropertySource(value = "classpath:/bidder-config/teal.yaml", factory = YamlPropertySourceFactory.class)
+public class TealConfiguration {
- private static final String BIDDER_NAME = "adoppler";
+ private static final String BIDDER_NAME = "teal";
- @Bean("adopplerConfigurationProperties")
- @ConfigurationProperties("adapters.adoppler")
+ @Bean("tealConfigurationProperties")
+ @ConfigurationProperties("adapters.teal")
BidderConfigurationProperties configurationProperties() {
return new BidderConfigurationProperties();
}
@Bean
- BidderDeps adopplerBidderDeps(BidderConfigurationProperties adopplerConfigurationProperties,
- @NotBlank @Value("${external-url}") String externalUrl,
- JacksonMapper mapper) {
+ BidderDeps tealBidderDeps(BidderConfigurationProperties tealConfigurationProperties,
+ @NotBlank @Value("${external-url}") String externalUrl,
+ JacksonMapper mapper) {
return BidderDepsAssembler.forBidder(BIDDER_NAME)
- .withConfig(adopplerConfigurationProperties)
+ .withConfig(tealConfigurationProperties)
.usersyncerCreator(UsersyncerCreator.create(externalUrl))
- .bidderCreator(config -> new AdopplerBidder(config.getEndpoint(), mapper))
+ .bidderCreator(config -> new TealBidder(config.getEndpoint(), mapper))
.assemble();
}
+
}
diff --git a/src/main/java/org/prebid/server/validation/RequestValidator.java b/src/main/java/org/prebid/server/validation/RequestValidator.java
index d4b9fb06726..958c59d3a5a 100644
--- a/src/main/java/org/prebid/server/validation/RequestValidator.java
+++ b/src/main/java/org/prebid/server/validation/RequestValidator.java
@@ -376,7 +376,7 @@ private void validateEidPermissions(List eid
boolean isDebugEnabled,
List warnings) throws ValidationException {
- if (eidPermissions == null) {
+ if (CollectionUtils.isEmpty(eidPermissions)) {
return;
}
@@ -385,14 +385,24 @@ private void validateEidPermissions(List eid
throw new ValidationException("request.ext.prebid.data.eidpermissions[i] can't be null");
}
- validateEidPermissionSource(eidPermission.getSource());
+ validateEidPermissionCriteria(
+ eidPermission.getInserter(),
+ eidPermission.getSource(),
+ eidPermission.getMatcher(),
+ eidPermission.getMm());
+
validateEidPermissionBidders(eidPermission.getBidders(), aliases, isDebugEnabled, warnings);
}
}
- private void validateEidPermissionSource(String source) throws ValidationException {
- if (StringUtils.isEmpty(source)) {
- throw new ValidationException("Missing required value request.ext.prebid.data.eidPermissions[].source");
+ private void validateEidPermissionCriteria(String inserter,
+ String source,
+ String matcher,
+ Integer mm) throws ValidationException {
+
+ if (StringUtils.isAllEmpty(inserter, source, matcher) && mm == null) {
+ throw new ValidationException("Missing required parameter(s) in request.ext.prebid.data.eidPermissions[]. "
+ + "Either one or a combination of inserter, source, matcher, or mm should be defined.");
}
}
diff --git a/src/main/resources/bidder-config/adocean.yaml b/src/main/resources/bidder-config/adocean.yaml
deleted file mode 100644
index f659947c985..00000000000
--- a/src/main/resources/bidder-config/adocean.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-adapters:
- adocean:
- endpoint: https://{{Host}}.adocean.pl
- meta-info:
- maintainer-email: aoteam@gemius.com
- app-media-types:
- - banner
- site-media-types:
- - banner
- supported-vendors:
- vendor-id: 328
diff --git a/src/main/resources/bidder-config/adoppler.yaml b/src/main/resources/bidder-config/adoppler.yaml
deleted file mode 100644
index 1f66b131d83..00000000000
--- a/src/main/resources/bidder-config/adoppler.yaml
+++ /dev/null
@@ -1,13 +0,0 @@
-adapters:
- adoppler:
- endpoint: http://{{AccountID}}.trustedmarketplace.io/ads/processHeaderBid/{{AdUnit}}
- meta-info:
- maintainer-email: info@adoppler.com
- app-media-types:
- - banner
- - video
- site-media-types:
- - banner
- - video
- supported-vendors:
- vendor-id: 0
diff --git a/src/main/resources/bidder-config/adverxo.yaml b/src/main/resources/bidder-config/adverxo.yaml
index d2fd18f505b..b363a70bc08 100644
--- a/src/main/resources/bidder-config/adverxo.yaml
+++ b/src/main/resources/bidder-config/adverxo.yaml
@@ -31,20 +31,6 @@ adapters:
url: https://taetee.com/usync?type=image&gdpr={{gdpr}}&consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
uid-macro: '$UID'
support-cors: false
- mobupps:
- enabled: false
- endpoint: https://mobupps.pbsadverxo.com/auction?id={{adUnitId}}&auth={{auth}}
- usersync:
- enabled: false
- cookie-family-name: mobupps
- iframe:
- url: https://mobupps.pbsadverxo.com/usync?type=iframe&gdpr={{gdpr}}&consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
- uid-macro: '$UID'
- support-cors: false
- redirect:
- url: https://mobupps.pbsadverxo.com/usync?type=image&gdpr={{gdpr}}&consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}
- uid-macro: '$UID'
- support-cors: false
meta-info:
maintainer-email: developer@adverxo.com
app-media-types:
diff --git a/src/main/resources/bidder-config/aso.yaml b/src/main/resources/bidder-config/aso.yaml
index fa6a6381741..5f3bdad4829 100644
--- a/src/main/resources/bidder-config/aso.yaml
+++ b/src/main/resources/bidder-config/aso.yaml
@@ -7,16 +7,34 @@ adapters:
endpoint: https://srv.datacygnal.io/pbs/bidder?zid={{ZoneID}}
meta-info:
maintainer-email: contact@bcm.ltd
+ usersync:
+ cookie-family-name: bcmint
+ redirect:
+ url: https://track.datacygnal.io/sync/v2?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}}
+ support-cors: false
+ uid-macro: '{uid}'
bidagency:
enabled: false
endpoint: https://srv.bidgx.com/pbs/bidder?zid={{ZoneID}}
meta-info:
maintainer-email: aso@bidgency.com
+ usersync:
+ cookie-family-name: bidagency
+ redirect:
+ url: https://track.bidgx.com/sync/v2?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}}
+ support-cors: false
+ uid-macro: '{uid}'
kuantyx:
enabled: false
endpoint: https://srv.kntxy.com/pbs/bidder?zid={{ZoneID}}
meta-info:
maintainer-email: ssp@kuantyx.com
+ usersync:
+ cookie-family-name: kuantyx
+ redirect:
+ url: https://track.kntxy.com/sync/v2?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}}
+ support-cors: false
+ uid-macro: '{uid}'
meta-info:
maintainer-email: support@adsrv.org
app-media-types:
@@ -28,3 +46,9 @@ adapters:
- video
- native
vendor-id: 0
+ usersync:
+ cookie-family-name: aso
+ redirect:
+ url: https://track.aso1.net/sync/v2?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&usp={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}}
+ support-cors: false
+ uid-macro: '{uid}'
diff --git a/src/main/resources/bidder-config/clydo.yaml b/src/main/resources/bidder-config/clydo.yaml
new file mode 100644
index 00000000000..aa90e3a4f07
--- /dev/null
+++ b/src/main/resources/bidder-config/clydo.yaml
@@ -0,0 +1,21 @@
+adapters:
+ clydo:
+ endpoint: http://region={{Region}}.clydo.io/partnerId={{PartnerId}}
+ modifying-vast-xml-allowed: true
+ endpoint-compression: gzip
+ geoscope:
+ - global
+ meta-info:
+ maintainer-email: cto@clydo.io
+ app-media-types:
+ - banner
+ - video
+ - audio
+ - native
+ site-media-types:
+ - banner
+ - video
+ - audio
+ - native
+ supported-vendors:
+ vendor-id: 0
diff --git a/src/main/resources/bidder-config/elementaltv.yaml b/src/main/resources/bidder-config/elementaltv.yaml
new file mode 100644
index 00000000000..198b887ea4b
--- /dev/null
+++ b/src/main/resources/bidder-config/elementaltv.yaml
@@ -0,0 +1,18 @@
+adapters:
+ elementaltv:
+ endpoint: https://pbs.elementaltv.io/ads/processHeaderBid/{{AdUnit}}
+ ortb-version: "2.6"
+ aliases:
+ adoppler: ~
+ meta-info:
+ maintainer-email: support@elementaltv.com
+ app-media-types:
+ - banner
+ - video
+ - native
+ site-media-types:
+ - banner
+ - video
+ - native
+ supported-vendors:
+ vendor-id: 0
diff --git a/src/main/resources/bidder-config/nexx360.yaml b/src/main/resources/bidder-config/nexx360.yaml
index 443ca7f5ab4..975dcd6a21e 100644
--- a/src/main/resources/bidder-config/nexx360.yaml
+++ b/src/main/resources/bidder-config/nexx360.yaml
@@ -19,4 +19,4 @@ adapters:
- native
- audio
supported-vendors:
- vendor-id: 0
+ vendor-id: 965
diff --git a/src/main/resources/bidder-config/optidigital.yaml b/src/main/resources/bidder-config/optidigital.yaml
index 9e48244221b..756e0d71b2e 100644
--- a/src/main/resources/bidder-config/optidigital.yaml
+++ b/src/main/resources/bidder-config/optidigital.yaml
@@ -17,6 +17,6 @@ adapters:
usersync:
cookie-family-name: optidigital
iframe:
- url: https://scripts.opti-digital.com/js/presyncs2s.html?endpoint=optidigital&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redir={{redirect_url}}
+ url: https://scripts.opti-digital.com/js/presyncs2s.html?endpoint=optidigital&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}}
support-cors: false
uid-macro: '$UID'
diff --git a/src/main/resources/bidder-config/smarthub.yaml b/src/main/resources/bidder-config/smarthub.yaml
index 73a50212676..e3d370266b5 100644
--- a/src/main/resources/bidder-config/smarthub.yaml
+++ b/src/main/resources/bidder-config/smarthub.yaml
@@ -11,9 +11,6 @@ adapters:
tredio:
enabled: false
endpoint: https://tredio-prebid.attekmi.com/pbserver/?seat={{AccountID}}&token={{SourceId}}
- vimayx:
- enabled: false
- endpoint: https://vimayx-prebid.attekmi.com/pbserver/?seat={{AccountID}}&token={{SourceId}}
felixads:
enabled: false
endpoint: https://felixads-prebid.attekmi.com/pbserver/?seat={{AccountID}}&token={{SourceId}}
@@ -29,6 +26,9 @@ adapters:
addigi:
enabled: false
endpoint: https://addigi-prebid.attekmi.com/pbserver/?seat={{AccountID}}&token={{SourceId}}
+ radianfusion:
+ enabled: false
+ endpoint: https://radiantfusion-prebid.attekmi.co/pbserver/?seat={{AccountID}}&token={{SourceId}}
meta-info:
maintainer-email: prebid@attekmi.com
app-media-types:
diff --git a/src/main/resources/bidder-config/teal.yaml b/src/main/resources/bidder-config/teal.yaml
new file mode 100644
index 00000000000..00d1a6d48fc
--- /dev/null
+++ b/src/main/resources/bidder-config/teal.yaml
@@ -0,0 +1,30 @@
+adapters:
+ teal:
+ ortb-version: "2.6"
+ endpoint: https://a.bids.ws/openrtb2/auction
+ modifying-vast-xml-allowed: true
+ endpoint-compression: gzip
+ geoscope:
+ - global
+ aliases:
+ tealplus:
+ enabled: false
+ ortb:
+ multiformat-supported: true
+ meta-info:
+ maintainer-email: prebid@teal.works
+ app-media-types:
+ - banner
+ - video
+ - native
+ site-media-types:
+ - banner
+ - video
+ - native
+ supported-vendors:
+ vendor-id: 1378
+ usersync:
+ cookie-family-name: teal
+ iframe:
+ url: https://bids.ws/load-pbs.html?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect_url={{redirect_url}}
+ support-cors: false
diff --git a/src/main/resources/bidder-config/teqblaze.yaml b/src/main/resources/bidder-config/teqblaze.yaml
index 5e1006433ae..9938c49f107 100644
--- a/src/main/resources/bidder-config/teqblaze.yaml
+++ b/src/main/resources/bidder-config/teqblaze.yaml
@@ -43,6 +43,23 @@ adapters:
mata-info:
maintainer-email: support@gravite.net
vendor-id: 377
+ progx:
+ enabled: false
+ endpoint: https://us-east.progrtb.com/pserver
+ meta-info:
+ maintainer-email: pxteam@programmaticx.ai
+ vendor-id: 1344
+ usersync:
+ enabled: true
+ cookie-family-name: progx
+ iframe:
+ url: https://sync.progrtb.com/pbserverIframe?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&ccpa={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&pbserverUrl={{.RedirectURL}}
+ support-cors: false
+ uid-macro: '[UID]'
+ redirect:
+ url: https://sync.progrtb.com/pbserver?gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&ccpa={{us_privacy}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}&redir={{redirect_url}}
+ support-cors: false
+ uid-macro: '[UID]'
meta-info:
maintainer-email: github@teqblaze.com
app-media-types:
diff --git a/src/main/resources/bidder-config/vidazoo.yaml b/src/main/resources/bidder-config/vidazoo.yaml
index 3e1768fb0cb..adb9b9758a5 100644
--- a/src/main/resources/bidder-config/vidazoo.yaml
+++ b/src/main/resources/bidder-config/vidazoo.yaml
@@ -2,19 +2,6 @@ adapters:
vidazoo:
endpoint: https://prebidsrvr.cootlogix.com/openrtb/
aliases:
- progx:
- enabled: false
- endpoint: https://exchange.programmaticx.ai/openrtb/
- meta-info:
- maintainer-email: pxteam@programmaticx.ai
- vendor-id: 1344
- usersync:
- enabled: true
- cookie-family-name: progx
- iframe:
- url: https://sync.programmaticx.ai/api/user/html/685297194d85991a5e6e36dd?pbs=true&gdpr={{gdpr}}&gdpr_consent={{gdpr_consent}}&us_privacy={{us_privacy}}&redirect={{redirect_url}}&gpp={{gpp}}&gpp_sid={{gpp_sid}}
- support-cors: false
- uid-macro: '${userId}'
omnidex:
endpoint: https://exchange.omni-dex.io/openrtb/
usersync:
diff --git a/src/main/resources/static/bidder-params/adagio.json b/src/main/resources/static/bidder-params/adagio.json
index d0cbf17d8fc..af6a6bc4465 100644
--- a/src/main/resources/static/bidder-params/adagio.json
+++ b/src/main/resources/static/bidder-params/adagio.json
@@ -10,7 +10,7 @@
},
"placement": {
"type": "string",
- "description": "Refers to the placement of an adunit in a page. Must not contain any information about the type of device."
+ "description": "Refers to the placement of an adunit in a page. See Adagio recommended values: https://prebid.org/dev-docs/bidders/adagio.html#recommended-placement-param-values."
},
"site": {
"type": "string",
diff --git a/src/main/resources/static/bidder-params/adocean.json b/src/main/resources/static/bidder-params/adocean.json
deleted file mode 100644
index 5c62c410fb1..00000000000
--- a/src/main/resources/static/bidder-params/adocean.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "$schema": "http://json-schema.org/draft-04/schema#",
- "title": "AdOcean Adapter Params",
- "description": "A schema which validates params accepted by the AdOcean adapter",
- "type": "object",
- "properties": {
- "emiter": {
- "type": "string",
- "description": "Deprecated, use emitterPrefix instead. AdOcean emiter",
- "pattern": ".+"
- },
- "emitterPrefix": {
- "type": "string",
- "description": "AdOcean emitter prefix",
- "pattern": "^[\\w\\-]+$"
- },
- "masterId": {
- "type": "string",
- "description": "Master's id",
- "pattern": "^[\\w.]+$"
- },
- "slaveId": {
- "type": "string",
- "description": "Slave's id",
- "pattern": "^adocean[\\w.]+$"
- }
- },
- "oneOf": [
- {
- "required": [
- "emiter",
- "masterId",
- "slaveId"
- ]
- },
- {
- "required": [
- "emitterPrefix",
- "masterId",
- "slaveId"
- ]
- }
- ]
-}
diff --git a/src/main/resources/static/bidder-params/adot.json b/src/main/resources/static/bidder-params/adot.json
index e82b17be8d4..45caca4da80 100644
--- a/src/main/resources/static/bidder-params/adot.json
+++ b/src/main/resources/static/bidder-params/adot.json
@@ -14,7 +14,8 @@
},
"publisherPath": {
"type": "string",
- "description": "An optional path used in the bid endpoint"
+ "enum": ["/hubvisor", "/spotx"],
+ "description": "An optional path used in the bid endpoint. Only '/hubvisor' and '/spotx' are allowed."
}
},
"required": []
diff --git a/src/main/resources/static/bidder-params/clydo.json b/src/main/resources/static/bidder-params/clydo.json
new file mode 100644
index 00000000000..71b60d09405
--- /dev/null
+++ b/src/main/resources/static/bidder-params/clydo.json
@@ -0,0 +1,20 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Clydo Adapter Params",
+ "description": "A schema which validates params accepted by the Clydo adapter",
+ "type": "object",
+ "properties": {
+ "partnerId": {
+ "type": "string",
+ "description": "Partner ID",
+ "minLength": 1
+ },
+ "region": {
+ "type": "string",
+ "description": "Regional endpoint identifier (us, usw, eu, apac)",
+ "enum": ["us", "usw", "eu", "apac"]
+ }
+ },
+
+ "required": ["partnerId"]
+}
diff --git a/src/main/resources/static/bidder-params/adoppler.json b/src/main/resources/static/bidder-params/elementaltv.json
similarity index 66%
rename from src/main/resources/static/bidder-params/adoppler.json
rename to src/main/resources/static/bidder-params/elementaltv.json
index eaa4e6df80e..f80e6c4f0d7 100644
--- a/src/main/resources/static/bidder-params/adoppler.json
+++ b/src/main/resources/static/bidder-params/elementaltv.json
@@ -1,16 +1,12 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
- "title": "Adoppler Adapter Params",
- "description": "A schema which validates params accepted by the Adoppler adapter",
+ "title": "ElementalTV Adapter Params",
+ "description": "A schema which validates params accepted by the ElementalTV adapter",
"type": "object",
"properties": {
"adunit": {
"type": "string",
"description": "AdUnit to bid against to."
- },
- "client": {
- "type": "string",
- "description": "Client name."
}
},
"required": ["adunit"]
diff --git a/src/main/resources/static/bidder-params/teal.json b/src/main/resources/static/bidder-params/teal.json
new file mode 100644
index 00000000000..52e073c3af4
--- /dev/null
+++ b/src/main/resources/static/bidder-params/teal.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "Teal Adapter Params",
+ "description": "A schema which validates params accepted by the Teal adapter",
+ "type": "object",
+ "properties": {
+ "account": {
+ "type": "string",
+ "description": "Account ID"
+ },
+ "placement": {
+ "type": "string",
+ "description": "Placement ID or name (optional)"
+ }
+ },
+ "required": [
+ "account"
+ ]
+}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/Eid.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/Eid.groovy
index 2cb344f6967..25e14e62126 100644
--- a/src/test/groovy/org/prebid/server/functional/model/request/auction/Eid.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/Eid.groovy
@@ -25,4 +25,14 @@ class Eid {
it.matchMethod = PBSUtils.randomNumber
}
}
+
+ static Eid from(EidPermission eidPermission, List uids = [Uid.defaultUid]) {
+ new Eid().tap {
+ it.source = eidPermission.source
+ it.uids = uids
+ it.inserter = eidPermission.inserter
+ it.matcher = eidPermission.matcher
+ it.matchMethod = eidPermission.matchMethod
+ }
+ }
}
diff --git a/src/test/groovy/org/prebid/server/functional/model/request/auction/EidPermission.groovy b/src/test/groovy/org/prebid/server/functional/model/request/auction/EidPermission.groovy
index df77f8f1fbe..5807c6f9241 100644
--- a/src/test/groovy/org/prebid/server/functional/model/request/auction/EidPermission.groovy
+++ b/src/test/groovy/org/prebid/server/functional/model/request/auction/EidPermission.groovy
@@ -1,11 +1,39 @@
package org.prebid.server.functional.model.request.auction
+import com.fasterxml.jackson.annotation.JsonProperty
import groovy.transform.ToString
import org.prebid.server.functional.model.bidder.BidderName
+import org.prebid.server.functional.util.PBSUtils
+
+import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
@ToString(includeNames = true, ignoreNulls = true)
class EidPermission {
String source
- List bidders
+ String inserter
+ String matcher
+ @JsonProperty("mm")
+ Integer matchMethod
+ List bidders = [GENERIC]
+
+ static EidPermission getDefaultEidPermission(List bidders = [GENERIC]) {
+ new EidPermission().tap {
+ it.source = PBSUtils.randomString
+ it.inserter = PBSUtils.randomString
+ it.matcher = PBSUtils.randomString
+ it.matchMethod = PBSUtils.randomNumber
+ it.bidders = bidders
+ }
+ }
+
+ static EidPermission from(Eid eid, List bidders = [GENERIC]) {
+ new EidPermission().tap {
+ it.source = eid.source
+ it.inserter = eid.inserter
+ it.matcher = eid.matcher
+ it.matchMethod = eid.matchMethod
+ it.bidders = bidders
+ }
+ }
}
diff --git a/src/test/groovy/org/prebid/server/functional/tests/EidsSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/EidsSpec.groovy
index 46b0ff2a5aa..6bc172f3fa4 100644
--- a/src/test/groovy/org/prebid/server/functional/tests/EidsSpec.groovy
+++ b/src/test/groovy/org/prebid/server/functional/tests/EidsSpec.groovy
@@ -13,9 +13,12 @@ import org.prebid.server.functional.model.request.auction.UserExt
import org.prebid.server.functional.service.PrebidServerException
import org.prebid.server.functional.util.PBSUtils
+import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST
import static org.prebid.server.functional.model.bidder.BidderName.ALIAS
import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
+import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE
import static org.prebid.server.functional.model.bidder.BidderName.OPENX
+import static org.prebid.server.functional.model.bidder.BidderName.RUBICON
import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN
import static org.prebid.server.functional.model.bidder.BidderName.WILDCARD
import static org.prebid.server.functional.model.request.auction.DebugCondition.DISABLED
@@ -224,7 +227,7 @@ class EidsSpec extends BaseSpec {
imp[0].ext.prebid.bidder.openx = Openx.defaultOpenx
ext.prebid.data = new ExtRequestPrebidData(
eidpermissions: [new EidPermission(source: PBSUtils.randomString, bidders: eidsBidder),
- new EidPermission(source: PBSUtils.randomString)])
+ new EidPermission(source: PBSUtils.randomString, bidders: null)])
}
when: "PBS processes auction request"
@@ -338,4 +341,256 @@ class EidsSpec extends BaseSpec {
and: "Bid response shouldn't contain warning"
assert !bidResponse.ext.warnings
}
+
+ def "PBS should pass user.eids to all bidders when any of required eid permissions doesn't match"() {
+ given: "Default BidRequest with eids"
+ def eidPermission = EidPermission.getDefaultEidPermission([RUBICON])
+ def userEid = updateEidClosure(Eid.from(eidPermission))
+
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ user = new User(eids: [userEid])
+ ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [eidPermission])
+ }
+
+ when: "PBS processes auction request"
+ def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain requested eids"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.user.eids == [userEid]
+
+ and: "Bid response shouldn't contain any errors and warnings"
+ assert !bidResponse.ext.errors
+ assert !bidResponse.ext.warnings
+
+ where:
+ updateEidClosure << [
+ { Eid eid -> eid.tap { it.inserter = null } },
+ { Eid eid -> eid.tap { it.matcher = null } },
+ { Eid eid -> eid.tap { it.matchMethod = null } },
+
+ { Eid eid -> eid.tap { it.inserter = "" } },
+ { Eid eid -> eid.tap { it.matcher = "" } },
+
+ { Eid eid -> eid.tap { it.source = PBSUtils.randomString } },
+ { Eid eid -> eid.tap { it.inserter = PBSUtils.randomString } },
+ { Eid eid -> eid.tap { it.matcher = PBSUtils.randomString } },
+ { Eid eid -> eid.tap { it.matchMethod = PBSUtils.randomNumber } }
+ ]
+ }
+
+ def "PBS shouldn't pass user.eids to unmatched bidders when eidPermissions fields match user.eids"() {
+ given: "Default BidRequest with eids"
+ def eidPermissionWithUnmatchedBidder = eidPermission.tap {
+ bidders = [RUBICON]
+ }
+ def userEid = updateEidClosure(eidPermissionWithUnmatchedBidder)
+
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ user = new User(eids: [userEid])
+ ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [eidPermissionWithUnmatchedBidder])
+ }
+
+ when: "PBS processes auction request"
+ def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request shouldn't contain eids"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert !bidderRequest.user.eids
+
+ and: "Bid response shouldn't contain any errors and warnings"
+ assert !bidResponse.ext.errors
+ assert !bidResponse.ext.warnings
+
+ where:
+ eidPermission | updateEidClosure
+ new EidPermission(source: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid) }
+ new EidPermission(source: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.source = eid.source } }
+ new EidPermission(inserter: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } }
+ new EidPermission(inserter: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.inserter = eid.inserter } }
+ new EidPermission(matcher: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } }
+ new EidPermission(matcher: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.matcher = eid.matcher } }
+ new EidPermission(matchMethod: PBSUtils.randomNumber) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } }
+ new EidPermission(matchMethod: PBSUtils.randomNumber) | { EidPermission eid -> Eid.getDefaultEid().tap { it.matchMethod = eid.matchMethod } }
+ }
+
+ def "PBS should filter only unauthorized eids when multiple eids with different permissions are present"() {
+ given: "Default BidRequest with eids"
+ def eidPermissionWithUnmatchedBidder = EidPermission.getDefaultEidPermission([RUBICON])
+ def userEid = Eid.from(eidPermissionWithUnmatchedBidder)
+ def properEids = [Eid.defaultEid, Eid.defaultEid]
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ user = new User(eids: properEids + userEid)
+ ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [eidPermissionWithUnmatchedBidder])
+ }
+
+ when: "PBS processes auction request"
+ def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain requested eids"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.user.eids == properEids
+
+ and: "Bid response shouldn't contain any errors and warnings"
+ assert !bidResponse.ext.errors
+ assert !bidResponse.ext.warnings
+ }
+
+ def "PBS should pass user.eids to matched bidders when eidPermissions fields match user.eids"() {
+ given: "Default BidRequest with eids"
+ def eidPermissionAllowingCurrentBidder = eidPermission.tap {
+ bidders = [[GENERIC, GENERIC_CAMEL_CASE].shuffled().first()]
+ }
+ def userEid = updateEidClosure(eidPermissionAllowingCurrentBidder)
+
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ user = new User(eids: [userEid])
+ ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [eidPermissionAllowingCurrentBidder])
+ }
+
+ when: "PBS processes auction request"
+ def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain requested eids"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.user.eids == [userEid]
+
+ and: "Bid response shouldn't contain any errors and warnings"
+ assert !bidResponse.ext.errors
+ assert !bidResponse.ext.warnings
+
+ where:
+ eidPermission | updateEidClosure
+ new EidPermission(source: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid) }
+ new EidPermission(source: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.source = eid.source } }
+ new EidPermission(inserter: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } }
+ new EidPermission(inserter: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.inserter = eid.inserter } }
+ new EidPermission(matcher: PBSUtils.randomString) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } }
+ new EidPermission(matcher: PBSUtils.randomString) | { EidPermission eid -> Eid.getDefaultEid().tap { it.matcher = eid.matcher } }
+ new EidPermission(matchMethod: PBSUtils.randomNumber) | { EidPermission eid -> Eid.from(eid).tap { it.source = PBSUtils.randomString } }
+ new EidPermission(matchMethod: PBSUtils.randomNumber) | { EidPermission eid -> Eid.getDefaultEid().tap { it.matchMethod = eid.matchMethod } }
+ }
+
+ def "PBS should apply most specific eidPermissions rule when multiple rules match"() {
+ given: "Default BidRequest with eids"
+ def userEid = Eid.getDefaultEid()
+ def moreSpecificEidPermission = moreSpecificPermissionClosure(userEid).tap {
+ it.bidders = [RUBICON]
+ }
+ def lessSpecificEidPermission = lessSpecificPermissionClosure(userEid).tap {
+ it.bidders = [GENERIC]
+ }
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ user = new User(eids: [userEid])
+ ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [moreSpecificEidPermission, lessSpecificEidPermission].shuffled())
+ }
+
+ when: "PBS processes auction request"
+ def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request shouldn't contain eids"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert !bidderRequest.user.eids
+
+ and: "Bid response shouldn't contain any errors and warnings"
+ assert !bidResponse.ext.errors
+ assert !bidResponse.ext.warnings
+
+ where:
+ moreSpecificPermissionClosure | lessSpecificPermissionClosure
+ ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.source = null } })
+ ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.inserter = null } })
+ ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.matcher = null } })
+ ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.matchMethod = null } })
+ ({ Eid eid -> EidPermission.from(eid).tap { it.source = null } }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) })
+ ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) }) | ({ Eid eid -> new EidPermission(matchMethod: eid.matchMethod) })
+ }
+
+ def "PBS should allow access to bidder defined in most specific rule when multiple rules match"() {
+ given: "Default BidRequest with eids"
+ def userEid = Eid.getDefaultEid()
+ def moreSpecificEidPermission = moreSpecificPermissionClosure(userEid).tap {
+ it.bidders = [GENERIC]
+ }
+ def lessSpecificEidPermission = lessSpecificPermissionClosure(userEid).tap {
+ it.bidders = [RUBICON]
+ }
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ user = new User(eids: [userEid])
+ ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [moreSpecificEidPermission, lessSpecificEidPermission].shuffled())
+ }
+
+ when: "PBS processes auction request"
+ def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain requested eids"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.user.eids == [userEid]
+
+ and: "Bid response shouldn't contain any errors and warnings"
+ assert !bidResponse.ext.errors
+ assert !bidResponse.ext.warnings
+
+ where:
+ moreSpecificPermissionClosure | lessSpecificPermissionClosure
+ ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.source = null } })
+ ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.inserter = null } })
+ ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.matcher = null } })
+ ({ Eid eid -> EidPermission.from(eid) }) | ({ Eid eid -> EidPermission.from(eid).tap { it.matchMethod = null } })
+ ({ Eid eid -> EidPermission.from(eid).tap { it.source = null } }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) })
+ ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) }) | ({ Eid eid -> new EidPermission(matchMethod: eid.matchMethod) })
+ }
+
+ def "PBS should apply permissions from any matching rule when specificity is equal"() {
+ given: "Default BidRequest with eids"
+ def userEid = Eid.getDefaultEid()
+ def allowingRule = allowingPermissionClosure(userEid).tap {
+ it.bidders = [GENERIC]
+ }
+ def restrictingRule = restrictingPermissionClosure(userEid).tap {
+ it.bidders = [RUBICON]
+ }
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ user = new User(eids: [userEid])
+ ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [allowingRule, restrictingRule].shuffled())
+ }
+
+ when: "PBS processes auction request"
+ def bidResponse = defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "Bidder request should contain requested eids"
+ def bidderRequest = bidder.getBidderRequest(bidRequest.id)
+ assert bidderRequest.user.eids == [userEid]
+
+ and: "Bid response shouldn't contain any errors and warnings"
+ assert !bidResponse.ext.errors
+ assert !bidResponse.ext.warnings
+
+ where:
+ allowingPermissionClosure | restrictingPermissionClosure
+ ({ Eid eid -> new EidPermission(source: eid.source) }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter) })
+ ({ Eid eid -> new EidPermission(matcher: eid.matcher) }) | ({ Eid eid -> new EidPermission(source: eid.source) })
+ ({ Eid eid -> new EidPermission(matchMethod: eid.matchMethod) }) | ({ Eid eid -> new EidPermission(matcher: eid.matcher) })
+ ({ Eid eid -> new EidPermission(source: eid.source, matcher: eid.matcher) }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter, matchMethod: eid.matchMethod) })
+ ({ Eid eid -> new EidPermission(source: eid.source, inserter: eid.inserter) }) | ({ Eid eid -> new EidPermission(matcher: eid.matcher, matchMethod: eid.matchMethod) })
+ ({ Eid eid -> new EidPermission(source: eid.source, matchMethod: eid.matchMethod) }) | ({ Eid eid -> new EidPermission(inserter: eid.inserter, matcher: eid.matcher) })
+ ({ Eid eid -> EidPermission.from(eid).tap { matchMethod = null } }) | ({ Eid eid -> EidPermission.from(eid).tap { matcher = null } })
+ }
+
+ def "PBS should throw an error when all eidPermissions fields are empty"() {
+ given: "Default bid request with invalid eidPermission"
+ def bidRequest = BidRequest.defaultBidRequest.tap {
+ ext.prebid.data = new ExtRequestPrebidData(eidpermissions: [new EidPermission()])
+ }
+
+ when: "PBS processes auction request"
+ defaultPbsService.sendAuctionRequest(bidRequest)
+
+ then: "PBS should throw error"
+ def exception = thrown(PrebidServerException)
+ assert exception.statusCode == BAD_REQUEST.code()
+ assert exception.responseBody == "Invalid request format: " +
+ "Missing required parameter(s) in request.ext.prebid.data.eidPermissions[]. " +
+ "Either one or a combination of inserter, source, matcher, or mm should be defined."
+ }
}
diff --git a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy
index 7e9eff9ebd3..06a4e22a518 100644
--- a/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy
+++ b/src/test/groovy/org/prebid/server/functional/tests/SetUidSpec.groovy
@@ -20,7 +20,9 @@ import static org.prebid.server.functional.model.bidder.BidderName.ALIAS_CAMEL_C
import static org.prebid.server.functional.model.bidder.BidderName.APPNEXUS
import static org.prebid.server.functional.model.bidder.BidderName.GENERIC
import static org.prebid.server.functional.model.bidder.BidderName.GENERIC_CAMEL_CASE
+import static org.prebid.server.functional.model.bidder.BidderName.GRID
import static org.prebid.server.functional.model.bidder.BidderName.OPENX
+import static org.prebid.server.functional.model.bidder.BidderName.OPENX_ALIAS
import static org.prebid.server.functional.model.bidder.BidderName.RUBICON
import static org.prebid.server.functional.model.bidder.BidderName.UNKNOWN
import static org.prebid.server.functional.model.bidder.BidderName.WILDCARD
@@ -38,21 +40,50 @@ class SetUidSpec extends BaseSpec {
private static final boolean CORS_SUPPORT = false
private static final Integer RANDOM_EXPIRE_DAY = PBSUtils.getRandomNumber(1, 10)
private static final String USER_SYNC_URL = "$networkServiceContainer.rootUri/generic-usersync"
- private static final Map PBS_CONFIG =
- ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String,
- "adapters.${RUBICON.value}.enabled" : "true",
- "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value,
- "adapters.${OPENX.value}.enabled" : "true",
- "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value,
- "adapters.${APPNEXUS.value}.enabled" : "true",
- "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value,
- "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL,
- "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()]
+ private static final String GENERIC_COOKIE_FAMILY_NAME = GENERIC.value
+ private static final String VENDOR_ID = PBSUtils.randomNumber as String
private static final Map UID_COOKIES_CONFIG = ['setuid.number-of-uid-cookies': MAX_NUMBER_OF_UID_COOKIES.toString()]
private static final Map GENERIC_ALIAS_CONFIG = ["adapters.generic.aliases.alias.enabled" : "true",
"adapters.generic.aliases.alias.endpoint": "$networkServiceContainer.rootUri/auction".toString()]
private static final String TCF_ERROR_MESSAGE = "The gdpr_consent param prevents cookies from being saved"
private static final int UNAVAILABLE_FOR_LEGAL_REASONS_CODE = 451
+ private static final Map PBS_CONFIG =
+ ["host-cookie.max-cookie-size-bytes" : MAX_COOKIE_SIZE as String,
+
+ "adapters.${RUBICON.value}.enabled" : "true",
+ "adapters.${RUBICON.value}.usersync.cookie-family-name" : RUBICON.value,
+
+ "adapters.${OPENX.value}.enabled" : "true",
+ "adapters.${OPENX.value}.usersync.cookie-family-name" : OPENX.value,
+
+ "adapters.${APPNEXUS.value}.enabled" : "true",
+ "adapters.${APPNEXUS.value}.usersync.cookie-family-name" : APPNEXUS.value,
+
+ "adapters.${GENERIC.value}.meta-info.vendor-id" : VENDOR_ID,
+ "adapters.${GENERIC.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME,
+ "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL,
+ "adapters.${GENERIC.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(),
+
+ "adapters.${GRID.value}.enabled" : "true",
+ "adapters.${GRID.value}.meta-info.vendor-id" : VENDOR_ID,
+ "adapters.${GRID.value}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME,
+ "adapters.${GRID.value}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL,
+ "adapters.${GRID.value}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(),
+ "adapters.${GRID.value}.usersync.${USER_SYNC_TYPE.value}.uid-macro" : "",
+
+ "adapters.${GENERIC}.aliases.${ALIAS}.enabled" : "true",
+ "adapters.${GENERIC}.aliases.${ALIAS}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(),
+ "adapters.${GENERIC}.aliases.${ALIAS}.meta-info.vendor-id" : VENDOR_ID,
+ "adapters.${GENERIC}.aliases.${ALIAS}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME,
+ "adapters.${GENERIC}.aliases.${ALIAS}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL,
+ "adapters.${GENERIC}.aliases.${ALIAS}.usersync.${USER_SYNC_TYPE.value}.support-cors" : CORS_SUPPORT.toString(),
+
+ "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.enabled" : "true",
+ "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.endpoint" : "$networkServiceContainer.rootUri/auction".toString(),
+ "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.meta-info.vendor-id" : VENDOR_ID,
+ "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.cookie-family-name" : GENERIC_COOKIE_FAMILY_NAME,
+ "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.${USER_SYNC_TYPE.value}.url" : USER_SYNC_URL,
+ "adapters.${GENERIC}.aliases.${OPENX_ALIAS}.usersync.${USER_SYNC_TYPE.value}.support-cors": CORS_SUPPORT.toString()]
@Shared
PrebidServerService singleCookiesPbsService = pbsServiceFactory.getService(PBS_CONFIG + GENERIC_ALIAS_CONFIG)
@@ -513,6 +544,38 @@ class SetUidSpec extends BaseSpec {
assert getSetUidsHeaders(response, true).size() == MAX_NUMBER_OF_UID_COOKIES
}
+ def "PBS shouldn't failed with error when adapters has same user sync and vendor id config"() {
+ given: "Default set uid request"
+ def request = SetuidRequest.defaultSetuidRequest
+
+ and: "Default uids cookie generic and adtrgtme"
+ def genericUidsCookie = UidsCookie.getDefaultUidsCookie(GENERIC)
+ def gridUidsCookie = UidsCookie.getDefaultUidsCookie(GRID)
+
+ when: "PBS processes auction request"
+ def response = singleCookiesPbsService.sendSetUidRequest(request, [gridUidsCookie, genericUidsCookie])
+
+ then: "Response should contain requested tempUIDs"
+ assert response.uidsCookie.tempUIDs[GENERIC]
+ assert response.uidsCookie.tempUIDs[GRID]
+ }
+
+ def "PBS shouldn't failed with error when alias adapters has same user sync and vendor id config"() {
+ given: "Default set uid request"
+ def request = SetuidRequest.defaultSetuidRequest
+
+ and: "Default uids cookie generic alias and opnex alias"
+ def genericAliasUidsCookie = UidsCookie.getDefaultUidsCookie(ALIAS)
+ def genericOpenxAliasUidsCookie = UidsCookie.getDefaultUidsCookie(OPENX_ALIAS)
+
+ when: "PBS processes auction request"
+ def response = singleCookiesPbsService.sendSetUidRequest(request, [genericAliasUidsCookie, genericOpenxAliasUidsCookie])
+
+ then: "Response should contain requested tempUIDs"
+ assert response.uidsCookie.tempUIDs[ALIAS]
+ assert response.uidsCookie.tempUIDs[OPENX_ALIAS]
+ }
+
List getSetUidsHeaders(SetuidResponse response, boolean includeEmpty = false) {
response.headers.get("Set-Cookie").findAll { cookie ->
includeEmpty || !(cookie =~ /\buids\d*=\s*;/)
diff --git a/src/test/java/org/prebid/server/auction/EidPermissionResolverTest.java b/src/test/java/org/prebid/server/auction/EidPermissionResolverTest.java
new file mode 100644
index 00000000000..08d8627f172
--- /dev/null
+++ b/src/test/java/org/prebid/server/auction/EidPermissionResolverTest.java
@@ -0,0 +1,251 @@
+package org.prebid.server.auction;
+
+import com.iab.openrtb.request.Eid;
+import org.junit.jupiter.api.Test;
+import org.prebid.server.proto.openrtb.ext.request.ExtRequestPrebidDataEidPermissions;
+
+import java.util.List;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.singletonList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EidPermissionResolverTest {
+
+ @Test
+ public void resolveShouldFilterEidsWhenBidderIsNotAllowedForSourceIgnoringCase() {
+ // given
+ final List userEids = asList(
+ Eid.builder().source("source1").build(),
+ Eid.builder().source("source2").build());
+
+ final EidPermissionResolver resolver = EidPermissionResolver.of(
+ singletonList(ExtRequestPrebidDataEidPermissions.builder()
+ .source("source1")
+ .bidders(singletonList("OtHeRbIdDeR"))
+ .build()));
+
+ // when and then
+ assertThat(resolver.resolveAllowedEids(userEids, "someBidder"))
+ .containsExactly(Eid.builder().source("source2").build());
+ }
+
+ @Test
+ void resolveShouldFilterEidsWhenBidderIsNotAllowedForInserterIgnoringCase() {
+ // given
+ final List userEids = asList(
+ Eid.builder().inserter("inserter1").build(),
+ Eid.builder().inserter("inserter2").build());
+
+ final EidPermissionResolver resolver = EidPermissionResolver.of(
+ singletonList(ExtRequestPrebidDataEidPermissions.builder()
+ .inserter("inserter1")
+ .bidders(singletonList("OtHeRbIdDeR"))
+ .build()));
+
+ // when and then
+ assertThat(resolver.resolveAllowedEids(userEids, "someBidder"))
+ .containsExactly(Eid.builder().inserter("inserter2").build());
+ }
+
+ @Test
+ public void resolveShouldFilterEidsWhenBidderIsNotAllowedForMatcherIgnoringCase() {
+ // given
+ final List userEids = asList(
+ Eid.builder().matcher("matcher1").build(),
+ Eid.builder().matcher("matcher2").build());
+
+ final EidPermissionResolver resolver = EidPermissionResolver.of(
+ singletonList(ExtRequestPrebidDataEidPermissions.builder()
+ .matcher("matcher1")
+ .bidders(singletonList("OtHeRbIdDeR"))
+ .build()));
+
+ // when and then
+ assertThat(resolver.resolveAllowedEids(userEids, "someBidder"))
+ .containsExactly(Eid.builder().matcher("matcher2").build());
+ }
+
+ @Test
+ public void resolveShouldFilterEidsWhenBidderIsNotAllowedForMm() {
+ // given
+ final List userEids = asList(Eid.builder().mm(1).build(), Eid.builder().mm(2).build());
+
+ final EidPermissionResolver resolver = EidPermissionResolver.of(
+ singletonList(ExtRequestPrebidDataEidPermissions.builder()
+ .mm(1)
+ .bidders(singletonList("OtHeRbIdDeR"))
+ .build()));
+
+ // when and then
+ assertThat(resolver.resolveAllowedEids(userEids, "someBidder"))
+ .containsExactly(Eid.builder().mm(2).build());
+ }
+
+ @Test
+ public void resolveShouldFilterEidsWhenBidderIsNotAllowedUsingMultipleCriteria() {
+ // given
+ final List userEids = asList(
+ Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(),
+ Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build());
+
+ final EidPermissionResolver resolver = EidPermissionResolver.of(
+ singletonList(ExtRequestPrebidDataEidPermissions.builder()
+ .inserter("inserter1")
+ .source("source1")
+ .matcher("matcher1")
+ .mm(1)
+ .bidders(singletonList("OtHeRbIdDeR"))
+ .build()));
+
+ // when and then
+ assertThat(resolver.resolveAllowedEids(userEids, "someBidder")).containsExactly(
+ Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build());
+ }
+
+ @Test
+ public void resolveShouldFilterEidsWhenEveryCriteriaMatches() {
+ // given
+ final List userEids = asList(
+ Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(),
+ Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build());
+
+ final EidPermissionResolver resolver = EidPermissionResolver.of(
+ singletonList(ExtRequestPrebidDataEidPermissions.builder()
+ .inserter("inserter1")
+ .source("source2")
+ .matcher("matcher3")
+ .mm(4)
+ .bidders(singletonList("OtHeRbIdDeR"))
+ .build()));
+
+ // when and then
+ assertThat(resolver.resolveAllowedEids(userEids, "someBidder")).containsExactly(
+ Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(),
+ Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build());
+ }
+
+ @Test
+ public void resolveShouldFilterEidsWhenBidderIsNotAllowedUsingTheMostSpecificRule() {
+ // given
+ final List userEids = asList(
+ Eid.builder().inserter("inserter1").source("source1").matcher("matcher1").mm(1).build(),
+ Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build());
+
+ final EidPermissionResolver resolver = EidPermissionResolver.of(
+ asList(ExtRequestPrebidDataEidPermissions.builder()
+ .inserter("inserter1")
+ .bidders(singletonList("someBidder"))
+ .build(),
+ ExtRequestPrebidDataEidPermissions.builder()
+ .inserter("inserter1")
+ .source("source1")
+ .matcher("matcher1")
+ .mm(1)
+ .bidders(singletonList("OtHeRbIdDeR"))
+ .build()));
+
+ // when and then
+ assertThat(resolver.resolveAllowedEids(userEids, "someBidder")).containsExactly(
+ Eid.builder().inserter("inserter2").source("source2").matcher("matcher2").mm(2).build());
+ }
+
+ @Test
+ public void resolveShouldNotFilterUserExtEidsWhenBidderIsAllowedUsingTheMostSpecificRule() {
+ // given
+ final List