diff --git a/.github/workflows/helpers/pull-request-utils.js b/.github/workflows/helpers/pull-request-utils.js
new file mode 100644
index 00000000000..64ea538290f
--- /dev/null
+++ b/.github/workflows/helpers/pull-request-utils.js
@@ -0,0 +1,337 @@
+const synchronizeEvent = "synchronize",
+ openedEvent = "opened",
+ completedStatus = "completed",
+ resultSize = 100
+
+class diffHelper {
+ constructor(input) {
+ this.owner = input.context.repo.owner
+ this.repo = input.context.repo.repo
+ this.github = input.github
+ this.pullRequestNumber = input.context.payload.pull_request.number
+ this.pullRequestEvent = input.event
+ this.testName = input.testName
+ this.fileNameFilter = !input.fileNameFilter ? () => true : input.fileNameFilter
+ this.fileLineFilter = !input.fileLineFilter ? () => true : input.fileLineFilter
+ }
+
+ /*
+ Checks whether the test defined by this.testName has been executed on the given commit
+ @param {string} commit - commit SHA to check for test execution
+ @returns {boolean} - returns true if the test has been executed on the commit, otherwise false
+ */
+ async #isTestExecutedOnCommit(commit) {
+ const response = await this.github.rest.checks.listForRef({
+ owner: this.owner,
+ repo: this.repo,
+ ref: commit,
+ })
+
+ return response.data.check_runs.some(
+ ({ status, name }) => status === completedStatus && name === this.testName
+ )
+ }
+
+ /*
+ Retrieves the line numbers of added or updated lines in the provided files
+ @param {Array} files - array of files containing their filename and patch
+ @returns {Object} - object mapping filenames to arrays of line numbers indicating the added or updated lines
+ */
+ async #getDiffForFiles(files = []) {
+ let diff = {}
+ for (const { filename, patch } of files) {
+ if (this.fileNameFilter(filename)) {
+ const lines = patch.split("\n")
+ if (lines.length === 1) {
+ continue
+ }
+
+ let lineNumber
+ for (const line of lines) {
+ // Check if line is diff header
+ // example:
+ // @@ -1,3 +1,3 @@
+ // 1 var a
+ // 2
+ // 3 - //test
+ // 3 +var b
+ // Here @@ -1,3 +1,3 @@ is diff header
+ if (line.match(/@@\s.*?@@/) != null) {
+ lineNumber = parseInt(line.match(/\+(\d+)/)[0])
+ continue
+ }
+
+ // "-" prefix indicates line was deleted. So do not consider deleted line
+ if (line.startsWith("-")) {
+ continue
+ }
+
+ // "+"" prefix indicates line was added or updated. Include line number in diff details
+ if (line.startsWith("+") && this.fileLineFilter(line)) {
+ diff[filename] = diff[filename] || []
+ diff[filename].push(lineNumber)
+ }
+ lineNumber++
+ }
+ }
+ }
+ return diff
+ }
+
+ /*
+ Retrieves a list of commits that have not been checked by the test defined by this.testName
+ @returns {Array} - array of commit SHAs that have not been checked by the test
+ */
+ async #getNonScannedCommits() {
+ const { data } = await this.github.rest.pulls.listCommits({
+ owner: this.owner,
+ repo: this.repo,
+ pull_number: this.pullRequestNumber,
+ per_page: resultSize,
+ })
+ let nonScannedCommits = []
+
+ // API returns commits in ascending order. Loop in reverse to quickly retrieve unchecked commits
+ for (let i = data.length - 1; i >= 0; i--) {
+ const { sha, parents } = data[i]
+
+ // Commit can be merged master commit. Such commit have multiple parents
+ // Do not consider such commit for building file diff
+ if (parents.length > 1) {
+ continue
+ }
+
+ const isTestExecuted = await this.#isTestExecutedOnCommit(sha)
+ if (isTestExecuted) {
+ // Remaining commits have been tested in previous scans. Therefore, do not need to be considered again
+ break
+ } else {
+ nonScannedCommits.push(sha)
+ }
+ }
+
+ // Reverse to return commits in ascending order. This is needed to build diff for commits in chronological order
+ return nonScannedCommits.reverse()
+ }
+
+ /*
+ Filters the commit diff to include only the files that are part of the PR diff
+ @param {Array} commitDiff - array of line numbers representing lines added or updated in the commit
+ @param {Array} prDiff - array of line numbers representing lines added or updated in the pull request
+ @returns {Array} - filtered commit diff, including only the files that are part of the PR diff
+ */
+ async #filterCommitDiff(commitDiff = [], prDiff = []) {
+ return commitDiff.filter((file) => prDiff.includes(file))
+ }
+
+ /*
+ Builds the diff for the pull request, including both the changes in the pull request and the changes in non-scanned commits
+ @returns {string} - json string representation of the pull request diff and the diff for non-scanned commits
+ */
+ async buildDiff() {
+ const { data } = await this.github.rest.pulls.listFiles({
+ owner: this.owner,
+ repo: this.repo,
+ pull_number: this.pullRequestNumber,
+ per_page: resultSize,
+ })
+
+ const pullRequestDiff = await this.#getDiffForFiles(data)
+
+ const nonScannedCommitsDiff =
+ Object.keys(pullRequestDiff).length != 0 && this.pullRequestEvent === synchronizeEvent // The "synchronize" event implies that new commit are pushed after the pull request was opened
+ ? await this.getNonScannedCommitDiff(pullRequestDiff)
+ : {}
+
+ const prDiffFiles = Object.keys(pullRequestDiff)
+ const pullRequest = {
+ hasChanges: prDiffFiles.length > 0,
+ files: prDiffFiles.join(" "),
+ diff: pullRequestDiff,
+ }
+ const uncheckedCommits = { diff: nonScannedCommitsDiff }
+ return JSON.stringify({ pullRequest, uncheckedCommits })
+ }
+
+ /*
+ Retrieves the diff for non-scanned commits by comparing their changes with the pull request diff
+ @param {Object} pullRequestDiff - The diff of files in the pull request
+ @returns {Object} - The diff of files in the non-scanned commits that are part of the pull request diff
+ */
+ async getNonScannedCommitDiff(pullRequestDiff) {
+ let nonScannedCommitsDiff = {}
+ // Retrieves list of commits that have not been scanned by the PR check
+ const nonScannedCommits = await this.#getNonScannedCommits()
+ for (const commit of nonScannedCommits) {
+ const { data } = await this.github.rest.repos.getCommit({
+ owner: this.owner,
+ repo: this.repo,
+ ref: commit,
+ })
+
+ const commitDiff = await this.#getDiffForFiles(data.files)
+ const files = Object.keys(commitDiff)
+ for (const file of files) {
+ // Consider scenario where the changes made to a file in the initial commit are completely undone by subsequent commits
+ // In such cases, the modifications from the initial commit should not be taken into account
+ // If the changes were entirely removed, there should be no entry for the file in the pullRequestStats
+ const filePRDiff = pullRequestDiff[file]
+ if (!filePRDiff) {
+ continue
+ }
+
+ // Consider scenario where changes made in the commit were partially removed or modified by subsequent commits
+ // In such cases, include only those commit changes that are part of the pullRequestStats object
+ // This ensures that only the changes that are reflected in the pull request are considered
+ const changes = await this.#filterCommitDiff(commitDiff[file], filePRDiff)
+
+ if (changes.length !== 0) {
+ // Check if nonScannedCommitsDiff[file] exists, if not assign an empty array to it
+ nonScannedCommitsDiff[file] = nonScannedCommitsDiff[file] || []
+ // Combine the existing nonScannedCommitsDiff[file] array with the commit changes
+ // Remove any duplicate elements using the Set data structure
+ nonScannedCommitsDiff[file] = [
+ ...new Set([...nonScannedCommitsDiff[file], ...changes]),
+ ]
+ }
+ }
+ }
+ return nonScannedCommitsDiff
+ }
+}
+
+class semgrepHelper {
+ constructor(input) {
+ this.owner = input.context.repo.owner
+ this.repo = input.context.repo.repo
+ this.github = input.github
+
+ this.pullRequestNumber = input.context.payload.pull_request.number
+ this.pullRequestEvent = input.event
+
+ this.pullRequestDiff = input.diff.pullRequest.diff
+ this.newCommitsDiff = input.diff.uncheckedCommits.diff
+
+ this.semgrepErrors = []
+ this.semgrepWarnings = []
+ input.semgrepResult.forEach((res) => {
+ res.severity === "High" ? this.semgrepErrors.push(res) : this.semgrepWarnings.push(res)
+ })
+
+ this.headSha = input.headSha
+ }
+
+ /*
+ Retrieves the matching line number from the provided diff for a given file and range of lines
+ @param {Object} range - object containing the file, start line, and end line to find a match
+ @param {Object} diff - object containing file changes and corresponding line numbers
+ @returns {number|null} - line number that matches the range within the diff, or null if no match is found
+ */
+ async #getMatchingLineFromDiff({ file, start, end }, diff) {
+ const fileDiff = diff[file]
+ if (!fileDiff) {
+ return null
+ }
+ if (fileDiff.includes(start)) {
+ return start
+ }
+ if (fileDiff.includes(end)) {
+ return end
+ }
+ return null
+ }
+
+ /*
+ Splits the semgrep results into different categories based on the scan
+ @param {Array} semgrepResults - array of results reported by semgrep
+ @returns {Object} - object containing the categorized semgrep results i.e results reported in previous scans and new results found in the current scan
+ */
+ async #splitSemgrepResultsByScan(semgrepResults = []) {
+ const result = {
+ nonDiff: [], // Errors or warnings found in files updated in pull request, but not part of sections that were modified in the pull request
+ previous: [], // Errors or warnings found in previous semgrep scans
+ current: [], // Errors or warnings found in current semgrep scan
+ }
+
+ for (const se of semgrepResults) {
+ const prDiffLine = await this.#getMatchingLineFromDiff(se, this.pullRequestDiff)
+ if (!prDiffLine) {
+ result.nonDiff.push({ ...se })
+ continue
+ }
+
+ switch (this.pullRequestEvent) {
+ case openedEvent:
+ // "Opened" event implies that this is the first check
+ // Therefore, the error should be appended to the result.current
+ result.current.push({ ...se, line: prDiffLine })
+ case synchronizeEvent:
+ const commitDiffLine = await this.#getMatchingLineFromDiff(se, this.newCommitsDiff)
+ // Check if error or warning is part of current commit diff
+ // If not then error or warning was reported in previous scans
+ commitDiffLine != null
+ ? result.current.push({ ...se, line: commitDiffLine })
+ : result.previous.push({
+ ...se,
+ line: prDiffLine,
+ })
+ }
+ }
+ return result
+ }
+
+ /*
+ Adds review comments based on the semgrep results to the current pull request
+ @returns {Object} - object containing the count of unaddressed comments from the previous scan and the count of new comments from the current scan
+ */
+ async addReviewComments() {
+ let result = {
+ previousScan: { unAddressedComments: 0 },
+ currentScan: { newComments: 0 },
+ }
+
+ if (this.semgrepErrors.length == 0 && this.semgrepWarnings.length == 0) {
+ return result
+ }
+
+ const errors = await this.#splitSemgrepResultsByScan(this.semgrepErrors)
+ if (errors.previous.length == 0 && errors.current.length == 0) {
+ console.log("Semgrep did not find any errors in the current pull request changes")
+ } else {
+ for (const { message, file, line } of errors.current) {
+ await this.github.rest.pulls.createReviewComment({
+ owner: this.owner,
+ repo: this.repo,
+ pull_number: this.pullRequestNumber,
+ commit_id: this.headSha,
+ body: message,
+ path: file,
+ line: line,
+ })
+ }
+ result.currentScan.newComments = errors.current.length
+ if (this.pullRequestEvent == synchronizeEvent) {
+ result.previousScan.unAddressedComments = errors.previous.length
+ }
+ }
+
+ const warnings = await this.#splitSemgrepResultsByScan(this.semgrepWarnings)
+ for (const { message, file, line } of warnings.current) {
+ await this.github.rest.pulls.createReviewComment({
+ owner: this.owner,
+ repo: this.repo,
+ pull_number: this.pullRequestNumber,
+ commit_id: this.headSha,
+ body: "Consider this as a suggestion. " + message,
+ path: file,
+ line: line,
+ })
+ }
+ return result
+ }
+}
+
+module.exports = {
+ diffHelper: (input) => new diffHelper(input),
+ semgrepHelper: (input) => new semgrepHelper(input),
+}
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
new file mode 100644
index 00000000000..87c5573e671
--- /dev/null
+++ b/.github/workflows/semgrep.yml
@@ -0,0 +1,74 @@
+name: Adapter semgrep checks
+on:
+ pull_request:
+ paths: ["adapters/*/*.go"]
+permissions: write-all
+jobs:
+ semgrep-check:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout repo
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - name: Calculate diff
+ id: calculate_diff
+ uses: actions/github-script@v6
+ with:
+ result-encoding: string
+ script: |
+ const utils = require('./.github/workflows/helpers/pull-request-utils.js')
+ // consider only non-test Go files that are part of the adapter code
+ function fileNameFilter(filename) {
+ return filename.startsWith("adapters/") && filename.split("/").length > 2 && filename.endsWith(".go") && !filename.endsWith("_test.go")
+ }
+ const helper = utils.diffHelper({github, context, fileNameFilter, event: "${{github.event.action}}", testName: "${{github.job}}"})
+ return await helper.buildDiff()
+
+ - name: Should run semgrep
+ id: should_run_semgrep
+ run: |
+ hasChanges=$(echo '${{ steps.calculate_diff.outputs.result }}' | jq .pullRequest.hasChanges)
+ echo "hasChanges=${hasChanges}" >> $GITHUB_OUTPUT
+
+ - name: Install semgrep
+ if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true')
+ run: |
+ pip3 install semgrep==1.22.0
+ semgrep --version
+
+ - name: Run semgrep tests
+ id: run_semgrep_tests
+ if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true')
+ run: |
+ unqouted_string=$(echo '${{ steps.calculate_diff.outputs.result }}' | jq .pullRequest.files | tr -d '"')
+ outputs=$(semgrep --gitlab-sast --config=.semgrep/adapter $unqouted_string | jq '[.vulnerabilities[] | {"file": .location.file, "severity": .severity, "start": .location.start_line, "end": .location.end_line, "message": (.message | gsub("\\n"; "\n"))}]' | jq -c | jq -R)
+ echo "semgrep_result=${outputs}" >> "$GITHUB_OUTPUT"
+
+ - name: Add pull request comment
+ id: add_pull_request_comment
+ if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true')
+ uses: actions/github-script@v6.4.1
+ with:
+ github-token: ${{ secrets.GITHUB_TOKEN }}
+ result-encoding: string
+ script: |
+ const utils = require('./.github/workflows/helpers/pull-request-utils.js')
+ const helper = utils.semgrepHelper({
+ github, context, event: "${{github.event.action}}",
+ semgrepResult: JSON.parse(${{ steps.run_semgrep_tests.outputs.semgrep_result }}),
+ diff: ${{ steps.calculate_diff.outputs.result }}, headSha: "${{github.event.pull_request.head.sha}}"
+ })
+ const { previousScan, currentScan } = await helper.addReviewComments()
+ return previousScan.unAddressedComments + currentScan.newComments
+
+ - name: Adapter semgrep checks result
+ if: contains(steps.should_run_semgrep.outputs.hasChanges, 'true')
+ run: |
+ if [ "${{steps.add_pull_request_comment.outputs.result}}" -ne "0" ]; then
+ echo 'Semgrep has found "${{steps.add_pull_request_comment.outputs.result}}" errors'
+ exit 1
+ else
+ echo 'Semgrep did not find any errors in the pull request changes'
+ fi
diff --git a/.github/workflows/validate-merge.yml b/.github/workflows/validate-merge.yml
index e85dc3de50c..85ae4a89236 100644
--- a/.github/workflows/validate-merge.yml
+++ b/.github/workflows/validate-merge.yml
@@ -2,7 +2,7 @@ name: Validate Merge
on:
pull_request:
- branches: [master]
+ branches: [master, msp]
jobs:
validate-merge:
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
index 83b29709bd4..2c224394f9c 100644
--- a/.github/workflows/validate.yml
+++ b/.github/workflows/validate.yml
@@ -2,9 +2,9 @@ name: Validate
on:
push:
- branches: [master]
+ branches: [master, msp]
pull_request:
- branches: [master]
+ branches: [master, msp]
jobs:
validate:
diff --git a/.semgrep/adapter/builder-struct-name.go b/.semgrep/adapter/builder-struct-name.go
new file mode 100644
index 00000000000..37a938ca748
--- /dev/null
+++ b/.semgrep/adapter/builder-struct-name.go
@@ -0,0 +1,124 @@
+/*
+ builder-struct-name tests
+ https://semgrep.dev/docs/writing-rules/testing-rules
+ "ruleid" prefix in comment indicates patterns that should be flagged by semgrep
+ "ok" prefix in comment indidcates patterns that should not be flagged by the semgrep
+*/
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ foo1 := foo{}
+ // ruleid: builder-struct-name-check
+ return &fooadapter{foo: foo1}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ // ruleid: builder-struct-name-check
+ return &adapterbar{}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ // ruleid: builder-struct-name-check
+ return &fooadapterbar{}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ // ruleid: builder-struct-name-check
+ return &FooAdapter{}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ // ruleid: builder-struct-name-check
+ return &AdapterBar{}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ // ruleid: builder-struct-name-check
+ return &AdapterBar{}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ // ruleid: builder-struct-name-check
+ return &FooAdapterBar{}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ foo2 := foo{}
+ //ruleid: builder-struct-name-check
+ adpt1 := Adapter{foo: foo2}
+ return &adpt1, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ //ruleid: builder-struct-name-check
+ builder := &Adapter{foo{}}
+ return builder, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ foo3 := foo{}
+ if foo3.bar == "" {
+ foo3.bar = "bar"
+ }
+ //ruleid: builder-struct-name-check
+ adpt2 := Adapter{}
+ adpt2.foo = foo3
+ return &adpt2, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ //ruleid: builder-struct-name-check
+ return &foo{}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ //ruleid: builder-struct-name-check
+ var obj Adapter
+ obj.Foo = "foo"
+ if obj.Bar == "" {
+ obj.Bar = "bar"
+ }
+ return &obj, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ //ruleid: builder-struct-name-check
+ var obj *FooAdapterBar
+ obj.Foo = "foo"
+ if obj.Bar == "" {
+ obj.Bar = "bar"
+ }
+ return obj, nil
+}
+
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+ // ok: builder-struct-name-check
+ return &adapter{endpoint: "www.foo.com"}, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ builder := &adapter{}
+ builder.endpoint = "www.foo.com"
+ // ok: builder-struct-name-check
+ return builder, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ builder := adapter{}
+ builder.endpoint = "www.foo.com"
+ // ok: builder-struct-name-check
+ return &builder, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ var builder adapter
+ builder.endpoint = "www.foo.com"
+ // ok: builder-struct-name-check
+ return &builder, nil
+}
+
+func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (adapters.Bidder, error) {
+ var builder *adapter
+ builder.endpoint = "www.foo.com"
+ // ok: builder-struct-name-check
+ return builder, nil
+}
\ No newline at end of file
diff --git a/.semgrep/adapter/builder-struct-name.yml b/.semgrep/adapter/builder-struct-name.yml
new file mode 100644
index 00000000000..bc876ae1809
--- /dev/null
+++ b/.semgrep/adapter/builder-struct-name.yml
@@ -0,0 +1,58 @@
+rules:
+ - id: builder-struct-name-check
+ languages:
+ - go
+ message: |
+ You can call this simply "adapter", the `$BUILDER` identification is already supplied by the package name. As you have it, referencing your adapter from outside the package would be `$BUILDER.$BUILDER` which looks a little redundant. See example below:
+
+ ```
+ package foo
+
+ type adapter struct {
+ endpoint string
+ }
+
+ func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+ return &adapter{endpoint: "https://www.foo.com"}, nil
+ }
+ ```
+ severity: ERROR
+ patterns:
+ - pattern-either:
+ - pattern-inside: >
+ func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) {
+ ...
+ $BUILDER_OBJ := &$BUILDER{...}
+ ...
+ return $BUILDER_OBJ, nil
+ }
+ - pattern-inside: >
+ func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) {
+ ...
+ $BUILDER_OBJ := $BUILDER{...}
+ ...
+ return &$BUILDER_OBJ, nil
+ }
+ - pattern-inside: >
+ func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) {
+ ...
+ return &$BUILDER{...}, ...
+ }
+ - pattern-inside: >
+ func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) {
+ ...
+ var $BUILDER_OBJ $BUILDER
+ ...
+ return &$BUILDER_OBJ, ...
+ }
+ - pattern-inside: >
+ func Builder($BIDDER_NAME openrtb_ext.BidderName, $CONFIG config.Adapter, $SERVER config.Server) (adapters.Bidder, error) {
+ ...
+ var $BUILDER_OBJ *$BUILDER
+ ...
+ return $BUILDER_OBJ, ...
+ }
+ - focus-metavariable: $BUILDER
+ - metavariable-regex:
+ metavariable: $BUILDER
+ regex: (?!adapter$)
diff --git a/.semgrep/adapter/package-import.go b/.semgrep/adapter/package-import.go
new file mode 100644
index 00000000000..a34ce8789cd
--- /dev/null
+++ b/.semgrep/adapter/package-import.go
@@ -0,0 +1,47 @@
+import (
+ // ok: package-import-check
+ "fmt"
+ // ok: package-import-check
+ "os"
+ // ruleid: package-import-check
+ "github.com/mitchellh/copystructure"
+ // ruleid: package-import-check
+ "github.com/golang/glog"
+)
+
+import (
+ // ok: package-import-check
+ "fmt"
+ // ruleid: package-import-check
+ cs "github.com/mitchellh/copystructure"
+ // ok: package-import-check
+ "os"
+ // ruleid: package-import-check
+ log "github.com/golang/glog"
+)
+
+import (
+ // ok: package-import-check
+ "fmt"
+ // ruleid: package-import-check
+ cs "github.com/mitchellh/copystructure/subpackage"
+ // ok: package-import-check
+ "os"
+ // ruleid: package-import-check
+ log "github.com/golang/glog/subpackage"
+)
+
+// ruleid: package-import-check
+import "github.com/golang/glog"
+
+// ruleid: package-import-check
+import "github.com/mitchellh/copystructure"
+
+// ruleid: package-import-check
+import log "github.com/golang/glog"
+
+// ruleid: package-import-check
+import copy "github.com/mitchellh/copystructure"
+
+// ok: package-import-check
+import "fmt"
diff --git a/.semgrep/adapter/package-import.yml b/.semgrep/adapter/package-import.yml
new file mode 100644
index 00000000000..20de2d107da
--- /dev/null
+++ b/.semgrep/adapter/package-import.yml
@@ -0,0 +1,13 @@
+rules:
+ - id: package-import-check
+ message: Usage of "$PKG" package is prohibited in adapter code
+ languages:
+ - go
+ severity: ERROR
+ pattern-either:
+ - patterns:
+ - pattern: import "$PKG"
+ - focus-metavariable: $PKG
+ - metavariable-regex:
+ metavariable: $PKG
+ regex: (^github\.com/mitchellh/copystructure(/.*)?$|^github\.com/golang/glog(/.*)?$)
\ No newline at end of file
diff --git a/.semgrep/adapter/type-bid-assignment.go b/.semgrep/adapter/type-bid-assignment.go
new file mode 100644
index 00000000000..fb29a4912d5
--- /dev/null
+++ b/.semgrep/adapter/type-bid-assignment.go
@@ -0,0 +1,181 @@
+/*
+ type-bid-assignment tests
+ https://semgrep.dev/docs/writing-rules/testing-rules
+ "ruleid" prefix in comment indicates patterns that should be flagged by semgrep
+ "ok" prefix in comment indidcates patterns that should not be flagged by the semgrep
+*/
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var bidResp openrtb2.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ for _, seatBid := range bidResp.SeatBid {
+ for _, sb := range seatBid.Bid {
+ bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp)
+ if err != nil {
+ errs = append(errs, err)
+ } else {
+ b := &adapters.TypedBid{
+ // ruleid: type-bid-assignment-check
+ Bid: &sb,
+ BidType: bidType,
+ }
+ bidResponse.Bids = append(bidResponse.Bids, b)
+ }
+ }
+ }
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var bidResp openrtb2.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ for _, seatBid := range bidResp.SeatBid {
+ for _, sb := range seatBid.Bid {
+ sbcopy := sb
+ bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp)
+ if err != nil {
+ errs = append(errs, err)
+ } else {
+ b := &adapters.TypedBid{
+ // ok: type-bid-assignment-check
+ Bid: &sbcopy,
+ BidType: bidType,
+ }
+ bidResponse.Bids = append(bidResponse.Bids, b)
+ }
+ }
+ }
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var bidResp openrtb2.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ for _, seatBid := range bidResp.SeatBid {
+ for _, sb := range seatBid.Bid {
+ bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp)
+ if err != nil {
+ return nil, err
+ }
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{
+ // ruleid: type-bid-assignment-check
+ Bid: &sb,
+ BidType: bidType,
+ })
+
+ }
+ }
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var bidResp openrtb2.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ for _, seatBid := range bidResp.SeatBid {
+ for _, sb := range seatBid.Bid {
+ bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp)
+ if err != nil {
+ return nil, err
+ }
+ // ruleid: type-bid-assignment-check
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &sb, BidType: bidType})
+ }
+ }
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var bidResp openrtb2.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ var errors []error
+ for _, seatBid := range bidResp.SeatBid {
+ for _, bid := range seatBid.Bid {
+ var t adapters.TypedBid
+ // ruleid: type-bid-assignment-check
+ t.Bid = &bid
+ bidResponse.Bids = append(bidResponse.Bids, &t)
+ }
+ }
+ return bidResponse, errors
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var bidResp openrtb2.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ var errors []error
+ for _, seatBid := range bidResp.SeatBid {
+ for _, bid := range seatBid.Bid {
+ var t adapters.TypedBid
+ t = adapters.TypedBid{
+ // ruleid: type-bid-assignment-check
+ Bid: &bid,
+ }
+
+ bidResponse.Bids = append(bidResponse.Bids, &t)
+ }
+ }
+ return bidResponse, errors
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var bidResp openrtb2.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ for _, seatBid := range bidResp.SeatBid {
+ for idx, _ := range seatBid.Bid {
+ bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp)
+ if err != nil {
+ errs = append(errs, err)
+ } else {
+ b := &adapters.TypedBid{
+ // ok: type-bid-assignment-check
+ Bid: &seatBid.Bid[idx],
+ BidType: bidType,
+ }
+ bidResponse.Bids = append(bidResponse.Bids, b)
+ }
+ }
+ }
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var bidResp openrtb2.BidResponse
+
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{err}
+ }
+
+ for _, seatBid := range bidResp.SeatBid {
+ for idx := range seatBid.Bid {
+ bidType, err := getMediaTypeForImp(seatBid.Bid[i], internalRequest.Imp)
+ if err != nil {
+ return nil, err
+ }
+ // ok: type-bid-assignment-check
+ bidResponse.Bids = append(bidResponse.Bids, &adapters.TypedBid{Bid: &seatBid.Bid[idx], BidType: bidType})
+ }
+ }
+}
diff --git a/.semgrep/adapter/type-bid-assignment.yml b/.semgrep/adapter/type-bid-assignment.yml
new file mode 100644
index 00000000000..95fb6811227
--- /dev/null
+++ b/.semgrep/adapter/type-bid-assignment.yml
@@ -0,0 +1,64 @@
+rules:
+ - id: type-bid-assignment-check
+ languages:
+ - go
+ message: >
+ Found incorrect assignment made to $KEY. $BID variable receives a new value in each iteration of range loop. Assigning the address of $BID `(&$BID)` to $KEY will result in a pointer that always points to the same memory address with the value of the last iteration.
+ This can lead to unexpected behavior or incorrect results. Refer https://go.dev/play/p/9ZS1f-5h4qS
+
+ Consider using an index variable in the seatBids.Bid loop as shown below
+
+ ```
+ for _, seatBid := range response.SeatBid {
+ for i := range seatBids.Bid {
+ ...
+ responseBid := &adapters.TypedBid{
+ Bid: &seatBids.Bid[i],
+ ...
+ }
+ ...
+ ...
+ }
+ }
+ ```
+ severity: ERROR
+ patterns:
+ - pattern-either:
+ - pattern: >
+ for _, $BID := range ... {
+ ...
+ ... := &adapters.TypedBid{
+ $KEY: &$BID,
+ ...
+ }
+ ...
+ }
+ - pattern: >
+ for _, $BID := range ... {
+ ...
+ ... = adapters.TypedBid{
+ $KEY: &$BID,
+ ...
+ }
+ ...
+ }
+ - pattern: >
+ for _, $BID := range ... {
+ ...
+ ... = append(..., &adapters.TypedBid{
+ $KEY: &$BID,
+ ...
+ })
+ ...
+ }
+ - pattern: >
+ for _, $BID := range ... {
+ var $TYPEBID_OBJ adapters.TypedBid
+ ...
+ $TYPEBID_OBJ.$KEY = &$BID
+ ...
+ }
+ - focus-metavariable: $KEY
+ - metavariable-regex:
+ metavariable: $KEY
+ regex: Bid
diff --git a/README.md b/README.md
index 4ec6792c0c8..95e0262b46f 100644
--- a/README.md
+++ b/README.md
@@ -5,17 +5,17 @@
# Prebid Server
Prebid Server is an open source implementation of Server-Side Header Bidding.
-It is managed by [Prebid.org](http://prebid.org/overview/what-is-prebid-org.html),
-and upholds the principles from the [Prebid Code of Conduct](http://prebid.org/wrapper_code_of_conduct.html).
+It is managed by [Prebid.org](https://prebid.org/about/),
+and upholds the principles from the [Prebid Code of Conduct](https://prebid.org/code-of-conduct/).
This project does not support the same set of Bidders as Prebid.js, although there is overlap.
The current set can be found in the [adapters](./adapters) package. If you don't see the one you want, feel free to [contribute it](https://docs.prebid.org/prebid-server/developers/add-new-bidder-go.html).
For more information, see:
-- [What is Prebid?](https://prebid.org/overview/intro.html)
+- [What is Prebid?](https://docs.prebid.org/overview/intro.html)
- [Prebid Server Overview](https://docs.prebid.org/prebid-server/overview/prebid-server-overview.html)
-- [Current Bidders](http://prebid.org/dev-docs/pbs-bidders.html)
+- [Current Bidders](https://docs.prebid.org/dev-docs/pbs-bidders.html)
Please consider [registering your Prebid Server](https://docs.prebid.org/prebid-server/hosting/pbs-hosting.html#optional-registration) to get on the mailing list for updates, etc.
diff --git a/adapters/aidem/aidem.go b/adapters/aidem/aidem.go
new file mode 100644
index 00000000000..9748f32c957
--- /dev/null
+++ b/adapters/aidem/aidem.go
@@ -0,0 +1,102 @@
+package aidem
+
+import (
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/prebid/openrtb/v19/openrtb2"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+type adapter struct {
+ endpoint string
+}
+
+func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
+
+ reqJson, err := json.Marshal(request)
+ if err != nil {
+ return nil, []error{err}
+ }
+
+ headers := http.Header{}
+ headers.Add("Content-Type", "application/json;charset=utf-8")
+
+ return []*adapters.RequestData{{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: reqJson,
+ Headers: headers,
+ }}, nil
+}
+
+func (a *adapter) MakeBids(internalRequest *openrtb2.BidRequest, externalRequest *adapters.RequestData, response *adapters.ResponseData) (*adapters.BidderResponse, []error) {
+ var errs []error
+
+ if adapters.IsResponseStatusCodeNoContent(response) {
+ return nil, nil
+ }
+
+ if err := adapters.CheckResponseStatusCodeForErrors(response); err != nil {
+ return nil, []error{&errortypes.BadInput{
+ Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+ }}
+ }
+
+ var bidResp openrtb2.BidResponse
+ if err := json.Unmarshal(response.Body, &bidResp); err != nil {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: fmt.Sprintf("JSON parsing error: %v", err),
+ }}
+ }
+
+ if len(bidResp.SeatBid) == 0 {
+ return nil, []error{&errortypes.BadServerResponse{
+ Message: "Empty SeatBid array",
+ }}
+ }
+
+ bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(bidResp.SeatBid[0].Bid))
+
+ for _, seatBid := range bidResp.SeatBid {
+ for i := range seatBid.Bid {
+ bidType, err := getMediaTypeForBid(seatBid.Bid[i])
+ if err != nil {
+ errs = append(errs, err)
+ } else {
+ b := &adapters.TypedBid{
+ Bid: &seatBid.Bid[i],
+ BidType: bidType,
+ }
+ bidResponse.Bids = append(bidResponse.Bids, b)
+ }
+ }
+ }
+ return bidResponse, errs
+}
+
+// Builder builds a new instance of the AIDEM adapter for the given bidder with the given config.
+func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
+ return &adapter{
+ endpoint: config.Endpoint,
+ }, nil
+}
+
+func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
+ switch bid.MType {
+ case openrtb2.MarkupBanner:
+ return openrtb_ext.BidTypeBanner, nil
+ case openrtb2.MarkupVideo:
+ return openrtb_ext.BidTypeVideo, nil
+ case openrtb2.MarkupAudio:
+ return openrtb_ext.BidTypeAudio, nil
+ case openrtb2.MarkupNative:
+ return openrtb_ext.BidTypeNative, nil
+ default:
+ return "", fmt.Errorf("Unable to fetch mediaType in multi-format: %s", bid.ImpID)
+ }
+}
diff --git a/adapters/aidem/aidem_test.go b/adapters/aidem/aidem_test.go
new file mode 100644
index 00000000000..03bcc7e0fb5
--- /dev/null
+++ b/adapters/aidem/aidem_test.go
@@ -0,0 +1,30 @@
+package aidem
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+ "github.com/prebid/prebid-server/adapters/adapterstest"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+func TestJsonSamples(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderAidem, config.Adapter{
+ Endpoint: "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ }, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ if buildErr != nil {
+ t.Fatalf("Builder returned unexpected error %v", buildErr)
+ }
+
+ adapterstest.RunJSONBidderTest(t, "aidemtest", bidder)
+}
+
+func TestEndpointTemplateMalformed(t *testing.T) {
+ _, buildErr := Builder(openrtb_ext.BidderAidem, config.Adapter{
+ Endpoint: "{{Malformed}}"}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+
+ assert.Nil(t, buildErr)
+}
diff --git a/adapters/aidem/aidemtest/exemplary/multi-format.json b/adapters/aidem/aidemtest/exemplary/multi-format.json
new file mode 100644
index 00000000000..0c940d4ba59
--- /dev/null
+++ b/adapters/aidem/aidemtest/exemplary/multi-format.json
@@ -0,0 +1,119 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 320,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 320,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "aax",
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 1.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "h": 50,
+ "w": 320,
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 1.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "w": 320,
+ "h": 50,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/aidem/aidemtest/exemplary/multi-imps-multi-bid.json b/adapters/aidem/aidemtest/exemplary/multi-imps-multi-bid.json
new file mode 100644
index 00000000000..5c2b36948f7
--- /dev/null
+++ b/adapters/aidem/aidemtest/exemplary/multi-imps-multi-bid.json
@@ -0,0 +1,154 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ },
+ {
+ "id": "2",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ },
+ {
+ "id": "2",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "aax",
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 1.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "h": 50,
+ "w": 320,
+ "mtype": 1
+ },
+ {
+ "id": "test-bid-id-2",
+ "impid": "2",
+ "price": 1.10,
+ "adm": "some-test-ad-2",
+ "crid": "test-crid-2",
+ "h": 50,
+ "w": 300,
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 1.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "w": 320,
+ "h": 50,
+ "mtype": 1
+ },
+ "type": "banner"
+ },
+ {
+ "bid": {
+ "id": "test-bid-id-2",
+ "impid": "2",
+ "price": 1.10,
+ "adm": "some-test-ad-2",
+ "crid": "test-crid-2",
+ "w": 300,
+ "h": 50,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/aidem/aidemtest/exemplary/multi-imps-single-bid.json b/adapters/aidem/aidemtest/exemplary/multi-imps-single-bid.json
new file mode 100644
index 00000000000..9eae7101a90
--- /dev/null
+++ b/adapters/aidem/aidemtest/exemplary/multi-imps-single-bid.json
@@ -0,0 +1,131 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ },
+ {
+ "id": "2",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ },
+ {
+ "id": "2",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "aax",
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 1.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "h": 50,
+ "w": 320,
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 1.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "w": 320,
+ "h": 50,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/aidem/aidemtest/exemplary/no-bid.json b/adapters/aidem/aidemtest/exemplary/no-bid.json
new file mode 100644
index 00000000000..7418425f10b
--- /dev/null
+++ b/adapters/aidem/aidemtest/exemplary/no-bid.json
@@ -0,0 +1,58 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/aidem/aidemtest/exemplary/optional-params.json b/adapters/aidem/aidemtest/exemplary/optional-params.json
new file mode 100644
index 00000000000..69511fb595c
--- /dev/null
+++ b/adapters/aidem/aidemtest/exemplary/optional-params.json
@@ -0,0 +1,60 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234",
+ "placementId": "ext"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234",
+ "placementId": "ext"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
diff --git a/adapters/aidem/aidemtest/exemplary/simple-banner.json b/adapters/aidem/aidemtest/exemplary/simple-banner.json
new file mode 100644
index 00000000000..73db297ee42
--- /dev/null
+++ b/adapters/aidem/aidemtest/exemplary/simple-banner.json
@@ -0,0 +1,97 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "banner": {
+ "format": [
+ {
+ "w": 320,
+ "h": 50
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "aax",
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 1.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "h": 50,
+ "w": 320,
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 1.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "w": 320,
+ "h": 50,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/aidem/aidemtest/exemplary/simple-video.json b/adapters/aidem/aidemtest/exemplary/simple-video.json
new file mode 100644
index 00000000000..0daaffaa8cf
--- /dev/null
+++ b/adapters/aidem/aidemtest/exemplary/simple-video.json
@@ -0,0 +1,103 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 320,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "1",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "protocols": [
+ 2,
+ 5
+ ],
+ "w": 320,
+ "h": 480
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "seat": "aax",
+ "bid": [
+ {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 2.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "w": 320,
+ "h": 480,
+ "mtype": 2
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-bid-id",
+ "impid": "1",
+ "price": 2.50,
+ "adm": "some-test-ad",
+ "crid": "test-crid",
+ "w": 320,
+ "h": 480,
+ "mtype": 2
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/aidem/aidemtest/supplemental/invalid-req-400-status-code-bad-request.json b/adapters/aidem/aidemtest/supplemental/invalid-req-400-status-code-bad-request.json
new file mode 100644
index 00000000000..3dea13ef7c9
--- /dev/null
+++ b/adapters/aidem/aidemtest/supplemental/invalid-req-400-status-code-bad-request.json
@@ -0,0 +1,97 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ],
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 400
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unexpected status code: 400. Run with request.debug = 1 for more info",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aidem/aidemtest/supplemental/invalid-res-200-status-code-empty-bids.json b/adapters/aidem/aidemtest/supplemental/invalid-res-200-status-code-empty-bids.json
new file mode 100644
index 00000000000..dd64125f467
--- /dev/null
+++ b/adapters/aidem/aidemtest/supplemental/invalid-res-200-status-code-empty-bids.json
@@ -0,0 +1,102 @@
+{
+ "mockBidRequest": {
+ "id": "some-request-id",
+ "tmax": 1000,
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "some-request-id",
+ "imp": [
+ {
+ "id": "some-impression-id",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ],
+ "app": {
+ "publisher": {
+ "id": "123456789"
+ },
+ "cat": [
+ "IAB22-1"
+ ],
+ "bundle": "com.app.awesome",
+ "name": "Awesome App",
+ "domain": "awesomeapp.com",
+ "id": "123456789"
+ },
+ "user": {
+ "buyeruid": "0000-000-000-0000"
+ },
+ "tmax": 1000
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "tid",
+ "seatbid": [],
+ "bidid": "bid01"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Empty SeatBid array",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aidem/aidemtest/supplemental/invalid-resp-multi-imp-type.json b/adapters/aidem/aidemtest/supplemental/invalid-resp-multi-imp-type.json
new file mode 100644
index 00000000000..95f97d31f72
--- /dev/null
+++ b/adapters/aidem/aidemtest/supplemental/invalid-resp-multi-imp-type.json
@@ -0,0 +1,151 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "native": {
+ "ver": "1.1",
+ "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.example.com",
+ "page": "http://www.example.com",
+ "publisher": {
+ "domain": "example.com"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "userAgent",
+ "ip": "193.168.244.1"
+ },
+ "at": 1,
+ "tmax": 5000,
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "w": 640,
+ "h": 480,
+ "minduration": 120,
+ "maxduration": 150
+ },
+ "native": {
+ "ver": "1.1",
+ "request": "{\"adunit\":2,\"assets\":[{\"id\":3,\"img\":{\"h\":120,\"hmin\":0,\"type\":3,\"w\":180,\"wmin\":0},\"required\":1},{\"id\":0,\"required\":1,\"title\":{\"len\":25}},{\"data\":{\"len\":25,\"type\":1},\"id\":4,\"required\":1},{\"data\":{\"len\":140,\"type\":2},\"id\":6,\"required\":1}],\"context\":1,\"layout\":1,\"contextsubtype\":11,\"plcmtcnt\":1,\"plcmttype\":2,\"ver\":\"1.1\",\"ext\":{\"banner\":{\"w\":320,\"h\":50}}}"
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.example.com",
+ "page": "http://www.example.com",
+ "publisher": {
+ "domain": "example.com"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "userAgent",
+ "ip": "193.168.244.1"
+ },
+ "at": 1,
+ "tmax": 5000,
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "tid",
+ "seatbid": [
+ {
+ "seat": "aax",
+ "bid": [
+ {
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300
+ }
+ ]
+ }
+ ],
+ "bidid": "bid01"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [{"currency":"USD","bids":[]}],
+ "expectedMakeBidsErrors": [
+ {
+ "value": "Unable to fetch mediaType in multi-format: test-imp-id",
+ "comparison": "literal"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aidem/aidemtest/supplemental/valid-req-200-bid-response-from-aidem.json b/adapters/aidem/aidemtest/supplemental/valid-req-200-bid-response-from-aidem.json
new file mode 100644
index 00000000000..cbd534cd91c
--- /dev/null
+++ b/adapters/aidem/aidemtest/supplemental/valid-req-200-bid-response-from-aidem.json
@@ -0,0 +1,141 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.example.com",
+ "page": "http://www.example.com",
+ "publisher": {
+ "domain": "example.com"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "userAgent",
+ "ip": "193.168.244.1"
+ },
+ "at": 1,
+ "tmax": 5000,
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ }
+ }
+ ],
+ "site": {
+ "domain": "www.example.com",
+ "page": "http://www.example.com",
+ "publisher": {
+ "domain": "example.com"
+ },
+ "ext": {
+ "amp": 0
+ }
+ },
+ "device": {
+ "ua": "userAgent",
+ "ip": "193.168.244.1"
+ },
+ "at": 1,
+ "tmax": 5000,
+ "cur": [
+ "USD"
+ ],
+ "regs": {
+ "ext": {
+ "gdpr": 0
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "tid",
+ "seatbid": [
+ {
+ "seat": "aax",
+ "bid": [
+ {
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300,
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "bidid": "bid01"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300,
+ "mtype": 1
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/aidem/aidemtest/supplemental/valid-req-204-response-from-aidem.json b/adapters/aidem/aidemtest/supplemental/valid-req-204-response-from-aidem.json
new file mode 100644
index 00000000000..ddd4e5a7735
--- /dev/null
+++ b/adapters/aidem/aidemtest/supplemental/valid-req-204-response-from-aidem.json
@@ -0,0 +1,66 @@
+{
+ "mockBidRequest": {
+ "app": {
+ "bundle": "com.example.app"
+ },
+ "id": "req-id",
+ "device": {
+ "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+ "ip": "1.1.1.1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+ },
+ "imp": [
+ {
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ },
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "id": "imp-id"
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://fakezero.aidemsrv.com/ortb/v2.6/bid/request",
+ "body": {
+ "app": {
+ "bundle": "com.example.app"
+ },
+ "id": "req-id",
+ "device": {
+ "ifa": "9d8fe0a9-c0dd-4482-b16b-5709b00c608d",
+ "ip": "1.1.1.1",
+ "ua": "Mozilla/5.0 (Linux; Android 8.0.0; SM-G960F Build/R16NW) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.84 Mobile Safari/537.36"
+ },
+ "imp": [
+ {
+ "ext": {
+ "bidder": {
+ "siteId": "TCID",
+ "publisherId": "1234"
+ }
+ },
+ "banner": {
+ "w": 320,
+ "h": 50
+ },
+ "id": "imp-id"
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 204,
+ "body": {}
+ }
+ }
+ ],
+ "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/adapters/aidem/params_test.go b/adapters/aidem/params_test.go
new file mode 100644
index 00000000000..36190c0bc9f
--- /dev/null
+++ b/adapters/aidem/params_test.go
@@ -0,0 +1,57 @@
+package aidem
+
+import (
+ "encoding/json"
+ "testing"
+
+ "github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// This file actually intends to test static/bidder-params/aidem.json TODO: MUST BE CREATED
+func TestValidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, validParam := range validParams {
+ if err := validator.Validate(openrtb_ext.BidderAidem, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected aidem params: %s", validParam)
+ }
+ }
+}
+
+func TestInvalidParams(t *testing.T) {
+ validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
+ if err != nil {
+ t.Fatalf("Failed to fetch the json-schemas. %v", err)
+ }
+
+ for _, invalidParam := range invalidParams {
+ if err := validator.Validate(openrtb_ext.BidderAidem, json.RawMessage(invalidParam)); err == nil {
+ t.Errorf("Schema allowed unexpected params: %s", invalidParam)
+ }
+ }
+}
+
+var validParams = []string{
+ `{"siteId":"123", "publisherId":"1234"}`,
+ `{"siteId":"123", "publisherId":"1234", "placementId":"12345"}`,
+ `{"siteId":"123", "publisherId":"1234", "rateLimit":1}`,
+ `{"siteId":"123", "publisherId":"1234", "placementId":"12345", "rateLimit":1}`,
+}
+
+var invalidParams = []string{
+ ``,
+ `null`,
+ `true`,
+ `5`,
+ `4.2`,
+ `[]`,
+ `{}`,
+ `{"siteId":"", "publisherId":""}`,
+ `{"siteId":"only siteId is present"}`,
+ `{"publisherId":"only publisherId is present"}`,
+ `{"ssiteId":"123","ppublisherId":"123"}`,
+ `{"aid":123, "placementId":"123", "siteId":"321"}`,
+}
diff --git a/adapters/audienceNetwork/facebook.go b/adapters/audienceNetwork/facebook.go
index 753b8a2cf70..42192817f69 100644
--- a/adapters/audienceNetwork/facebook.go
+++ b/adapters/audienceNetwork/facebook.go
@@ -41,6 +41,11 @@ type facebookReqExt struct {
AuthID string `json:"authentication_id"`
}
+type ExtImpFB struct {
+ AppSecret string `json:"app_secret"`
+ PlatformID string `json:"platform_id"`
+}
+
func (this *FacebookAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
if len(request.Imp) == 0 {
return nil, []error{&errortypes.BadInput{
@@ -116,8 +121,8 @@ func (this *FacebookAdapter) buildRequests(request *openrtb2.BidRequest) ([]*ada
// The authentication ID is a sha256 hmac hash encoded as a hex string, based on
// the app secret and the ID of the bid request
-func (this *FacebookAdapter) makeAuthID(req *openrtb2.BidRequest) string {
- h := hmac.New(sha256.New, []byte(this.appSecret))
+func (this *FacebookAdapter) makeAuthID(req *openrtb2.BidRequest, appSecret string) string {
+ h := hmac.New(sha256.New, []byte(appSecret))
h.Write([]byte(req.ID))
return hex.EncodeToString(h.Sum(nil))
@@ -139,9 +144,23 @@ func (this *FacebookAdapter) modifyRequest(out *openrtb2.BidRequest) error {
// ID *BEFORE* we generate the auth ID since its a hash based on the request ID
out.ID = imp.ID
+ platformId := this.platformID
+ appSecret := this.appSecret
+
+ var bidderExt adapters.ExtImpBidder
+ err = json.Unmarshal(imp.Ext, &bidderExt)
+ if err == nil {
+ var impressionExt ExtImpFB
+ err = json.Unmarshal(bidderExt.Bidder, &impressionExt)
+ if err == nil && len(impressionExt.PlatformID) > 0 && len(impressionExt.AppSecret) > 0 {
+ platformId = impressionExt.PlatformID
+ appSecret = impressionExt.AppSecret
+ }
+ }
+
reqExt := facebookReqExt{
- PlatformID: this.platformID,
- AuthID: this.makeAuthID(out),
+ PlatformID: platformId,
+ AuthID: this.makeAuthID(out, appSecret),
}
if out.Ext, err = json.Marshal(reqExt); err != nil {
@@ -369,6 +388,18 @@ func (this *FacebookAdapter) MakeBids(request *openrtb2.BidRequest, adapterReque
continue
}
+ // TODO: move to msp repo and apply for all bidders
+ var fbreq openrtb2.BidRequest
+ err := json.Unmarshal(adapterRequest.Body, &fbreq)
+ if err == nil {
+ if len(fbreq.Imp) > 0 && fbreq.Imp[0].BidFloor > bid.Price {
+ errs = append(errs, &errortypes.BadServerResponse{
+ Message: fmt.Sprintf("bid price %f less than floor %f", bid.Price, fbreq.Imp[0].BidFloor),
+ })
+ continue
+ }
+ }
+
bid.AdID = obj.BidID
bid.CrID = obj.BidID
@@ -454,7 +485,13 @@ func (fa *FacebookAdapter) MakeTimeoutNotification(req *adapters.RequestData) (*
}
}
- uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", fa.platformID, pubID, rID)
+ platformId := fa.platformID
+ requestPlatformId, err := jsonparser.GetString(req.Body, "ext", "platformid")
+ if err == nil {
+ platformId = requestPlatformId
+ }
+
+ uri := fmt.Sprintf("https://www.facebook.com/audiencenetwork/nurl/?partner=%s&app=%s&auction=%s&ortb_loss_code=2", platformId, pubID, rID)
timeoutReq := adapters.RequestData{
Method: "GET",
Uri: uri,
diff --git a/adapters/consumable/consumable.go b/adapters/consumable/consumable.go
index 9969ea3796e..0e16497774c 100644
--- a/adapters/consumable/consumable.go
+++ b/adapters/consumable/consumable.go
@@ -40,6 +40,8 @@ type bidRequest struct {
Coppa bool `json:"coppa,omitempty"`
SChain openrtb2.SupplyChain `json:"schain"`
Content *openrtb2.Content `json:"content,omitempty"`
+ GPP string `json:"gpp,omitempty"`
+ GPPSID []int8 `json:"gpp_sid,omitempty"`
}
type placement struct {
@@ -192,6 +194,14 @@ func (a *ConsumableAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *
body.Coppa = request.Regs != nil && request.Regs.COPPA > 0
+ if request.Regs != nil && request.Regs.GPP != "" {
+ body.GPP = request.Regs.GPP
+ }
+
+ if request.Regs != nil && request.Regs.GPPSID != nil {
+ body.GPPSID = request.Regs.GPPSID
+ }
+
if request.Site != nil && request.Site.Content != nil {
body.Content = request.Site.Content
} else if request.App != nil && request.App.Content != nil {
diff --git a/adapters/consumable/consumable/supplemental/simple-banner-gpp.json b/adapters/consumable/consumable/supplemental/simple-banner-gpp.json
new file mode 100644
index 00000000000..9b2e3d75031
--- /dev/null
+++ b/adapters/consumable/consumable/supplemental/simple-banner-gpp.json
@@ -0,0 +1,130 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{"w": 728, "h": 250}]
+ },
+ "ext": {
+ "bidder": {
+ "networkId": 11,
+ "siteId": 32,
+ "unitId": 42
+ }
+ }
+ }
+ ],
+ "device": {
+ "ua": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36",
+ "ip": "123.123.123.123"
+ },
+ "site": {
+ "domain": "www.some.com",
+ "page": "http://www.some.com/page-where-ad-will-be-shown"
+ },
+ "regs": {
+ "gpp": "gppString",
+ "gpp_sid": [7]
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "https://e.serverbid.com/api/v2",
+ "headers": {
+ "Accept": [
+ "application/json"
+ ],
+ "Content-Type": [
+ "application/json"
+ ],
+ "User-Agent": [
+ "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.91 Safari/537.36"
+ ],
+ "X-Forwarded-For": [
+ "123.123.123.123"
+ ],
+ "Forwarded": [
+ "for=123.123.123.123"
+ ],
+ "Origin": [
+ "http://www.some.com"
+ ],
+ "Referer": [
+ "http://www.some.com/page-where-ad-will-be-shown"
+ ]
+ },
+ "body": {
+ "placements": [
+ {
+ "adTypes": [2730],
+ "divName": "test-imp-id",
+ "networkId": 11,
+ "siteId": 32,
+ "unitId": 42
+ }
+ ],
+ "schain": {
+ "complete": 0,
+ "nodes": null,
+ "ver": ""
+ },
+ "networkId": 11,
+ "siteId": 32,
+ "unitId": 42,
+ "time": 1451651415,
+ "url": "http://www.some.com/page-where-ad-will-be-shown",
+ "includePricingData": true,
+ "user":{},
+ "enableBotFiltering": true,
+ "parallel": true,
+ "gpp": "gppString",
+ "gpp_sid": [7]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "decisions": {
+ "test-imp-id": {
+ "adId": 1234567890,
+ "pricing": {
+ "clearPrice": 0.5
+ },
+ "width": 728,
+ "height": 250,
+ "impressionUrl": "http://localhost:8080/shown",
+ "contents" : [
+ {
+ "body": ""
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "test-request-id",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "",
+ "crid": "1234567890",
+ "exp": 30,
+ "w": 728,
+ "h": 250
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/improvedigital/improvedigital.go b/adapters/improvedigital/improvedigital.go
index 4c64451f247..b934ac753a0 100644
--- a/adapters/improvedigital/improvedigital.go
+++ b/adapters/improvedigital/improvedigital.go
@@ -140,6 +140,7 @@ func (a *ImprovedigitalAdapter) MakeBids(internalRequest *openrtb2.BidRequest, e
}
bidResponse := adapters.NewBidderResponseWithBidsCapacity(len(seatBid.Bid))
+ bidResponse.Currency = bidResp.Cur
for i := range seatBid.Bid {
bid := seatBid.Bid[i]
diff --git a/adapters/improvedigital/improvedigitaltest/supplemental/foreign-currency.json b/adapters/improvedigital/improvedigitaltest/supplemental/foreign-currency.json
new file mode 100644
index 00000000000..633e8b6b0aa
--- /dev/null
+++ b/adapters/improvedigital/improvedigitaltest/supplemental/foreign-currency.json
@@ -0,0 +1,89 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "cur": ["EUR"],
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "placementId": 13245
+ }
+ }
+ }]
+ },
+
+ "httpCalls": [{
+ "expectedRequest": {
+ "uri": "http://localhost/pbs",
+ "body": {
+ "id": "test-request-id",
+ "cur": ["EUR"],
+ "site": {
+ "page": "https://good.site/url"
+ },
+ "imp": [{
+ "id": "test-imp-id",
+ "banner": {
+ "format": [{
+ "w": 300,
+ "h": 250
+ }]
+ },
+ "ext": {
+ "bidder": {
+ "placementId": 13245
+ }
+ }
+ }]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "cur": "EUR",
+ "seatbid": [{
+ "seat": "improvedigital",
+ "bid": [{
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "12345678",
+ "adm": "some-test-ad",
+ "cid": "987",
+ "crid": "12345678",
+ "h": 250,
+ "w": 300
+ }]
+ }]
+ }
+ }
+ }],
+
+ "expectedBidResponses": [{
+ "currency": "EUR",
+ "bids": [{
+ "bid": {
+ "id": "randomid",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "12345678",
+ "cid": "987",
+ "crid": "12345678",
+ "w": 300,
+ "h": 250
+ },
+ "type": "banner"
+ }]
+ }]
+}
diff --git a/adapters/ix/ix.go b/adapters/ix/ix.go
index b17d913f42d..796fefbbff0 100644
--- a/adapters/ix/ix.go
+++ b/adapters/ix/ix.go
@@ -19,8 +19,7 @@ import (
)
type IxAdapter struct {
- URI string
- maxRequests int
+ URI string
}
type ExtRequest struct {
@@ -30,108 +29,127 @@ type ExtRequest struct {
}
type IxDiag struct {
- PbsV string `json:"pbsv,omitempty"`
- PbjsV string `json:"pbjsv,omitempty"`
+ PbsV string `json:"pbsv,omitempty"`
+ PbjsV string `json:"pbjsv,omitempty"`
+ MultipleSiteIds string `json:"multipleSiteIds,omitempty"`
}
func (a *IxAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
- nImp := len(request.Imp)
- if nImp > a.maxRequests {
- request.Imp = request.Imp[:a.maxRequests]
- nImp = a.maxRequests
- }
-
+ requests := make([]*adapters.RequestData, 0, len(request.Imp))
errs := make([]error, 0)
- if err := BuildIxDiag(request); err != nil {
- errs = append(errs, err)
- }
-
- // Multi-size banner imps are split into single-size requests.
- // The first size imp requests are added to the first slice.
- // Additional size requests are added to the second slice and are merged with the first at the end.
- // Preallocate the max possible size to avoid reallocating arrays.
- requests := make([]*adapters.RequestData, 0, a.maxRequests)
- multiSizeRequests := make([]*adapters.RequestData, 0, a.maxRequests-nImp)
-
headers := http.Header{
"Content-Type": {"application/json;charset=utf-8"},
"Accept": {"application/json"}}
- imps := request.Imp
- for iImp := range imps {
- request.Imp = imps[iImp : iImp+1]
- if request.Site != nil {
- if err := setSitePublisherId(request, iImp); err != nil {
- errs = append(errs, err)
- continue
- }
+ uniqueSiteIDs := make(map[string]struct{})
+ filteredImps := make([]openrtb2.Imp, 0, len(request.Imp))
+ requestCopy := *request
+
+ ixDiag := &IxDiag{}
+
+ for _, imp := range requestCopy.Imp {
+ var err error
+ ixExt, err := unmarshalToIxExt(&imp)
+
+ if err != nil {
+ errs = append(errs, err)
+ continue
}
- if request.Imp[0].Banner != nil {
- banner := *request.Imp[0].Banner
- request.Imp[0].Banner = &banner
- formats := getBannerFormats(&banner)
- for iFmt := range formats {
- banner.Format = formats[iFmt : iFmt+1]
- banner.W = openrtb2.Int64Ptr(banner.Format[0].W)
- banner.H = openrtb2.Int64Ptr(banner.Format[0].H)
- if requestData, err := createRequestData(a, request, &headers); err == nil {
- if iFmt == 0 {
- requests = append(requests, requestData)
- } else {
- multiSizeRequests = append(multiSizeRequests, requestData)
- }
- } else {
- errs = append(errs, err)
- }
- if len(multiSizeRequests) == cap(multiSizeRequests) {
- break
- }
+ if err = parseSiteId(ixExt, uniqueSiteIDs); err != nil {
+ errs = append(errs, err)
+ continue
+ }
+
+ if err := moveSid(&imp, ixExt); err != nil {
+ errs = append(errs, err)
+ }
+
+ if imp.Banner != nil {
+ bannerCopy := *imp.Banner
+
+ if len(bannerCopy.Format) == 0 && bannerCopy.W != nil && bannerCopy.H != nil {
+ bannerCopy.Format = []openrtb2.Format{{W: *bannerCopy.W, H: *bannerCopy.H}}
}
- } else if requestData, err := createRequestData(a, request, &headers); err == nil {
+
+ if len(bannerCopy.Format) == 1 {
+ bannerCopy.W = openrtb2.Int64Ptr(bannerCopy.Format[0].W)
+ bannerCopy.H = openrtb2.Int64Ptr(bannerCopy.Format[0].H)
+ }
+ imp.Banner = &bannerCopy
+ }
+ filteredImps = append(filteredImps, imp)
+ }
+ requestCopy.Imp = filteredImps
+
+ setSitePublisherId(&requestCopy, uniqueSiteIDs, ixDiag)
+
+ err := setIxDiagIntoExtRequest(&requestCopy, ixDiag)
+ if err != nil {
+ errs = append(errs, err)
+ }
+
+ if len(requestCopy.Imp) != 0 {
+ if requestData, err := createRequestData(a, &requestCopy, &headers); err == nil {
requests = append(requests, requestData)
} else {
errs = append(errs, err)
}
}
- request.Imp = imps
- return append(requests, multiSizeRequests...), errs
+ return requests, errs
}
-func setSitePublisherId(request *openrtb2.BidRequest, iImp int) error {
- if iImp == 0 {
- // first impression - create a site and pub copy
- site := *request.Site
+func setSitePublisherId(requestCopy *openrtb2.BidRequest, uniqueSiteIDs map[string]struct{}, ixDiag *IxDiag) {
+ if requestCopy.Site != nil {
+ site := *requestCopy.Site
if site.Publisher == nil {
site.Publisher = &openrtb2.Publisher{}
} else {
publisher := *site.Publisher
site.Publisher = &publisher
}
- request.Site = &site
+
+ siteIDs := make([]string, 0, len(uniqueSiteIDs))
+ for key := range uniqueSiteIDs {
+ siteIDs = append(siteIDs, key)
+ }
+ if len(siteIDs) == 1 {
+ site.Publisher.ID = siteIDs[0]
+ }
+ if len(siteIDs) > 1 {
+ // Sorting siteIDs for predictable output as Go maps don't guarantee order
+ sort.Strings(siteIDs)
+ multipleSiteIDs := strings.Join(siteIDs, ", ")
+ ixDiag.MultipleSiteIds = multipleSiteIDs
+ }
+ requestCopy.Site = &site
}
+}
+func unmarshalToIxExt(imp *openrtb2.Imp) (*openrtb_ext.ExtImpIx, error) {
var bidderExt adapters.ExtImpBidder
- if err := json.Unmarshal(request.Imp[0].Ext, &bidderExt); err != nil {
- return err
+ if err := json.Unmarshal(imp.Ext, &bidderExt); err != nil {
+ return nil, err
}
var ixExt openrtb_ext.ExtImpIx
if err := json.Unmarshal(bidderExt.Bidder, &ixExt); err != nil {
- return err
+ return nil, err
}
- request.Site.Publisher.ID = ixExt.SiteId
- return nil
+ return &ixExt, nil
}
-func getBannerFormats(banner *openrtb2.Banner) []openrtb2.Format {
- if len(banner.Format) == 0 && banner.W != nil && banner.H != nil {
- banner.Format = []openrtb2.Format{{W: *banner.W, H: *banner.H}}
+func parseSiteId(ixExt *openrtb_ext.ExtImpIx, uniqueSiteIDs map[string]struct{}) error {
+ if ixExt == nil {
+ return fmt.Errorf("Nil Ix Ext")
+ }
+ if ixExt.SiteId != "" {
+ uniqueSiteIDs[ixExt.SiteId] = struct{}{}
}
- return banner.Format
+ return nil
}
func createRequestData(a *IxAdapter, request *openrtb2.BidRequest, headers *http.Header) (*adapters.RequestData, error) {
@@ -180,7 +198,8 @@ func (a *IxAdapter) MakeBids(internalRequest *openrtb2.BidRequest, externalReque
}
}
- bidderResponse := adapters.NewBidderResponseWithBidsCapacity(5)
+ // capacity 0 will make channel unbuffered
+ bidderResponse := adapters.NewBidderResponseWithBidsCapacity(0)
bidderResponse.Currency = bidResponse.Cur
var errs []error
@@ -275,8 +294,7 @@ func getMediaTypeForBid(bid openrtb2.Bid, impMediaTypeReq map[string]openrtb_ext
// Builder builds a new instance of the Ix adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
bidder := &IxAdapter{
- URI: config.Endpoint,
- maxRequests: 20,
+ URI: config.Endpoint,
}
return bidder, nil
}
@@ -328,29 +346,33 @@ func marshalJsonWithoutUnicode(v interface{}) (string, error) {
return strings.TrimSuffix(sb.String(), "\n"), nil
}
-func BuildIxDiag(request *openrtb2.BidRequest) error {
+func setIxDiagIntoExtRequest(request *openrtb2.BidRequest, ixDiag *IxDiag) error {
extRequest := &ExtRequest{}
if request.Ext != nil {
if err := json.Unmarshal(request.Ext, &extRequest); err != nil {
return err
}
}
- ixdiag := &IxDiag{}
if extRequest.Prebid != nil && extRequest.Prebid.Channel != nil {
- ixdiag.PbjsV = extRequest.Prebid.Channel.Version
+ ixDiag.PbjsV = extRequest.Prebid.Channel.Version
}
-
// Slice commit hash out of version
if strings.Contains(version.Ver, "-") {
- ixdiag.PbsV = version.Ver[:strings.Index(version.Ver, "-")]
+ ixDiag.PbsV = version.Ver[:strings.Index(version.Ver, "-")]
} else if version.Ver != "" {
- ixdiag.PbsV = version.Ver
+ ixDiag.PbsV = version.Ver
}
// Only set request.ext if ixDiag is not empty
- if *ixdiag != (IxDiag{}) {
- extRequest.IxDiag = ixdiag
+ if *ixDiag != (IxDiag{}) {
+ extRequest := &ExtRequest{}
+ if request.Ext != nil {
+ if err := json.Unmarshal(request.Ext, &extRequest); err != nil {
+ return err
+ }
+ }
+ extRequest.IxDiag = ixDiag
extRequestJson, err := json.Marshal(extRequest)
if err != nil {
return err
@@ -359,3 +381,24 @@ func BuildIxDiag(request *openrtb2.BidRequest) error {
}
return nil
}
+
+// moves sid from imp[].ext.bidder.sid to imp[].ext.sid
+func moveSid(imp *openrtb2.Imp, ixExt *openrtb_ext.ExtImpIx) error {
+ if ixExt == nil {
+ return fmt.Errorf("Nil Ix Ext")
+ }
+
+ if ixExt.Sid != "" {
+ var m map[string]interface{}
+ if err := json.Unmarshal(imp.Ext, &m); err != nil {
+ return err
+ }
+ m["sid"] = ixExt.Sid
+ ext, err := json.Marshal(m)
+ if err != nil {
+ return err
+ }
+ imp.Ext = ext
+ }
+ return nil
+}
diff --git a/adapters/ix/ix_test.go b/adapters/ix/ix_test.go
index 0f6b856dce4..a64e3d0c661 100644
--- a/adapters/ix/ix_test.go
+++ b/adapters/ix/ix_test.go
@@ -19,8 +19,6 @@ const endpoint string = "http://host/endpoint"
func TestJsonSamples(t *testing.T) {
if bidder, err := Builder(openrtb_ext.BidderIx, config.Adapter{Endpoint: endpoint}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"}); err == nil {
- ixBidder := bidder.(*IxAdapter)
- ixBidder.maxRequests = 2
adapterstest.RunJSONBidderTest(t, "ixtest", bidder)
} else {
t.Fatalf("Builder returned unexpected error %v", err)
@@ -44,7 +42,7 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) {
`{
"prebid": {},
"bidder": {
- "siteID": 123456
+ "siteID": "123456"
}
}`,
)},
@@ -106,7 +104,6 @@ func TestIxMakeBidsWithCategoryDuration(t *testing.T) {
func TestIxMakeRequestWithGppString(t *testing.T) {
bidder := &IxAdapter{}
- bidder.maxRequests = 2
testGppString := "DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN"
@@ -124,7 +121,7 @@ func TestIxMakeRequestWithGppString(t *testing.T) {
`{
"prebid": {},
"bidder": {
- "siteID": 123456
+ "siteId": "123456"
}
}`,
)},
@@ -256,7 +253,8 @@ func TestBuildIxDiag(t *testing.T) {
for _, test := range testCases {
t.Run(test.description, func(t *testing.T) {
version.Ver = test.pbsVersion
- err := BuildIxDiag(test.request)
+ ixDiag := &IxDiag{}
+ err := setIxDiagIntoExtRequest(test.request, ixDiag)
if test.expectError {
assert.NotNil(t, err)
} else {
diff --git a/adapters/ix/ixtest/exemplary/multi-imp-multi-size-requests.json b/adapters/ix/ixtest/exemplary/multi-imp-multi-size-requests.json
new file mode 100644
index 00000000000..4decdaf985d
--- /dev/null
+++ b/adapters/ix/ixtest/exemplary/multi-imp-multi-size-requests.json
@@ -0,0 +1,211 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-3",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://www.example.com/"
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-3",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }
+ ],
+ "w": 300,
+ "h": 600
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://www.example.com/",
+ "publisher": {
+ "id": "569749"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 600,
+ "w": 300,
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "29681110",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "w": 300,
+ "h": 600,
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/ix/ixtest/exemplary/max-requests.json b/adapters/ix/ixtest/exemplary/multi-imp-requests.json
similarity index 69%
rename from adapters/ix/ixtest/exemplary/max-requests.json
rename to adapters/ix/ixtest/exemplary/multi-imp-requests.json
index 46d9ec5d6b7..d8d00aeea69 100644
--- a/adapters/ix/ixtest/exemplary/max-requests.json
+++ b/adapters/ix/ixtest/exemplary/multi-imp-requests.json
@@ -9,10 +9,6 @@
{
"w": 300,
"h": 250
- },
- {
- "w": 300,
- "h": 600
}
]
},
@@ -62,6 +58,38 @@
"siteId": "569749"
}
}
+ },
+ {
+ "id": "test-imp-id-4",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-5",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
}
]
},
@@ -89,49 +117,7 @@
"siteId": "569749"
}
}
- }
- ]
- }
- },
- "mockResponse": {
- "status": 200,
- "body": {
- "id": "test-request-id",
- "seatbid": [
- {
- "seat": "958",
- "bid": [
- {
- "id": "7706636740145184841",
- "impid": "test-imp-id-1",
- "price": 0.5,
- "adid": "29681110",
- "adm": "some-test-ad",
- "adomain": [
- "https://advertiser.example.com"
- ],
- "cid": "958",
- "crid": "29681110",
- "h": 250,
- "w": 300,
- "ext": {
- "ix": {}
- }
- }
- ]
- }
- ],
- "bidid": "5778926625248726496",
- "cur": "USD"
- }
- }
- },
- {
- "expectedRequest": {
- "uri": "http://host/endpoint",
- "body": {
- "id": "test-request-id",
- "imp": [
+ },
{
"id": "test-imp-id-2",
"video": {
@@ -156,6 +142,60 @@
"siteId": "569749"
}
}
+ },
+ {
+ "banner": {
+ "format": [
+ {
+ "h": 600,
+ "w": 300
+ }
+ ],
+ "h": 600,
+ "w": 300
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ },
+ "id": "test-imp-id-3"
+ },
+ {
+ "id": "test-imp-id-4",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-5",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
}
]
}
@@ -170,7 +210,7 @@
"bid": [
{
"id": "7706636740145184841",
- "impid": "test-imp-id-2",
+ "impid": "test-imp-id-1",
"price": 0.5,
"adid": "29681110",
"adm": "some-test-ad",
@@ -181,9 +221,6 @@
"crid": "29681110",
"h": 250,
"w": 300,
- "cat": [
- "IAB9-1"
- ],
"ext": {
"ix": {}
}
@@ -222,34 +259,6 @@
"type": "banner"
}
]
- },
- {
- "currency": "USD",
- "bids": [
- {
- "bid": {
- "id": "7706636740145184841",
- "impid": "test-imp-id-2",
- "price": 0.5,
- "adm": "some-test-ad",
- "adid": "29681110",
- "adomain": [
- "https://advertiser.example.com"
- ],
- "cid": "958",
- "crid": "29681110",
- "w": 300,
- "h": 250,
- "cat": [
- "IAB9-1"
- ],
- "ext": {
- "ix": {}
- }
- },
- "type": "video"
- }
- ]
}
]
}
diff --git a/adapters/ix/ixtest/exemplary/multiple-siteIds.json b/adapters/ix/ixtest/exemplary/multiple-siteIds.json
new file mode 100644
index 00000000000..7f4227ac4b2
--- /dev/null
+++ b/adapters/ix/ixtest/exemplary/multiple-siteIds.json
@@ -0,0 +1,224 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569750"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-3",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569751"
+ }
+ }
+ }
+ ],
+ "site": {
+ "page": "https://www.example.com/"
+ },
+ "ext": {
+ "prebid": {
+ "channel": {
+ "name": "web",
+ "version": "7.0.0"
+ }
+ }
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ],
+ "w": 300,
+ "h": 250
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569750"
+ }
+ }
+ },
+ {
+ "banner": {
+ "format": [
+ {
+ "h": 600,
+ "w": 300
+ }
+ ],
+ "h": 600,
+ "w": 300
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569751"
+ }
+ },
+ "id": "test-imp-id-3"
+ }
+ ],
+ "site": {
+ "page": "https://www.example.com/",
+ "publisher": {
+ }
+ },
+ "ext": {
+ "ixdiag": {
+ "multipleSiteIds": "569749, 569750, 569751",
+ "pbjsv": "7.0.0"
+ },
+ "prebid": {
+ "channel": {
+ "name": "web",
+ "version": "7.0.0"
+ }
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "29681110",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/ix/ixtest/exemplary/simple-banner-multi-size.json b/adapters/ix/ixtest/exemplary/simple-banner-multi-size.json
index 5e0a311a91b..15dc3ecbb0c 100644
--- a/adapters/ix/ixtest/exemplary/simple-banner-multi-size.json
+++ b/adapters/ix/ixtest/exemplary/simple-banner-multi-size.json
@@ -13,6 +13,10 @@
{
"w": 300,
"h": 600
+ },
+ {
+ "w": 600,
+ "h": 800
}
]
},
@@ -22,7 +26,10 @@
}
}
}
- ]
+ ],
+ "site": {
+ "page": "https://www.example.com/"
+ }
},
"httpCalls": [
{
@@ -38,70 +45,16 @@
{
"w": 300,
"h": 250
- }
- ],
- "w": 300,
- "h": 250
- },
- "ext": {
- "bidder": {
- "siteId": "569749"
- }
- }
- }
- ]
- }
- },
- "mockResponse": {
- "status": 200,
- "body": {
- "id": "test-request-id",
- "seatbid": [
- {
- "seat": "958",
- "bid": [
- {
- "id": "7706636740145184841",
- "impid": "test-imp-id",
- "price": 0.5,
- "adid": "29681110",
- "adm": "some-test-ad",
- "adomain": [
- "https://advertiser.example.com"
- ],
- "cid": "958",
- "crid": "29681110",
- "h": 250,
- "w": 300,
- "ext": {
- "ix": {}
- }
- }
- ]
- }
- ],
- "bidid": "5778926625248726496",
- "cur": "USD"
- }
- }
- },
- {
- "expectedRequest": {
- "uri": "http://host/endpoint",
- "body": {
- "id": "test-request-id",
- "imp": [
- {
- "id": "test-imp-id",
- "banner": {
- "format": [
+ },
{
"w": 300,
"h": 600
+ },
+ {
+ "w": 600,
+ "h": 800
}
- ],
- "w": 300,
- "h": 600
+ ]
},
"ext": {
"bidder": {
@@ -109,7 +62,13 @@
}
}
}
- ]
+ ],
+ "site": {
+ "page": "https://www.example.com/",
+ "publisher": {
+ "id": "569749"
+ }
+ }
}
},
"mockResponse": {
@@ -131,7 +90,7 @@
],
"cid": "958",
"crid": "29681110",
- "h": 600,
+ "h": 250,
"w": 300,
"ext": {
"ix": {}
@@ -171,31 +130,6 @@
"type": "banner"
}
]
- },
- {
- "currency": "USD",
- "bids": [
- {
- "bid": {
- "id": "7706636740145184841",
- "impid": "test-imp-id",
- "price": 0.5,
- "adm": "some-test-ad",
- "adid": "29681110",
- "adomain": [
- "https://advertiser.example.com"
- ],
- "cid": "958",
- "crid": "29681110",
- "w": 300,
- "h": 600,
- "ext": {
- "ix": {}
- }
- },
- "type": "banner"
- }
- ]
}
]
}
diff --git a/adapters/ix/ixtest/exemplary/structured-pod.json b/adapters/ix/ixtest/exemplary/structured-pod.json
new file mode 100644
index 00000000000..a5ca9895554
--- /dev/null
+++ b/adapters/ix/ixtest/exemplary/structured-pod.json
@@ -0,0 +1,236 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560,
+ "podid": "1",
+ "slotinpod": 1,
+ "podseq": 1
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560,
+ "podid": "1",
+ "slotinpod": -1,
+ "podseq": 1
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560,
+ "podid": "1",
+ "slotinpod": 1,
+ "podseq": 1
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560,
+ "podid": "1",
+ "slotinpod": -1,
+ "podseq": 1
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 560,
+ "w": 940,
+ "dur": 30,
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ },
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-2",
+ "price": 0.75,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 560,
+ "w": 940,
+ "dur": 30,
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "29681110",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 560,
+ "w": 940,
+ "dur": 30,
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "video"
+ },
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-2",
+ "price": 0.75,
+ "adm": "some-test-ad",
+ "adid": "29681110",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 560,
+ "w": 940,
+ "dur": 30,
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/ix/ixtest/supplemental/multi-imp-requests-error.json b/adapters/ix/ixtest/supplemental/multi-imp-requests-error.json
new file mode 100644
index 00000000000..223e863284c
--- /dev/null
+++ b/adapters/ix/ixtest/supplemental/multi-imp-requests-error.json
@@ -0,0 +1,189 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749",
+ "sid": 12345
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-3",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-3",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 600
+ }
+ ],
+ "w": 300,
+ "h": 600
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 600,
+ "w": 300,
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "29681110",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "w": 300,
+ "h": 600,
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ }
+ ],
+ "expectedMakeRequestsErrors": [
+ {
+ "value": "json: cannot unmarshal number into Go struct field ExtImpIx.sid of type string",
+ "comparison": "literal"
+ }
+ ]
+}
diff --git a/adapters/ix/ixtest/supplemental/sid.json b/adapters/ix/ixtest/supplemental/sid.json
new file mode 100644
index 00000000000..c2156b6b20d
--- /dev/null
+++ b/adapters/ix/ixtest/supplemental/sid.json
@@ -0,0 +1,184 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749",
+ "sid": "1234"
+ }
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569750",
+ "sid": "5678"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://host/endpoint",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id-1",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569749",
+ "sid": "1234"
+ },
+ "sid": "1234"
+ }
+ },
+ {
+ "id": "test-imp-id-2",
+ "video": {
+ "mimes": [
+ "video/mp4"
+ ],
+ "minduration": 15,
+ "maxduration": 30,
+ "protocols": [
+ 2,
+ 3,
+ 5,
+ 6,
+ 7,
+ 8
+ ],
+ "w": 940,
+ "h": 560
+ },
+ "ext": {
+ "bidder": {
+ "siteId": "569750",
+ "sid": "5678"
+ },
+ "sid": "5678"
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "958",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 250,
+ "w": 300,
+ "ext": {
+ "ix": {}
+ }
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id-1",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "29681110",
+ "adomain": [
+ "https://advertiser.example.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "ix": {}
+ }
+ },
+ "type": "video"
+ }
+ ]
+ }
+ ]
+}
diff --git a/adapters/ix/params_test.go b/adapters/ix/params_test.go
index 9246a43a725..8ba937c12f4 100644
--- a/adapters/ix/params_test.go
+++ b/adapters/ix/params_test.go
@@ -38,6 +38,7 @@ var validParams = []string{
`{"siteID":"12345"}`,
`{"siteId":"123456"}`,
`{"siteid":"1234567", "size": [640,480]}`,
+ `{"siteId":"123456", "sid":"12345"}`,
}
var invalidParams = []string{
diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go
index f46fc5913a3..3e4ad138846 100644
--- a/adapters/mobilefuse/mobilefuse.go
+++ b/adapters/mobilefuse/mobilefuse.go
@@ -7,6 +7,7 @@ import (
"strconv"
"text/template"
+ "github.com/buger/jsonparser"
"github.com/prebid/openrtb/v19/openrtb2"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
@@ -27,6 +28,10 @@ type BidExt struct {
Mf ExtMf `json:"mf"`
}
+type ContextData struct {
+ MspPlacementId []string `json:"msp_placement_id,omitempty"`
+}
+
// Builder builds a new instance of the MobileFuse adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
template, err := template.New("endpointTemplate").Parse(config.Endpoint)
@@ -115,6 +120,15 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (
return nil, errs
}
+ if result, dataType, _, err := jsonparser.Get(bidRequest.Imp[0].Ext, "context", "data"); err == nil && dataType == jsonparser.Object {
+ var ctx ContextData
+ err := json.Unmarshal(result, &ctx)
+ if err == nil && len(ctx.MspPlacementId) > 0 && ctx.MspPlacementId[0] == "msp-android-article-inside-display-prod3" {
+ validImps[0].Banner = &openrtb2.Banner{Format: validImps[0].Banner.Format, API: validImps[0].Banner.API}
+ validImps[0].Banner.Format = append(validImps[0].Banner.Format, openrtb2.Format{W: 320, H: 50})
+ }
+ }
+
mobileFuseBidRequest := *bidRequest
mobileFuseBidRequest.Imp = validImps
body, err := json.Marshal(mobileFuseBidRequest)
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 8084b2bcabe..40a80a4eaa9 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -40,9 +40,14 @@ type pubmaticBidExtVideo struct {
Duration *int `json:"duration,omitempty"`
}
+type pubmaticContext struct {
+ Data json.RawMessage `json:"data"`
+}
+
type ExtImpBidderPubmatic struct {
adapters.ExtImpBidder
- Data json.RawMessage `json:"data,omitempty"`
+ Data json.RawMessage `json:"data,omitempty"`
+ Context pubmaticContext `json:"context"`
}
type ExtAdServer struct {
@@ -305,6 +310,10 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP
populateFirstPartyDataImpAttributes(bidderExt.Data, extMap)
}
+ if len(bidderExt.Context.Data) > 0 {
+ populateFirstPartyDataImpAttributes(bidderExt.Context.Data, extMap)
+ }
+
imp.Ext = nil
if len(extMap) > 0 {
ext, err := json.Marshal(extMap)
diff --git a/adapters/pwbid/pwbid_test.go b/adapters/pwbid/pwbid_test.go
index 21a4194249e..194e4bdea02 100644
--- a/adapters/pwbid/pwbid_test.go
+++ b/adapters/pwbid/pwbid_test.go
@@ -10,7 +10,7 @@ import (
func TestJsonSamples(t *testing.T) {
bidder, buildErr := Builder(openrtb_ext.BidderPWBid, config.Adapter{
- Endpoint: "https://bid.pubwise.io/prebid"},
+ Endpoint: "https://bidder.east2.pubwise.io/bid/pubwisedirect"},
config.Server{ExternalUrl: "http://hosturl.com", GvlID: 842, DataCenter: "2"})
if buildErr != nil {
diff --git a/adapters/pwbid/pwbidtest/exemplary/banner.json b/adapters/pwbid/pwbidtest/exemplary/banner.json
index ba618cb8cf1..4cf93e1ab76 100644
--- a/adapters/pwbid/pwbidtest/exemplary/banner.json
+++ b/adapters/pwbid/pwbidtest/exemplary/banner.json
@@ -23,7 +23,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "https://bid.pubwise.io/prebid",
+ "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect",
"body": {
"id": "test-request-id-banner",
"imp": [
@@ -90,4 +90,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/adapters/pwbid/pwbidtest/exemplary/native.json b/adapters/pwbid/pwbidtest/exemplary/native.json
index 907c16d467a..ff57752c5ea 100644
--- a/adapters/pwbid/pwbidtest/exemplary/native.json
+++ b/adapters/pwbid/pwbidtest/exemplary/native.json
@@ -18,7 +18,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "https://bid.pubwise.io/prebid",
+ "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect",
"body": {
"id": "test-request-id-native",
"imp": [
@@ -78,4 +78,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/adapters/pwbid/pwbidtest/exemplary/optional-params.json b/adapters/pwbid/pwbidtest/exemplary/optional-params.json
index a080be90208..5ababb24bdc 100644
--- a/adapters/pwbid/pwbidtest/exemplary/optional-params.json
+++ b/adapters/pwbid/pwbidtest/exemplary/optional-params.json
@@ -25,7 +25,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "https://bid.pubwise.io/prebid",
+ "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect",
"body": {
"id": "test-request-id-banner",
"imp": [
@@ -94,4 +94,4 @@
]
}
]
-}
\ No newline at end of file
+}
diff --git a/adapters/pwbid/pwbidtest/exemplary/video.json b/adapters/pwbid/pwbidtest/exemplary/video.json
index b74c780d0a9..6257de632d4 100644
--- a/adapters/pwbid/pwbidtest/exemplary/video.json
+++ b/adapters/pwbid/pwbidtest/exemplary/video.json
@@ -20,7 +20,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "https://bid.pubwise.io/prebid",
+ "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json b/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json
index 146ba93a27d..0d469893e0c 100644
--- a/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json
+++ b/adapters/pwbid/pwbidtest/supplemental/response-200-without-body.json
@@ -19,7 +19,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "https://bid.pubwise.io/prebid",
+ "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/pwbid/pwbidtest/supplemental/response-204.json b/adapters/pwbid/pwbidtest/supplemental/response-204.json
index 5fff7ee32cc..4fc8961e0bb 100644
--- a/adapters/pwbid/pwbidtest/supplemental/response-204.json
+++ b/adapters/pwbid/pwbidtest/supplemental/response-204.json
@@ -19,7 +19,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "https://bid.pubwise.io/prebid",
+ "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/pwbid/pwbidtest/supplemental/response-400.json b/adapters/pwbid/pwbidtest/supplemental/response-400.json
index d594e571243..a1517883243 100644
--- a/adapters/pwbid/pwbidtest/supplemental/response-400.json
+++ b/adapters/pwbid/pwbidtest/supplemental/response-400.json
@@ -19,7 +19,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "https://bid.pubwise.io/prebid",
+ "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/pwbid/pwbidtest/supplemental/response-500.json b/adapters/pwbid/pwbidtest/supplemental/response-500.json
index fa3d4d063a8..c2b5649418b 100644
--- a/adapters/pwbid/pwbidtest/supplemental/response-500.json
+++ b/adapters/pwbid/pwbidtest/supplemental/response-500.json
@@ -19,7 +19,7 @@
"httpCalls": [
{
"expectedRequest": {
- "uri": "https://bid.pubwise.io/prebid",
+ "uri": "https://bidder.east2.pubwise.io/bid/pubwisedirect",
"body": {
"id": "test-request-id",
"imp": [
diff --git a/adapters/rise/rise.go b/adapters/rise/rise.go
index 5570e27d6ef..e18b7c93852 100644
--- a/adapters/rise/rise.go
+++ b/adapters/rise/rise.go
@@ -8,6 +8,7 @@ import (
"strings"
"github.com/prebid/openrtb/v19/openrtb2"
+
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/openrtb_ext"
@@ -26,9 +27,9 @@ func Builder(_ openrtb_ext.BidderName, config config.Adapter, _ config.Server) (
// MakeRequests prepares the HTTP requests which should be made to fetch bids.
func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, _ *adapters.ExtraRequestInfo) (requestsToBidder []*adapters.RequestData, errs []error) {
- publisherID, err := extractPublisherID(openRTBRequest)
+ org, err := extractOrg(openRTBRequest)
if err != nil {
- errs = append(errs, fmt.Errorf("extractPublisherID: %w", err))
+ errs = append(errs, fmt.Errorf("extractOrg: %w", err))
return nil, errs
}
@@ -43,7 +44,7 @@ func (a *adapter) MakeRequests(openRTBRequest *openrtb2.BidRequest, _ *adapters.
return append(requestsToBidder, &adapters.RequestData{
Method: http.MethodPost,
- Uri: a.endpointURL + "?publisher_id=" + publisherID,
+ Uri: a.endpointURL + "?publisher_id=" + org,
Body: openRTBRequestJSON,
Headers: headers,
}), nil
@@ -87,7 +88,7 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, _ *adapters.RequestData
return bidResponse, errs
}
-func extractPublisherID(openRTBRequest *openrtb2.BidRequest) (string, error) {
+func extractOrg(openRTBRequest *openrtb2.BidRequest) (string, error) {
var err error
for _, imp := range openRTBRequest.Imp {
var bidderExt adapters.ExtImpBidder
@@ -100,12 +101,15 @@ func extractPublisherID(openRTBRequest *openrtb2.BidRequest) (string, error) {
return "", fmt.Errorf("unmarshal ImpExtRise: %w", err)
}
+ if impExt.Org != "" {
+ return strings.TrimSpace(impExt.Org), nil
+ }
if impExt.PublisherID != "" {
return strings.TrimSpace(impExt.PublisherID), nil
}
}
- return "", errors.New("no publisherID supplied")
+ return "", errors.New("no org or publisher_id supplied")
}
func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
diff --git a/adapters/rise/risetest/exemplary/banner-and-video-app.json b/adapters/rise/risetest/exemplary/banner-and-video-app.json
index 9eb79156403..fa267977f72 100644
--- a/adapters/rise/risetest/exemplary/banner-and-video-app.json
+++ b/adapters/rise/risetest/exemplary/banner-and-video-app.json
@@ -18,7 +18,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"path": "mvo",
"zone": "1r"
}
@@ -41,7 +41,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"path": "mvo",
"zone": "1r"
}
@@ -98,7 +98,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"zone": "1r",
"path": "mvo"
}
@@ -121,7 +121,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"zone": "1r",
"path": "mvo"
}
diff --git a/adapters/rise/risetest/exemplary/banner-and-video-gdpr.json b/adapters/rise/risetest/exemplary/banner-and-video-gdpr.json
index dfdf54a8c67..980b62446b5 100644
--- a/adapters/rise/risetest/exemplary/banner-and-video-gdpr.json
+++ b/adapters/rise/risetest/exemplary/banner-and-video-gdpr.json
@@ -18,7 +18,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"path": "mvo",
"zone": "1r"
}
@@ -41,7 +41,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"path": "mvo",
"zone": "1r"
}
@@ -83,7 +83,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"zone": "1r",
"path": "mvo"
}
@@ -106,7 +106,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"zone": "1r",
"path": "mvo"
}
diff --git a/adapters/rise/risetest/exemplary/banner-and-video.json b/adapters/rise/risetest/exemplary/banner-and-video.json
index 2a2e3f681a1..d0d6a9bcc44 100644
--- a/adapters/rise/risetest/exemplary/banner-and-video.json
+++ b/adapters/rise/risetest/exemplary/banner-and-video.json
@@ -55,7 +55,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"path": "mvo",
"zone": "1r"
}
@@ -123,7 +123,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"zone": "1r",
"path": "mvo"
}
diff --git a/adapters/rise/risetest/exemplary/simple-banner-both-ids.json b/adapters/rise/risetest/exemplary/simple-banner-both-ids.json
new file mode 100644
index 00000000000..cb6bbfa779f
--- /dev/null
+++ b/adapters/rise/risetest/exemplary/simple-banner-both-ids.json
@@ -0,0 +1,117 @@
+{
+ "mockBidRequest": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 300
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "org": "72720",
+ "publisher_id": "72721",
+ "path": "mvo",
+ "zone": "1r"
+ }
+ }
+ }
+ ]
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://localhost/prebid_server?publisher_id=72720",
+ "body": {
+ "id": "test-request-id",
+ "imp": [
+ {
+ "id": "test-imp-id",
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ },
+ {
+ "w": 300,
+ "h": 300
+ }
+ ]
+ },
+ "ext": {
+ "bidder": {
+ "org": "72720",
+ "publisher_id": "72721",
+ "zone": "1r",
+ "path": "mvo"
+ }
+ }
+ }
+ ]
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "test-request-id",
+ "seatbid": [
+ {
+ "seat": "Rhythmone",
+ "bid": [
+ {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.500000,
+ "adid": "29681110",
+ "adm": "some-test-ad",
+ "adomain": [
+ "yahoo.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "h": 250,
+ "w": 300,
+ "mtype": 1
+ }
+ ]
+ }
+ ],
+ "bidid": "5778926625248726496",
+ "cur": "USD"
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "bids": [{
+ "bid": {
+ "id": "7706636740145184841",
+ "impid": "test-imp-id",
+ "price": 0.5,
+ "adm": "some-test-ad",
+ "adid": "29681110",
+ "adomain": [
+ "yahoo.com"
+ ],
+ "cid": "958",
+ "crid": "29681110",
+ "w": 300,
+ "h": 250,
+ "mtype": 1
+ },
+ "type": "banner"
+ }]
+ }
+ ]
+}
diff --git a/adapters/rise/risetest/exemplary/simple-video.json b/adapters/rise/risetest/exemplary/simple-video.json
index 16ca8ac72dc..921736a6295 100644
--- a/adapters/rise/risetest/exemplary/simple-video.json
+++ b/adapters/rise/risetest/exemplary/simple-video.json
@@ -21,7 +21,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"path": "mvo",
"zone": "1r"
}
@@ -55,7 +55,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"zone": "1r",
"path": "mvo"
}
diff --git a/adapters/rise/risetest/supplemental/bad-request.json b/adapters/rise/risetest/supplemental/bad-request.json
index f2ba84b1c78..b5bedc090d9 100644
--- a/adapters/rise/risetest/supplemental/bad-request.json
+++ b/adapters/rise/risetest/supplemental/bad-request.json
@@ -21,7 +21,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"path": "mvo",
"zone": "1r"
}
@@ -55,7 +55,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"zone": "1r",
"path": "mvo"
}
diff --git a/adapters/rise/risetest/supplemental/missing-bidder.json b/adapters/rise/risetest/supplemental/missing-bidder.json
index 2bda5161c67..0a7cc67ca08 100644
--- a/adapters/rise/risetest/supplemental/missing-bidder.json
+++ b/adapters/rise/risetest/supplemental/missing-bidder.json
@@ -28,7 +28,7 @@
},
"expectedMakeRequestsErrors": [
{
- "value": "extractPublisherID: unmarshal ImpExtRise: unexpected end of JSON input",
+ "value": "extractOrg: unmarshal ImpExtRise: unexpected end of JSON input",
"comparison": "literal"
}
]
diff --git a/adapters/rise/risetest/supplemental/missing-extension.json b/adapters/rise/risetest/supplemental/missing-extension.json
index db8dbdf74d9..74e94f45c79 100644
--- a/adapters/rise/risetest/supplemental/missing-extension.json
+++ b/adapters/rise/risetest/supplemental/missing-extension.json
@@ -27,7 +27,7 @@
},
"expectedMakeRequestsErrors": [
{
- "value": "extractPublisherID: unmarshal bidderExt: unexpected end of JSON input",
+ "value": "extractOrg: unmarshal bidderExt: unexpected end of JSON input",
"comparison": "literal"
}
]
diff --git a/adapters/rise/risetest/supplemental/missing-mtype.json b/adapters/rise/risetest/supplemental/missing-mtype.json
index be45db584ea..5693f25ad83 100644
--- a/adapters/rise/risetest/supplemental/missing-mtype.json
+++ b/adapters/rise/risetest/supplemental/missing-mtype.json
@@ -21,7 +21,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"path": "mvo",
"zone": "1r"
}
@@ -55,7 +55,7 @@
},
"ext": {
"bidder": {
- "publisher_id": "72721",
+ "org": "72721",
"zone": "1r",
"path": "mvo"
}
diff --git a/adapters/rise/risetest/supplemental/missing-publisher-id.json b/adapters/rise/risetest/supplemental/missing-org.json
similarity index 89%
rename from adapters/rise/risetest/supplemental/missing-publisher-id.json
rename to adapters/rise/risetest/supplemental/missing-org.json
index 3a14b213160..4107d40e1c3 100644
--- a/adapters/rise/risetest/supplemental/missing-publisher-id.json
+++ b/adapters/rise/risetest/supplemental/missing-org.json
@@ -24,6 +24,7 @@
},
"ext": {
"bidder": {
+ "org": "",
"publisher_id": ""
}}
}
@@ -31,7 +32,7 @@
},
"expectedMakeRequestsErrors": [
{
- "value": "extractPublisherID: no publisherID supplied",
+ "value": "extractOrg: no org or publisher_id supplied",
"comparison": "literal"
}
]
diff --git a/adapters/sharethrough/sharethrough.go b/adapters/sharethrough/sharethrough.go
index f797bfe43d9..009ce0aa1d5 100644
--- a/adapters/sharethrough/sharethrough.go
+++ b/adapters/sharethrough/sharethrough.go
@@ -83,21 +83,29 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
requestCopy.BCat = append(requestCopy.BCat, strImpParams.BCat...)
requestCopy.BAdv = append(requestCopy.BAdv, strImpParams.BAdv...)
- requestCopy.Imp = []openrtb2.Imp{imp}
-
- requestJSON, err := json.Marshal(requestCopy)
+ impressionsByMediaType, err := splitImpressionsByMediaType(&imp)
if err != nil {
errors = append(errors, err)
continue
}
- requestData := &adapters.RequestData{
- Method: "POST",
- Uri: a.endpoint,
- Body: requestJSON,
- Headers: headers,
+ for _, impression := range impressionsByMediaType {
+ requestCopy.Imp = []openrtb2.Imp{impression}
+
+ requestJSON, err := json.Marshal(requestCopy)
+ if err != nil {
+ errors = append(errors, err)
+ continue
+ }
+
+ requestData := &adapters.RequestData{
+ Method: "POST",
+ Uri: a.endpoint,
+ Body: requestJSON,
+ Headers: headers,
+ }
+ requests = append(requests, requestData)
}
- requests = append(requests, requestData)
}
return requests, errors
@@ -150,6 +158,40 @@ func (a *adapter) MakeBids(request *openrtb2.BidRequest, requestData *adapters.R
return bidderResponse, errors
}
+func splitImpressionsByMediaType(impression *openrtb2.Imp) ([]openrtb2.Imp, error) {
+ if impression.Banner == nil && impression.Video == nil && impression.Native == nil {
+ return nil, &errortypes.BadInput{Message: "Invalid MediaType. Sharethrough only supports Banner, Video and Native."}
+ }
+
+ if impression.Audio != nil {
+ impression.Audio = nil
+ }
+
+ impressions := make([]openrtb2.Imp, 0, 3)
+
+ if impression.Banner != nil {
+ impCopy := *impression
+ impCopy.Video = nil
+ impCopy.Native = nil
+ impressions = append(impressions, impCopy)
+ }
+
+ if impression.Video != nil {
+ impCopy := *impression
+ impCopy.Banner = nil
+ impCopy.Native = nil
+ impressions = append(impressions, impCopy)
+ }
+
+ if impression.Native != nil {
+ impression.Banner = nil
+ impression.Video = nil
+ impressions = append(impressions, *impression)
+ }
+
+ return impressions, nil
+}
+
func getMediaTypeForBid(bid openrtb2.Bid) (openrtb_ext.BidType, error) {
if bid.Ext != nil {
diff --git a/adapters/sharethrough/sharethroughtest/supplemental/multiformat-impression.json b/adapters/sharethrough/sharethroughtest/supplemental/multiformat-impression.json
new file mode 100644
index 00000000000..fcebf79acc8
--- /dev/null
+++ b/adapters/sharethrough/sharethroughtest/supplemental/multiformat-impression.json
@@ -0,0 +1,362 @@
+{
+ "mockBidRequest": {
+ "id": "parent-id",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "impression-id",
+ "ext": {
+ "bidder": {
+ "pkey": "pkey1"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ },
+ "video": {
+ "w": 640,
+ "h": 480,
+ "mimes": [
+ "video/mp4"
+ ],
+ "placement": 1
+ },
+ "native": {
+ "ver": "1.2",
+ "request": "placeholder request"
+ },
+ "audio": {
+ "mimes": [
+ "audio/mp4"
+ ],
+ "protocols": [
+ 1,
+ 2
+ ]
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ }
+ },
+ "httpCalls": [
+ {
+ "expectedRequest": {
+ "uri": "http://whatever.url",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "body": {
+ "id": "parent-id",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "impression-id",
+ "tagid": "pkey1",
+ "ext": {
+ "bidder": {
+ "pkey": "pkey1"
+ }
+ },
+ "banner": {
+ "format": [
+ {
+ "w": 300,
+ "h": 250
+ }
+ ]
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ },
+ "source": {
+ "ext": {
+ "version": "",
+ "str": "10.0"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "parent-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "parent-id",
+ "impid": "impression-id",
+ "crid": "some-creative-id",
+ "adm": "
Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "uri": "http://whatever.url",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "body": {
+ "id": "parent-id",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "impression-id",
+ "tagid": "pkey1",
+ "ext": {
+ "bidder": {
+ "pkey": "pkey1"
+ }
+ },
+ "video": {
+ "w": 640,
+ "h": 480,
+ "mimes": [
+ "video/mp4"
+ ],
+ "placement": 1
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ },
+ "source": {
+ "ext": {
+ "version": "",
+ "str": "10.0"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "parent-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "parent-id",
+ "impid": "impression-id",
+ "crid": "some-creative-id",
+ "adm": "TAG",
+ "price": 20,
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ },
+ {
+ "expectedRequest": {
+ "uri": "http://whatever.url",
+ "headers": {
+ "Content-Type": [
+ "application/json;charset=utf-8"
+ ],
+ "Accept": [
+ "application/json"
+ ]
+ },
+ "body": {
+ "id": "parent-id",
+ "tmax": 3000,
+ "imp": [
+ {
+ "id": "impression-id",
+ "tagid": "pkey1",
+ "ext": {
+ "bidder": {
+ "pkey": "pkey1"
+ }
+ },
+ "native": {
+ "ver": "1.2",
+ "request": "placeholder request"
+ }
+ }
+ ],
+ "site": {
+ "publisher": {
+ "id": "1"
+ },
+ "page": "https://some-site.com",
+ "ref": "https://some-site.com"
+ },
+ "device": {
+ "w": 1200,
+ "h": 900
+ },
+ "source": {
+ "ext": {
+ "version": "",
+ "str": "10.0"
+ }
+ }
+ }
+ },
+ "mockResponse": {
+ "status": 200,
+ "body": {
+ "id": "parent-id",
+ "cur": "USD",
+ "seatbid": [
+ {
+ "bid": [
+ {
+ "id": "parent-id",
+ "impid": "impression-id",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ }
+ }
+ ]
+ }
+ ]
+ }
+ }
+ }
+ ],
+ "expectedBidResponses": [
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "parent-id",
+ "impid": "impression-id",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 300,
+ "h": 250,
+ "ext": {
+ "prebid": {
+ "type": "banner"
+ }
+ }
+ },
+ "type": "banner"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "parent-id",
+ "impid": "impression-id",
+ "crid": "some-creative-id",
+ "adm": "TAG",
+ "price": 20,
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "video"
+ }
+ }
+ },
+ "type": "video"
+ }
+ ]
+ },
+ {
+ "currency": "USD",
+ "bids": [
+ {
+ "bid": {
+ "id": "parent-id",
+ "impid": "impression-id",
+ "crid": "some-creative-id",
+ "adm": "Ad
",
+ "price": 20,
+ "w": 640,
+ "h": 480,
+ "ext": {
+ "prebid": {
+ "type": "native"
+ }
+ }
+ },
+ "type": "native"
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/adapters/yahooAdvertising/params_test.go b/adapters/yahooAds/params_test.go
similarity index 63%
rename from adapters/yahooAdvertising/params_test.go
rename to adapters/yahooAds/params_test.go
index 10d504cb291..c0deaaa32c9 100644
--- a/adapters/yahooAdvertising/params_test.go
+++ b/adapters/yahooAds/params_test.go
@@ -1,4 +1,4 @@
-package yahooAdvertising
+package yahooAds
import (
"encoding/json"
@@ -7,11 +7,11 @@ import (
"github.com/prebid/prebid-server/openrtb_ext"
)
-// This file actually intends to test static/bidder-params/yahooAdvertising.json
+// This file actually intends to test static/bidder-params/yahooAds.json
//
-// These also validate the format of the external API: request.imp[i].ext.yahooAdvertising
+// These also validate the format of the external API: request.imp[i].ext.yahooAds
-// TestValidParams makes sure that the yahooAdvertising schema accepts all imp.ext fields which we intend to support.
+// TestValidParams makes sure that the yahooAds schema accepts all imp.ext fields which we intend to support.
func TestValidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
@@ -19,13 +19,13 @@ func TestValidParams(t *testing.T) {
}
for _, validParam := range validParams {
- if err := validator.Validate(openrtb_ext.BidderYahooAdvertising, json.RawMessage(validParam)); err != nil {
- t.Errorf("Schema rejected yahooAdvertising params: %s", validParam)
+ if err := validator.Validate(openrtb_ext.BidderYahooAds, json.RawMessage(validParam)); err != nil {
+ t.Errorf("Schema rejected yahooAds params: %s", validParam)
}
}
}
-// TestInvalidParams makes sure that the yahooAdvertising schema rejects all the imp.ext fields we don't support.
+// TestInvalidParams makes sure that the yahooAds schema rejects all the imp.ext fields we don't support.
func TestInvalidParams(t *testing.T) {
validator, err := openrtb_ext.NewBidderParamsValidator("../../static/bidder-params")
if err != nil {
@@ -33,7 +33,7 @@ func TestInvalidParams(t *testing.T) {
}
for _, invalidParam := range invalidParams {
- if err := validator.Validate(openrtb_ext.BidderYahooAdvertising, json.RawMessage(invalidParam)); err == nil {
+ if err := validator.Validate(openrtb_ext.BidderYahooAds, json.RawMessage(invalidParam)); err == nil {
t.Errorf("Schema allowed unexpected params: %s", invalidParam)
}
}
diff --git a/adapters/yahooAdvertising/yahooAdvertising.go b/adapters/yahooAds/yahooAds.go
similarity index 93%
rename from adapters/yahooAdvertising/yahooAdvertising.go
rename to adapters/yahooAds/yahooAds.go
index d476adb4e8e..3597d0e359c 100644
--- a/adapters/yahooAdvertising/yahooAdvertising.go
+++ b/adapters/yahooAds/yahooAds.go
@@ -1,4 +1,4 @@
-package yahooAdvertising
+package yahooAds
import (
"encoding/json"
@@ -41,8 +41,8 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
continue
}
- var yahooAdvertisingExt openrtb_ext.ExtImpYahooAdvertising
- err = json.Unmarshal(bidderExt.Bidder, &yahooAdvertisingExt)
+ var yahooAdsExt openrtb_ext.ExtImpYahooAds
+ err = json.Unmarshal(bidderExt.Bidder, &yahooAdsExt)
if err != nil {
err = &errortypes.BadInput{
Message: fmt.Sprintf("imp #%d: %s", idx, err.Error()),
@@ -64,7 +64,7 @@ func (a *adapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.E
reqCopy.App = &appCopy
}
- if err := changeRequestForBidService(&reqCopy, &yahooAdvertisingExt); err != nil {
+ if err := changeRequestForBidService(&reqCopy, &yahooAdsExt); err != nil {
errors = append(errors, err)
continue
}
@@ -150,7 +150,7 @@ func getImpInfo(impId string, imps []openrtb2.Imp) (bool, openrtb_ext.BidType) {
return exists, mediaType
}
-func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpYahooAdvertising) error {
+func changeRequestForBidService(request *openrtb2.BidRequest, extension *openrtb_ext.ExtImpYahooAds) error {
/* Always override the tag ID and (site ID or app ID) of the request */
request.Imp[0].TagID = extension.Pos
if request.Site != nil {
@@ -218,7 +218,7 @@ func validateBanner(banner *openrtb2.Banner) error {
return nil
}
-// Builder builds a new instance of the YahooAdvertising adapter for the given bidder with the given config.
+// Builder builds a new instance of the YahooAds adapter for the given bidder with the given config.
func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
bidder := &adapter{
URI: config.Endpoint,
diff --git a/adapters/yahooAdvertising/yahooAdvertising_test.go b/adapters/yahooAds/yahooAds_test.go
similarity index 51%
rename from adapters/yahooAdvertising/yahooAdvertising_test.go
rename to adapters/yahooAds/yahooAds_test.go
index e701c3335c1..924eabd5ec1 100644
--- a/adapters/yahooAdvertising/yahooAdvertising_test.go
+++ b/adapters/yahooAds/yahooAds_test.go
@@ -1,4 +1,4 @@
-package yahooAdvertising
+package yahooAds
import (
"testing"
@@ -10,8 +10,8 @@ import (
"github.com/prebid/prebid-server/openrtb_ext"
)
-func TestYahooAdvertisingBidderEndpointConfig(t *testing.T) {
- bidder, buildErr := Builder(openrtb_ext.BidderYahooAdvertising, config.Adapter{
+func TestYahooAdsBidderEndpointConfig(t *testing.T) {
+ bidder, buildErr := Builder(openrtb_ext.BidderYahooAds, config.Adapter{
Endpoint: "http://localhost/bid",
}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
@@ -19,17 +19,17 @@ func TestYahooAdvertisingBidderEndpointConfig(t *testing.T) {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}
- bidderYahooAdvertising := bidder.(*adapter)
+ bidderYahooAds := bidder.(*adapter)
- assert.Equal(t, "http://localhost/bid", bidderYahooAdvertising.URI)
+ assert.Equal(t, "http://localhost/bid", bidderYahooAds.URI)
}
func TestJsonSamples(t *testing.T) {
- bidder, buildErr := Builder(openrtb_ext.BidderYahooAdvertising, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
+ bidder, buildErr := Builder(openrtb_ext.BidderYahooAds, config.Adapter{}, config.Server{ExternalUrl: "http://hosturl.com", GvlID: 1, DataCenter: "2"})
if buildErr != nil {
t.Fatalf("Builder returned unexpected error %v", buildErr)
}
- adapterstest.RunJSONBidderTest(t, "yahooAdvertisingtest", bidder)
+ adapterstest.RunJSONBidderTest(t, "yahooAdstest", bidder)
}
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-app-banner.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json
similarity index 98%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-app-banner.json
rename to adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json
index d5e77c71d3d..7ad41161915 100644
--- a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-app-banner.json
+++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-app-banner.json
@@ -77,7 +77,7 @@
"id": "test-request-id",
"seatbid": [
{
- "seat": "yahooAdvertising",
+ "seat": "yahooAds",
"bid": [{
"id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
"impid": "test-imp-id",
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-banner.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json
similarity index 98%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-banner.json
rename to adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json
index d08bdf06019..7036664d4ad 100644
--- a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-banner.json
+++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-banner.json
@@ -77,7 +77,7 @@
"id": "test-request-id",
"seatbid": [
{
- "seat": "yahooAdvertising",
+ "seat": "yahooAds",
"bid": [{
"id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
"impid": "test-imp-id",
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-video.json b/adapters/yahooAds/yahooAdstest/exemplary/simple-video.json
similarity index 98%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-video.json
rename to adapters/yahooAds/yahooAdstest/exemplary/simple-video.json
index ea95ec40306..ebf7af93d53 100644
--- a/adapters/yahooAdvertising/yahooAdvertisingtest/exemplary/simple-video.json
+++ b/adapters/yahooAds/yahooAdstest/exemplary/simple-video.json
@@ -92,7 +92,7 @@
"id": "test-request-id",
"seatbid": [
{
- "seat": "yahooAdvertising",
+ "seat": "yahooAds",
"bid": [{
"id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
"impid": "test-imp-id",
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/empty-banner-format.json b/adapters/yahooAds/yahooAdstest/supplemental/empty-banner-format.json
similarity index 100%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/empty-banner-format.json
rename to adapters/yahooAds/yahooAdstest/supplemental/empty-banner-format.json
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/invalid-banner-height.json b/adapters/yahooAds/yahooAdstest/supplemental/invalid-banner-height.json
similarity index 100%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/invalid-banner-height.json
rename to adapters/yahooAds/yahooAdstest/supplemental/invalid-banner-height.json
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/invalid-banner-width.json b/adapters/yahooAds/yahooAdstest/supplemental/invalid-banner-width.json
similarity index 100%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/invalid-banner-width.json
rename to adapters/yahooAds/yahooAdstest/supplemental/invalid-banner-width.json
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/non-supported-requests-bids-ignored.json b/adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json
similarity index 98%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/non-supported-requests-bids-ignored.json
rename to adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json
index a44020daf3f..c0d77fa496b 100644
--- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/non-supported-requests-bids-ignored.json
+++ b/adapters/yahooAds/yahooAdstest/supplemental/non-supported-requests-bids-ignored.json
@@ -74,7 +74,7 @@
"id": "test-request-id",
"seatbid": [
{
- "seat": "yahooAdvertising",
+ "seat": "yahooAds",
"bid": [{
"id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
"impid": "test-imp-id",
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/required-nobidder-info.json b/adapters/yahooAds/yahooAdstest/supplemental/required-nobidder-info.json
similarity index 100%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/required-nobidder-info.json
rename to adapters/yahooAds/yahooAdstest/supplemental/required-nobidder-info.json
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-error.json b/adapters/yahooAds/yahooAdstest/supplemental/server-error.json
similarity index 100%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-error.json
rename to adapters/yahooAds/yahooAdstest/supplemental/server-error.json
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-response-wrong-impid.json b/adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json
similarity index 98%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-response-wrong-impid.json
rename to adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json
index 1fcf61e12b7..f40819497a8 100644
--- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/server-response-wrong-impid.json
+++ b/adapters/yahooAds/yahooAdstest/supplemental/server-response-wrong-impid.json
@@ -76,7 +76,7 @@
"id": "test-request-id",
"seatbid": [
{
- "seat": "yahooAdvertising",
+ "seat": "yahooAds",
"bid": [{
"id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
"impid": "wrong",
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp-overwrite.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json
similarity index 98%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp-overwrite.json
rename to adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json
index 190f96bf0a6..94c895b996d 100644
--- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp-overwrite.json
+++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp-overwrite.json
@@ -92,7 +92,7 @@
"id": "test-request-id",
"seatbid": [
{
- "seat": "yahooAdvertising",
+ "seat": "yahooAds",
"bid": [{
"id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
"impid": "test-imp-id",
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json
similarity index 98%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp.json
rename to adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json
index 81c89f55c3d..3d5aff6c531 100644
--- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-gpp.json
+++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-gpp.json
@@ -87,7 +87,7 @@
"id": "test-request-id",
"seatbid": [
{
- "seat": "yahooAdvertising",
+ "seat": "yahooAds",
"bid": [{
"id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
"impid": "test-imp-id",
diff --git a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-ignore-width-when-height-missing.json b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json
similarity index 98%
rename from adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-ignore-width-when-height-missing.json
rename to adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json
index 353d5e29e71..1206005970c 100644
--- a/adapters/yahooAdvertising/yahooAdvertisingtest/supplemental/simple-banner-ignore-width-when-height-missing.json
+++ b/adapters/yahooAds/yahooAdstest/supplemental/simple-banner-ignore-width-when-height-missing.json
@@ -78,7 +78,7 @@
"id": "test-request-id",
"seatbid": [
{
- "seat": "yahooAdvertising",
+ "seat": "yahooAds",
"bid": [{
"id": "8ee514f1-b2b8-4abb-89fd-084437d1e800",
"impid": "test-imp-id",
diff --git a/analytics/config/config.go b/analytics/config/config.go
index 557fec361dc..cb8cd24efcd 100644
--- a/analytics/config/config.go
+++ b/analytics/config/config.go
@@ -37,6 +37,12 @@ func NewPBSAnalytics(analytics *config.Analytics) analytics.PBSAnalyticsModule {
glog.Errorf("Could not initialize PubstackModule: %v", err)
}
}
+
+ customAdapters := mspLoadAnalyticsAdapterPlugins(analytics.Custom)
+ for _, adapter := range customAdapters {
+ modules = append(modules, adapter)
+ }
+
return modules
}
diff --git a/analytics/config/msp_plugin.go b/analytics/config/msp_plugin.go
new file mode 100644
index 00000000000..477c3e5f474
--- /dev/null
+++ b/analytics/config/msp_plugin.go
@@ -0,0 +1,40 @@
+package config
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/golang/glog"
+ "github.com/prebid/prebid-server/analytics"
+ mspPlugin "github.com/prebid/prebid-server/msp/plugin"
+)
+
+type PluginBuilder interface {
+ Build(json.RawMessage) (analytics.PBSAnalyticsModule, error)
+}
+
+func mspLoadAnalyticsAdapterPlugins(cfg map[string]interface{}) []analytics.PBSAnalyticsModule {
+ plugins := make([]analytics.PBSAnalyticsModule, 0)
+
+ for name, cfgData := range cfg {
+ builder, cfgJson, skip, err := mspPlugin.LoadBuilder[PluginBuilder](name, cfgData)
+
+ if skip {
+ continue
+ }
+
+ if err != nil {
+ panic(err)
+ }
+
+ plugin, err := builder.Build(cfgJson)
+ if err != nil {
+ panic(fmt.Sprintf("Failed to build Analytics Adapter plugin %s, error: %+v\n", name, err))
+ } else {
+ glog.Infof("Loaded Analytics Adapter plugin: %s\n", name)
+ plugins = append(plugins, plugin)
+ }
+ }
+
+ return plugins
+}
diff --git a/config/account.go b/config/account.go
index 8beff9b6569..020402114de 100644
--- a/config/account.go
+++ b/config/account.go
@@ -40,7 +40,7 @@ type Account struct {
Validations Validations `mapstructure:"validations" json:"validations"`
DefaultBidLimit int `mapstructure:"default_bid_limit" json:"default_bid_limit"`
BidAdjustments *openrtb_ext.ExtRequestPrebidBidAdjustments `mapstructure:"bidadjustments" json:"bidadjustments"`
- Privacy AccountPrivacy `mapstructure:"privacy" json:"privacy"`
+ Privacy *AccountPrivacy `mapstructure:"privacy" json:"privacy"`
}
// CookieSync represents the account-level defaults for the cookie sync endpoint.
diff --git a/config/activity.go b/config/activity.go
index 987cbe84a2d..5bddc7c6405 100644
--- a/config/activity.go
+++ b/config/activity.go
@@ -8,16 +8,17 @@ type AllowActivities struct {
TransmitUserFPD Activity `mapstructure:"transmitUfpd" json:"transmitUfpd"`
TransmitPreciseGeo Activity `mapstructure:"transmitPreciseGeo" json:"transmitPreciseGeo"`
TransmitUniqueRequestIds Activity `mapstructure:"transmitUniqueRequestIds" json:"transmitUniqueRequestIds"`
+ TransmitTids Activity `mapstructure:"transmitTid" json:"transmitTid"`
}
type Activity struct {
Default *bool `mapstructure:"default" json:"default"`
Rules []ActivityRule `mapstructure:"rules" json:"rules"`
- Allow bool `mapstructure:"allow" json:"allow"`
}
type ActivityRule struct {
Condition ActivityCondition `mapstructure:"condition" json:"condition"`
+ Allow bool `mapstructure:"allow" json:"allow"`
}
type ActivityCondition struct {
diff --git a/config/bidderinfo.go b/config/bidderinfo.go
index 6f62488c878..4e6989aaab5 100644
--- a/config/bidderinfo.go
+++ b/config/bidderinfo.go
@@ -49,6 +49,12 @@ type BidderInfo struct {
// EndpointCompression determines, if set, the type of compression the bid request will undergo before being sent to the corresponding bid server
EndpointCompression string `yaml:"endpointCompression" mapstructure:"endpointCompression"`
OpenRTB *OpenRTBInfo `yaml:"openrtb" mapstructure:"openrtb"`
+
+ // For MSP Plugin extension only.
+ MspSoPath string `mapstructure:"so_path,omitempty" json:"so_path,omitempty"`
+
+ // For Nova Adapter only
+ NovaScylla AdapterNovaScylla `yaml:"scylla" mapstructure:"scylla"`
}
// BidderInfoExperiment specifies non-production ready feature config for a bidder
@@ -179,6 +185,12 @@ type SyncerEndpoint struct {
UserMacro string `yaml:"userMacro" mapstructure:"user_macro"`
}
+// AdapterNovaScylla specifies the the scylla config for Nova Adapter
+type AdapterNovaScylla struct {
+ Cluster string `yaml:"cluster" mapstructure:"cluster"`
+ KeySpace string `yaml:"keyspace" mapstructure:"keyspace"`
+}
+
func (bi BidderInfo) IsEnabled() bool {
return !bi.Disabled
}
@@ -436,6 +448,12 @@ func applyBidderInfoConfigOverrides(configBidderInfos BidderInfos, fsBidderInfos
if bidderInfo.EndpointCompression == "" && fsBidderCfg.EndpointCompression != "" {
bidderInfo.EndpointCompression = fsBidderCfg.EndpointCompression
}
+ if bidderInfo.NovaScylla.Cluster == "" && fsBidderCfg.NovaScylla.Cluster != "" {
+ bidderInfo.NovaScylla.Cluster = fsBidderCfg.NovaScylla.Cluster
+ }
+ if bidderInfo.NovaScylla.KeySpace == "" && fsBidderCfg.NovaScylla.KeySpace != "" {
+ bidderInfo.NovaScylla.KeySpace = fsBidderCfg.NovaScylla.KeySpace
+ }
// validate and try to apply the legacy usersync_url configuration in attempt to provide
// an easier upgrade path. be warned, this will break if the bidder adds a second syncer
diff --git a/config/config.go b/config/config.go
index ee18c746e88..754c42250f2 100644
--- a/config/config.go
+++ b/config/config.go
@@ -38,21 +38,22 @@ type Configuration struct {
GarbageCollectorThreshold int `mapstructure:"garbage_collector_threshold"`
// StatusResponse is the string which will be returned by the /status endpoint when things are OK.
// If empty, it will return a 204 with no content.
- StatusResponse string `mapstructure:"status_response"`
- AuctionTimeouts AuctionTimeouts `mapstructure:"auction_timeouts_ms"`
- TmaxAdjustments TmaxAdjustments `mapstructure:"tmax_adjustments"`
- CacheURL Cache `mapstructure:"cache"`
- ExtCacheURL ExternalCache `mapstructure:"external_cache"`
- RecaptchaSecret string `mapstructure:"recaptcha_secret"`
- HostCookie HostCookie `mapstructure:"host_cookie"`
- Metrics Metrics `mapstructure:"metrics"`
- StoredRequests StoredRequests `mapstructure:"stored_requests"`
- StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"`
- CategoryMapping StoredRequests `mapstructure:"category_mapping"`
- VTrack VTrack `mapstructure:"vtrack"`
- Event Event `mapstructure:"event"`
- Accounts StoredRequests `mapstructure:"accounts"`
- UserSync UserSync `mapstructure:"user_sync"`
+ StatusResponse string `mapstructure:"status_response"`
+ AuctionTimeouts AuctionTimeouts `mapstructure:"auction_timeouts_ms"`
+ TmaxAdjustments TmaxAdjustments `mapstructure:"tmax_adjustments"`
+ CacheURL Cache `mapstructure:"cache"`
+ ExtCacheURL ExternalCache `mapstructure:"external_cache"`
+ RecaptchaSecret string `mapstructure:"recaptcha_secret"`
+ HostCookie HostCookie `mapstructure:"host_cookie"`
+ Metrics Metrics `mapstructure:"metrics"`
+ MSPMetricsConfig MSPMetricsConfig `mapstructure:"msp_metrics"`
+ StoredRequests StoredRequests `mapstructure:"stored_requests"`
+ StoredRequestsAMP StoredRequests `mapstructure:"stored_amp_req"`
+ CategoryMapping StoredRequests `mapstructure:"category_mapping"`
+ VTrack VTrack `mapstructure:"vtrack"`
+ Event Event `mapstructure:"event"`
+ Accounts StoredRequests `mapstructure:"accounts"`
+ UserSync UserSync `mapstructure:"user_sync"`
// Note that StoredVideo refers to stored video requests, and has nothing to do with caching video creatives.
StoredVideo StoredRequests `mapstructure:"stored_video_req"`
StoredResponses StoredRequests `mapstructure:"stored_responses"`
@@ -155,6 +156,10 @@ func (cfg *Configuration) validate(v *viper.Viper) []error {
cfg.TmaxAdjustments.Enabled = false
}
+ if cfg.AccountDefaults.Privacy != nil {
+ glog.Warning("account_defaults.Privacy has no effect as the feature is under development.")
+ }
+
errs = cfg.Experiment.validate(errs)
errs = cfg.BidderInfos.validate(errs)
return errs
@@ -448,6 +453,10 @@ type LMT struct {
type Analytics struct {
File FileLogs `mapstructure:"file"`
Pubstack Pubstack `mapstructure:"pubstack"`
+
+ // Made by MSP. This field is for loading analytics adapter
+ // dynamically through Golang Plugin at runtime
+ Custom map[string]interface{} `mapstructure:"custom"`
}
type CurrencyConverter struct {
@@ -519,6 +528,12 @@ type Metrics struct {
Disabled DisabledMetrics `mapstructure:"disabled_metrics"`
}
+type MSPMetricsConfig struct {
+ Enabled bool `mapstructure:"enabled"`
+ Port int `mapstructure:"port"`
+ SoPath string `mapstructure:"so_path"`
+}
+
type DisabledMetrics struct {
// True if we want to stop collecting account-to-adapter metrics
AccountAdapterDetails bool `mapstructure:"account_adapter_details"`
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 73f4842ae83..8d328b7c51a 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -6,6 +6,7 @@ import (
"encoding/json"
"errors"
"fmt"
+ "github.com/prebid/prebid-server/privacy"
"net/http"
"net/url"
"strings"
@@ -222,6 +223,13 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR)
+ activities, activitiesErr := privacy.NewActivityControl(account.Privacy)
+ if activitiesErr != nil {
+ errL = append(errL, activitiesErr)
+ writeError(errL, w, &labels)
+ return
+ }
+
secGPC := r.Header.Get("Sec-GPC")
auctionRequest := &exchange.AuctionRequest{
@@ -239,6 +247,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
HookExecutor: hookExecutor,
QueryParams: r.URL.Query(),
TCF2Config: tcf2Config,
+ Activities: activities,
}
auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 216bff6eaf5..fcb70c3184a 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -15,6 +15,8 @@ import (
"strings"
"time"
+ "github.com/prebid/prebid-server/privacy"
+
"github.com/buger/jsonparser"
"github.com/gofrs/uuid"
"github.com/golang/glog"
@@ -191,9 +193,25 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR)
+ activities, activitiesErr := privacy.NewActivityControl(account.Privacy)
+ if activitiesErr != nil {
+ errL = append(errL, activitiesErr)
+ if errortypes.ContainsFatalError(errL) {
+ writeError(errL, w, &labels)
+ return
+ }
+ }
+
ctx := context.Background()
timeout := deps.cfg.AuctionTimeouts.LimitAuctionTimeout(time.Duration(req.TMax) * time.Millisecond)
+
+ // adjust tmax for requests with MSB feature enabled
+ msbConfig := exchange.ExtractMSBInfoReq(req.BidRequest)
+ if msbConfig.LastPeek.PeekStartTimeMilliSeconds > 0 {
+ timeout += time.Duration(msbConfig.LastPeek.PeekStartTimeMilliSeconds) * time.Millisecond
+ }
+
if timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithDeadline(ctx, start.Add(timeout))
@@ -236,6 +254,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
PubID: labels.PubID,
HookExecutor: hookExecutor,
TCF2Config: tcf2Config,
+ Activities: activities,
}
auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
ao.RequestWrapper = req
@@ -2288,6 +2307,7 @@ func writeError(errs []error, w http.ResponseWriter, labels *metrics.Labels) boo
w.WriteHeader(httpStatus)
labels.RequestStatus = metricsStatus
for _, err := range errs {
+ glog.Errorln("MSP Error", err.Error())
w.Write([]byte(fmt.Sprintf("Invalid request: %s\n", err.Error())))
}
rc = true
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 171600c0260..3818a16a03a 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -28,6 +28,7 @@ import (
"github.com/prebid/prebid-server/adapters/adview"
"github.com/prebid/prebid-server/adapters/adxcg"
"github.com/prebid/prebid-server/adapters/adyoulike"
+ "github.com/prebid/prebid-server/adapters/aidem"
"github.com/prebid/prebid-server/adapters/aja"
"github.com/prebid/prebid-server/adapters/algorix"
"github.com/prebid/prebid-server/adapters/amx"
@@ -169,7 +170,7 @@ import (
"github.com/prebid/prebid-server/adapters/visx"
"github.com/prebid/prebid-server/adapters/vrtcal"
"github.com/prebid/prebid-server/adapters/xeworks"
- "github.com/prebid/prebid-server/adapters/yahooAdvertising"
+ "github.com/prebid/prebid-server/adapters/yahooAds"
"github.com/prebid/prebid-server/adapters/yeahmobi"
"github.com/prebid/prebid-server/adapters/yieldlab"
"github.com/prebid/prebid-server/adapters/yieldmo"
@@ -212,6 +213,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderAdView: adview.Builder,
openrtb_ext.BidderAdxcg: adxcg.Builder,
openrtb_ext.BidderAdyoulike: adyoulike.Builder,
+ openrtb_ext.BidderAidem: aidem.Builder,
openrtb_ext.BidderAJA: aja.Builder,
openrtb_ext.BidderAlgorix: algorix.Builder,
openrtb_ext.BidderAMX: amx.Builder,
@@ -367,8 +369,9 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
openrtb_ext.BidderVisx: visx.Builder,
openrtb_ext.BidderVrtcal: vrtcal.Builder,
openrtb_ext.BidderXeworks: xeworks.Builder,
- openrtb_ext.BidderYahooAdvertising: yahooAdvertising.Builder,
- openrtb_ext.BidderYahooSSP: yahooAdvertising.Builder,
+ openrtb_ext.BidderYahooAds: yahooAds.Builder,
+ openrtb_ext.BidderYahooAdvertising: yahooAds.Builder,
+ openrtb_ext.BidderYahooSSP: yahooAds.Builder,
openrtb_ext.BidderYeahmobi: yeahmobi.Builder,
openrtb_ext.BidderYieldlab: yieldlab.Builder,
openrtb_ext.BidderYieldmo: yieldmo.Builder,
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index d8f683b35d2..10043b6ccad 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -12,7 +12,8 @@ import (
func BuildAdapters(client *http.Client, cfg *config.Configuration, infos config.BidderInfos, me metrics.MetricsEngine) (map[openrtb_ext.BidderName]AdaptedBidder, []error) {
server := config.Server{ExternalUrl: cfg.ExternalURL, GvlID: cfg.GDPR.HostVendorID, DataCenter: cfg.DataCenter}
- bidders, errs := buildBidders(infos, newAdapterBuilders(), server)
+ adapterBuilders := mspAddAdaptersFromPlugins(newAdapterBuilders(), infos)
+ bidders, errs := buildBidders(infos, adapterBuilders, server)
if len(errs) > 0 {
return nil, errs
@@ -88,7 +89,7 @@ func GetDisabledBiddersErrorMessages(infos config.BidderInfos) map[string]string
"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
"adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
- "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`,
+ "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAds" in your configuration.`,
"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
"oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
"groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`,
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index 9d2a5c3cd5f..2dd0fd04ab7 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -210,7 +210,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
"adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
- "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`,
+ "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAds" in your configuration.`,
"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
"oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
"groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`,
@@ -224,7 +224,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
"adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
- "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`,
+ "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAds" in your configuration.`,
"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
"oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
"groupm": `Bidder "groupm" is no longer available in Prebid Server. Please update your configuration.`,
@@ -238,7 +238,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
"adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
- "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`,
+ "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAds" in your configuration.`,
"appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
"oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
@@ -253,7 +253,7 @@ func TestGetDisabledBiddersErrorMessages(t *testing.T) {
"lifestreet": `Bidder "lifestreet" is no longer available in Prebid Server. Please update your configuration.`,
"adagio": `Bidder "adagio" is no longer available in Prebid Server. Please update your configuration.`,
"somoaudience": `Bidder "somoaudience" is no longer available in Prebid Server. Please update your configuration.`,
- "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAdvertising" in your configuration.`,
+ "yssp": `Bidder "yssp" is no longer available in Prebid Server. If you're looking to use the Yahoo SSP adapter, please rename it to "yahooAds" in your configuration.`,
"appnexus": `Bidder "appnexus" has been disabled on this instance of Prebid Server. Please work with the PBS host to enable this bidder again.`,
"andbeyondmedia": `Bidder "andbeyondmedia" is no longer available in Prebid Server. If you're looking to use the AndBeyond.Media SSP adapter, please rename it to "beyondmedia" in your configuration.`,
"oftmedia": `Bidder "oftmedia" is no longer available in Prebid Server. Please update your configuration.`,
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 24ca193f989..5852b53caac 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -14,6 +14,11 @@ import (
"strings"
"time"
+ "github.com/buger/jsonparser"
+ uuid "github.com/gofrs/uuid"
+ "github.com/golang/glog"
+ "github.com/prebid/openrtb/v19/openrtb2"
+ "github.com/prebid/openrtb/v19/openrtb3"
"github.com/prebid/prebid-server/adapters"
"github.com/prebid/prebid-server/adservertargeting"
"github.com/prebid/prebid-server/bidadjustment"
@@ -30,16 +35,11 @@ import (
"github.com/prebid/prebid-server/metrics"
"github.com/prebid/prebid-server/openrtb_ext"
"github.com/prebid/prebid-server/prebid_cache_client"
+ "github.com/prebid/prebid-server/privacy"
"github.com/prebid/prebid-server/stored_requests"
"github.com/prebid/prebid-server/stored_responses"
"github.com/prebid/prebid-server/usersync"
"github.com/prebid/prebid-server/util/maputil"
-
- "github.com/buger/jsonparser"
- "github.com/gofrs/uuid"
- "github.com/golang/glog"
- "github.com/prebid/openrtb/v19/openrtb2"
- "github.com/prebid/openrtb/v19/openrtb3"
)
type extCacheInstructions struct {
@@ -195,6 +195,7 @@ type AuctionRequest struct {
GlobalPrivacyControlHeader string
ImpExtInfoMap map[string]ImpExtInfo
TCF2Config gdpr.TCF2ConfigReader
+ Activities privacy.ActivityControl
// LegacyLabels is included here for temporary compatibility with cleanOpenRTBRequests
// in HoldAuction until we get to factoring it away. Do not use for anything new.
@@ -348,13 +349,12 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
liveAdapters []openrtb_ext.BidderName
)
- if len(r.StoredAuctionResponses) > 0 {
- adapterBids, fledge, liveAdapters, err = buildStoredAuctionResponse(r.StoredAuctionResponses)
+ shouldMspBackfillBids := mspUpdateStoredAuctionResponse(r)
+ if len(r.StoredAuctionResponses) > 0 && !shouldMspBackfillBids {
+ adapterBids, fledge, liveAdapters, err, anyBidsReturned = mspApplyStoredAuctionResponse(r)
if err != nil {
return nil, err
}
- anyBidsReturned = true
-
} else {
// List of bidders we have requests for.
liveAdapters = listBiddersWithRequests(bidderRequests)
@@ -367,6 +367,10 @@ func (e *exchange) HoldAuction(ctx context.Context, r *AuctionRequest, debugLog
alternateBidderCodes = *r.Account.AlternateBidderCodes
}
adapterBids, adapterExtra, fledge, anyBidsReturned = e.getAllBids(auctionCtx, bidderRequests, bidAdjustmentFactors, conversions, accountDebugAllow, r.GlobalPrivacyControlHeader, debugLog.DebugOverride, alternateBidderCodes, requestExtLegacy.Prebid.Experiment, r.HookExecutor, r.StartTime, bidAdjustmentRules)
+ adapterBids, fledge, liveAdapters, err, anyBidsReturned = mspPostProcessAuction(r, liveAdapters, adapterBids, adapterExtra, fledge, anyBidsReturned, shouldMspBackfillBids)
+ if err != nil {
+ return nil, err
+ }
r.MakeBidsTimeInfo = buildMakeBidsTimeInfoMap(adapterExtra)
}
@@ -651,6 +655,86 @@ func (e *exchange) makeAuctionContext(ctx context.Context, needsCache bool) (auc
return
}
+func (e *exchange) processBidder(
+ ctx context.Context,
+ bidderRequests []BidderRequest,
+ bidAdjustments map[string]float64,
+ conversions currency.Conversions,
+ accountDebugAllowed bool,
+ globalPrivacyControlHeader string,
+ headerDebugAllowed bool,
+ alternateBidderCodes openrtb_ext.ExtAlternateBidderCodes,
+ experiment *openrtb_ext.Experiment,
+ hookExecutor hookexecution.StageExecutor,
+ bidAdjustmentRules map[string][]openrtb_ext.Adjustment,
+ chBids chan *bidResponseWrapper,
+ bidder BidderRequest) {
+ // Here we actually call the adapters and collect the bids.
+ bidderRunner := e.recoverSafely(bidderRequests, func(bidderRequest BidderRequest, conversions currency.Conversions) {
+ // Passing in aName so a doesn't change out from under the go routine
+ if bidderRequest.BidderLabels.Adapter == "" {
+ glog.Errorf("Exchange: bidlables for %s (%s) missing adapter string", bidderRequest.BidderName, bidderRequest.BidderCoreName)
+ bidderRequest.BidderLabels.Adapter = bidderRequest.BidderCoreName
+ }
+ brw := new(bidResponseWrapper)
+ brw.bidder = bidderRequest.BidderName
+ brw.adapter = bidderRequest.BidderCoreName
+ // Defer basic metrics to insure we capture them after all the values have been set
+ defer func() {
+ e.me.RecordAdapterRequest(bidderRequest.BidderLabels)
+ }()
+ start := time.Now()
+
+ reqInfo := adapters.NewExtraRequestInfo(conversions)
+ reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
+ reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader
+ reqInfo.BidderRequestStartTime = start
+
+ bidReqOptions := bidRequestOptions{
+ accountDebugAllowed: accountDebugAllowed,
+ headerDebugAllowed: headerDebugAllowed,
+ addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]),
+ bidAdjustments: bidAdjustments,
+ }
+
+ seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules)
+
+ // Add in time reporting
+ elapsed := time.Since(start)
+ brw.adapterSeatBids = seatBids
+ // Structure to record extra tracking data generated during bidding
+ ae := new(seatResponseExtra)
+ ae.ResponseTimeMillis = int(elapsed / time.Millisecond)
+ if len(seatBids) != 0 {
+ ae.HttpCalls = seatBids[0].HttpCalls
+ }
+ // SeatBidsPreparationStartTime is needed to calculate duration for openrtb response preparation time metric
+ // No metric needs to be logged for requests which error out
+ if err == nil {
+ ae.MakeBidsTimeInfo = reqInfo.MakeBidsTimeInfo
+ }
+ // Timing statistics
+ e.me.RecordAdapterTime(bidderRequest.BidderLabels, elapsed)
+ bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterSeatBids)
+ bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err)
+ // Append any bid validation errors to the error list
+ ae.Errors = errsToBidderErrors(err)
+ ae.Warnings = errsToBidderWarnings(err)
+ brw.adapterExtra = ae
+ for _, seatBid := range seatBids {
+ if seatBid != nil {
+ for _, bid := range seatBid.Bids {
+ var cpm = float64(bid.Bid.Price * 1000)
+ e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm)
+ e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.BidType, bid.Bid.AdM != "")
+ }
+ }
+ }
+ chBids <- brw
+ }, chBids)
+ go bidderRunner(bidder, conversions)
+}
+
// This piece sends all the requests to the bidder adapters and gathers the results.
func (e *exchange) getAllBids(
ctx context.Context,
@@ -676,71 +760,30 @@ func (e *exchange) getAllBids(
bidsFound := false
e.me.RecordOverheadTime(metrics.MakeBidderRequests, time.Since(pbsRequestStartTime))
+ lastPeekBidderRequests := []BidderRequest{}
+ msbConfig := extractMSBInfoBidders(bidderRequests)
for _, bidder := range bidderRequests {
- // Here we actually call the adapters and collect the bids.
- bidderRunner := e.recoverSafely(bidderRequests, func(bidderRequest BidderRequest, conversions currency.Conversions) {
- // Passing in aName so a doesn't change out from under the go routine
- if bidderRequest.BidderLabels.Adapter == "" {
- glog.Errorf("Exchange: bidlables for %s (%s) missing adapter string", bidderRequest.BidderName, bidderRequest.BidderCoreName)
- bidderRequest.BidderLabels.Adapter = bidderRequest.BidderCoreName
- }
- brw := new(bidResponseWrapper)
- brw.bidder = bidderRequest.BidderName
- brw.adapter = bidderRequest.BidderCoreName
- // Defer basic metrics to insure we capture them after all the values have been set
- defer func() {
- e.me.RecordAdapterRequest(bidderRequest.BidderLabels)
- }()
- start := time.Now()
-
- reqInfo := adapters.NewExtraRequestInfo(conversions)
- reqInfo.PbsEntryPoint = bidderRequest.BidderLabels.RType
- reqInfo.GlobalPrivacyControlHeader = globalPrivacyControlHeader
- reqInfo.BidderRequestStartTime = start
-
- bidReqOptions := bidRequestOptions{
- accountDebugAllowed: accountDebugAllowed,
- headerDebugAllowed: headerDebugAllowed,
- addCallSignHeader: isAdsCertEnabled(experiment, e.bidderInfo[string(bidderRequest.BidderName)]),
- bidAdjustments: bidAdjustments,
- }
- seatBids, err := e.adapterMap[bidderRequest.BidderCoreName].requestBid(ctx, bidderRequest, conversions, &reqInfo, e.adsCertSigner, bidReqOptions, alternateBidderCodes, hookExecutor, bidAdjustmentRules)
-
- // Add in time reporting
- elapsed := time.Since(start)
- brw.adapterSeatBids = seatBids
- // Structure to record extra tracking data generated during bidding
- ae := new(seatResponseExtra)
- ae.ResponseTimeMillis = int(elapsed / time.Millisecond)
- if len(seatBids) != 0 {
- ae.HttpCalls = seatBids[0].HttpCalls
- }
- // SeatBidsPreparationStartTime is needed to calculate duration for openrtb response preparation time metric
- // No metric needs to be logged for requests which error out
- if err == nil {
- ae.MakeBidsTimeInfo = reqInfo.MakeBidsTimeInfo
- }
- // Timing statistics
- e.me.RecordAdapterTime(bidderRequest.BidderLabels, elapsed)
- bidderRequest.BidderLabels.AdapterBids = bidsToMetric(brw.adapterSeatBids)
- bidderRequest.BidderLabels.AdapterErrors = errorsToMetric(err)
- // Append any bid validation errors to the error list
- ae.Errors = errsToBidderErrors(err)
- ae.Warnings = errsToBidderWarnings(err)
- brw.adapterExtra = ae
- for _, seatBid := range seatBids {
- if seatBid != nil {
- for _, bid := range seatBid.Bids {
- var cpm = float64(bid.Bid.Price * 1000)
- e.me.RecordAdapterPrice(bidderRequest.BidderLabels, cpm)
- e.me.RecordAdapterBidReceived(bidderRequest.BidderLabels, bid.BidType, bid.Bid.AdM != "")
- }
- }
- }
- chBids <- brw
- }, chBids)
- go bidderRunner(bidder, conversions)
+ // save 2nd - nth peek tier bidder requests and process later
+ // if needed add in the future
+
+ // save last peek bidder requests and process later
+ bidderName := bidder.BidderName.String()
+ if _, isLastPeekBidder := msbConfig.LastPeek.PeekBidderFloorMultMap[bidderName]; isLastPeekBidder {
+ lastPeekBidderRequests = append(lastPeekBidderRequests, bidder)
+ continue
+ }
+ e.processBidder(ctx, bidderRequests, bidAdjustments, conversions, accountDebugAllowed, globalPrivacyControlHeader, headerDebugAllowed, alternateBidderCodes, experiment, hookExecutor, bidAdjustmentRules, chBids, bidder)
+ }
+
+ // process msb 2nd - nth peek tier bidder request:
+ // if needed add in the future
+
+ // process msb last peek bidder requests:
+ if len(lastPeekBidderRequests) > 0 {
+ for _, bidder := range mspUpdateLastPeekBiddersRequest(chBids, lastPeekBidderRequests, msbConfig.LastPeek, len(bidderRequests)-len(lastPeekBidderRequests)) {
+ e.processBidder(ctx, bidderRequests, bidAdjustments, conversions, accountDebugAllowed, globalPrivacyControlHeader, headerDebugAllowed, alternateBidderCodes, experiment, hookExecutor, bidAdjustmentRules, chBids, bidder)
+ }
}
var fledge *openrtb_ext.Fledge
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 511fd84b71a..8306849a762 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -2085,7 +2085,7 @@ func getTestBuildRequest(t *testing.T) *openrtb2.BidRequest {
H: 600,
}},
},
- Ext: json.RawMessage(`{"ext_field":"value}"}`),
+ Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`),
}, {
Video: &openrtb2.Video{
MIMEs: []string{"video/mp4"},
@@ -2094,7 +2094,7 @@ func getTestBuildRequest(t *testing.T) *openrtb2.BidRequest {
W: 300,
H: 600,
},
- Ext: json.RawMessage(`{"ext_field":"value}"}`),
+ Ext: json.RawMessage(`{"prebid":{"bidder":{"appnexus": {"placementId": 1}}}}`),
}},
}
}
@@ -3592,7 +3592,7 @@ func TestApplyDealSupport(t *testing.T) {
{
description: "hb_pb_cat_dur should be modified",
dealPriority: 5,
- impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
+ impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
targ: map[string]string{
"hb_pb_cat_dur": "12.00_movies_30s",
},
@@ -3603,7 +3603,7 @@ func TestApplyDealSupport(t *testing.T) {
{
description: "hb_pb_cat_dur should not be modified due to priority not exceeding min",
dealPriority: 9,
- impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}`),
+ impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 10, "prefix": "tier"}, "placementId": 10433394}}}}`),
targ: map[string]string{
"hb_pb_cat_dur": "12.00_medicine_30s",
},
@@ -3614,7 +3614,7 @@ func TestApplyDealSupport(t *testing.T) {
{
description: "hb_pb_cat_dur should not be modified due to invalid config",
dealPriority: 5,
- impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}`),
+ impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": ""}, "placementId": 10433394}}}}`),
targ: map[string]string{
"hb_pb_cat_dur": "12.00_games_30s",
},
@@ -3625,7 +3625,7 @@ func TestApplyDealSupport(t *testing.T) {
{
description: "hb_pb_cat_dur should not be modified due to deal priority of 0",
dealPriority: 0,
- impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
+ impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
targ: map[string]string{
"hb_pb_cat_dur": "12.00_auto_30s",
},
@@ -3695,11 +3695,11 @@ func TestApplyDealSupportMultiBid(t *testing.T) {
Imp: []openrtb2.Imp{
{
ID: "imp_id1",
- Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
+ Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
},
{
ID: "imp_id1",
- Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
+ Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
},
},
},
@@ -3741,11 +3741,11 @@ func TestApplyDealSupportMultiBid(t *testing.T) {
Imp: []openrtb2.Imp{
{
ID: "imp_id1",
- Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
+ Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
},
{
ID: "imp_id1",
- Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
+ Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
},
},
},
@@ -3792,11 +3792,11 @@ func TestApplyDealSupportMultiBid(t *testing.T) {
Imp: []openrtb2.Imp{
{
ID: "imp_id1",
- Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
+ Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
},
{
ID: "imp_id1",
- Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}`),
+ Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}, "placementId": 10433394}}}}`),
},
},
},
@@ -3869,7 +3869,7 @@ func TestGetDealTiers(t *testing.T) {
description: "One",
request: openrtb2.BidRequest{
Imp: []openrtb2.Imp{
- {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}}}`)},
+ {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier"}}}}}`)},
},
},
expected: map[string]openrtb_ext.DealTierBidderMap{
@@ -3880,8 +3880,8 @@ func TestGetDealTiers(t *testing.T) {
description: "Many",
request: openrtb2.BidRequest{
Imp: []openrtb2.Imp{
- {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}`)},
- {ID: "imp2", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 8, "prefix": "tier2"}}}`)},
+ {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}}}`)},
+ {ID: "imp2", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 8, "prefix": "tier2"}}}}}`)},
},
},
expected: map[string]openrtb_ext.DealTierBidderMap{
@@ -3893,8 +3893,8 @@ func TestGetDealTiers(t *testing.T) {
description: "Many - Skips Malformed",
request: openrtb2.BidRequest{
Imp: []openrtb2.Imp{
- {ID: "imp1", Ext: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}`)},
- {ID: "imp2", Ext: json.RawMessage(`{"appnexus": {"dealTier": "wrong type"}}`)},
+ {ID: "imp1", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "tier1"}}}}}`)},
+ {ID: "imp2", Ext: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": "wrong type"}}}}`)},
},
},
expected: map[string]openrtb_ext.DealTierBidderMap{
diff --git a/exchange/msp_bidders.go b/exchange/msp_bidders.go
new file mode 100644
index 00000000000..1fd9730f11d
--- /dev/null
+++ b/exchange/msp_bidders.go
@@ -0,0 +1,43 @@
+package exchange
+
+import (
+ "github.com/golang/glog"
+ "github.com/prebid/prebid-server/adapters"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/openrtb_ext"
+
+ mspPlugin "github.com/prebid/prebid-server/msp/plugin"
+)
+
+type PluginBuilder interface {
+ Build(openrtb_ext.BidderName, config.Adapter, config.Server) (adapters.Bidder, error)
+}
+
+func mspLoadBidderAdapterPlugins(cfg config.BidderInfos) map[openrtb_ext.BidderName]adapters.Builder {
+ plugins := make(map[openrtb_ext.BidderName]adapters.Builder)
+
+ for name, bidderInfo := range cfg {
+ if bidderInfo.MspSoPath != "" {
+ builder, err := mspPlugin.LoadBuilderFromPath[PluginBuilder](name, bidderInfo.MspSoPath)
+
+ if err != nil {
+ panic(err)
+ }
+
+ bidderName := openrtb_ext.BidderName(name)
+ plugins[bidderName] = builder.Build
+ glog.Infof("Loaded Bidder Adapter plugin: %s\n", name)
+ }
+ }
+
+ return plugins
+}
+
+func mspAddAdaptersFromPlugins(adapters map[openrtb_ext.BidderName]adapters.Builder, cfg config.BidderInfos) map[openrtb_ext.BidderName]adapters.Builder {
+ pluginAdatpers := mspLoadBidderAdapterPlugins(cfg)
+ for key, val := range pluginAdatpers {
+ adapters[key] = val
+ }
+
+ return adapters
+}
diff --git a/exchange/msp_exchange.go b/exchange/msp_exchange.go
new file mode 100644
index 00000000000..6508f121ec7
--- /dev/null
+++ b/exchange/msp_exchange.go
@@ -0,0 +1,227 @@
+package exchange
+
+import (
+ "encoding/json"
+ "math"
+ "time"
+
+ "github.com/buger/jsonparser"
+ "github.com/golang/glog"
+ "github.com/prebid/openrtb/v19/openrtb2"
+ "github.com/prebid/prebid-server/exchange/entities"
+ "github.com/prebid/prebid-server/openrtb_ext"
+ jsonpatch "gopkg.in/evanphx/json-patch.v4"
+)
+
+const (
+ MSP_SEAT_IN_HOUSE = "msp-in-house"
+)
+
+type MSBExt struct {
+ MSB MSBConfig `json:"msb"`
+}
+
+type MSBConfig struct {
+ LastPeek MSBLastPeekConfig `json:"last_peek"`
+}
+
+type MSBLastPeekConfig struct {
+ // start peek available bids after this
+ PeekStartTimeMilliSeconds int64 `json:"peek_start_time_miliseconds"`
+ // bidder -> FloorMult
+ // set floor for last peek bidder = current_available_max_bid_price * FloorMult to:
+ // 1. break tie
+ // 2. gain more revenue
+ PeekBidderFloorMultMap map[string]float64 `json:"peek_bidder_floor_mult_map"`
+}
+
+/*
+MSB feature controller(req.ext) example:
+{
+ "ext": {
+ "msb": {
+ "last_peek": {
+ "peek_start_time_miliseconds": 900,
+ "peek_bidder_floor_mult_map": {
+ "msp_google": 1.01,
+ "msp_nova": 1.01
+ }
+ }
+ }
+ }
+}
+MSP server is response for adding above MSB info to requests for certain traffic/placement/exps. peek_bidder_floor_mult_map controls which bidders
+are there for different peek tiers
+
+In the bove example
+ there are two peek tiers for bidders:
+ 1. last peek tier
+ 2. the rest(normal tier)
+
+ after all reponses are ready for normal tier or timeout=peek_start_time_miliseconds(900ms), for bidders in last peek tier(msp_google and msp_nova) peek available responses from normal tier,
+ get max_available_bid_prices_for_normal_tier and set floor = max_available_bid_prices_for_normal_tier * peek_bidder_floor_mult_map[bidder]
+*/
+
+type MSPFloor struct {
+ Floor float64 `json:"floor"`
+}
+
+var mspBidders = map[openrtb_ext.BidderName]int{
+ openrtb_ext.BidderMspGoogle: 1,
+ openrtb_ext.BidderMspFbAlpha: 1,
+ openrtb_ext.BidderMspFbBeta: 1,
+ openrtb_ext.BidderMspFbGamma: 1,
+ openrtb_ext.BidderMspNova: 1,
+}
+
+func mspUpdateStoredAuctionResponse(r *AuctionRequest) bool {
+ if len(r.StoredAuctionResponses) > 0 {
+ if rawSeatBid, ok := r.StoredAuctionResponses[r.BidRequestWrapper.Imp[0].ID]; ok {
+ var seatBids []openrtb2.SeatBid
+
+ err := json.Unmarshal(rawSeatBid, &seatBids)
+ if err == nil && len(seatBids) > 0 && seatBids[0].Seat == MSP_SEAT_IN_HOUSE {
+ // when price is the same, randomly choose one
+ swapIdx := time.Now().Second() % len(seatBids[0].Bid)
+ seatBids[0].Bid[0], seatBids[0].Bid[swapIdx] = seatBids[0].Bid[swapIdx], seatBids[0].Bid[0]
+ updatedJson, _ := json.Marshal(seatBids)
+ r.StoredAuctionResponses[r.BidRequestWrapper.Imp[0].ID] = updatedJson
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+func mspApplyStoredAuctionResponse(r *AuctionRequest) (map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
+ *openrtb_ext.Fledge,
+ []openrtb_ext.BidderName,
+ error, bool) {
+ adapterBids, fledge, liveAdapters, err := buildStoredAuctionResponse(r.StoredAuctionResponses)
+ return adapterBids, fledge, liveAdapters, err, err == nil
+}
+
+func mspPostProcessAuction(
+ r *AuctionRequest,
+ liveAdapters []openrtb_ext.BidderName,
+ adapterBids map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
+ adapterExtra map[openrtb_ext.BidderName]*seatResponseExtra,
+ fledge *openrtb_ext.Fledge,
+ anyBidsReturned bool,
+ shouldMspBackfillBids bool) (map[openrtb_ext.BidderName]*entities.PbsOrtbSeatBid,
+ *openrtb_ext.Fledge,
+ []openrtb_ext.BidderName,
+ error, bool) {
+ // TODO: freqcap, blocking
+
+ if shouldMspBackfillBids && !anyBidsReturned {
+ return mspApplyStoredAuctionResponse(r)
+ }
+
+ return adapterBids, fledge, liveAdapters, nil, anyBidsReturned
+}
+
+// peek max available(ready) bid price from channel without comsuming
+func peekChannelAvailableMaxBidPriceWithinTimeout(peekTier string, chBids chan *bidResponseWrapper, lastPeekConfig MSBLastPeekConfig, totalNormalBidders int) float64 {
+ timeout := time.After(time.Duration(lastPeekConfig.PeekStartTimeMilliSeconds) * time.Millisecond)
+ maxPrice := 0.0
+ hasData := true
+ peekedRespList := []*bidResponseWrapper{}
+ availableBidders := []string{}
+ // keep consuming message from channel until all normal bidder requests are collected or timeout reaches
+ for hasData {
+ select {
+ case resp, ok := <-chBids:
+ if !ok {
+ hasData = false
+ } else {
+ for _, bids := range resp.adapterSeatBids {
+ for _, bid := range bids.Bids {
+ if bid.Bid != nil {
+ maxPrice = math.Max(maxPrice, bid.Bid.Price)
+ }
+ }
+ }
+ peekedRespList = append(peekedRespList, resp)
+ availableBidders = append(availableBidders, resp.bidder.String())
+ if len(peekedRespList) == totalNormalBidders {
+ hasData = false
+ }
+ }
+
+ case <-timeout:
+ hasData = false
+ }
+
+ }
+ // push message back
+ for _, resp := range peekedRespList {
+ chBids <- resp
+ }
+ glog.Infof("MSB tier %s, peeked from available bidders %v, current max bid price: %f", peekTier, availableBidders, maxPrice)
+ return maxPrice
+}
+
+func mspUpdateLastPeekBiddersRequest(
+ chBids chan *bidResponseWrapper,
+ lastPeekBidderRequests []BidderRequest,
+ lastPeekConfig MSBLastPeekConfig,
+ totalNormalBidders int,
+) []BidderRequest {
+ maxPrice := peekChannelAvailableMaxBidPriceWithinTimeout("lastPeek", chBids, lastPeekConfig, totalNormalBidders)
+ for reqIdx := range lastPeekBidderRequests {
+ mult := lastPeekConfig.PeekBidderFloorMultMap[lastPeekBidderRequests[reqIdx].BidderName.String()]
+ updatedFloor := mult * maxPrice
+ for idx := range lastPeekBidderRequests[reqIdx].BidRequest.Imp {
+ bidder := &lastPeekBidderRequests[reqIdx]
+ // update req.imp.bidfloor
+ bidder.BidRequest.Imp[idx].BidFloor = math.Max(bidder.BidRequest.Imp[idx].BidFloor, updatedFloor)
+
+ // for msp bidders, update req.imp.ext.bidder.floor which is the source of truth for msp bidder's floor and
+ // will be updated/overwritten later by msp module stage: https://github.com/ParticleMedia/msp/blob/master/pkg/modules/dam_buckets/module/hook_bidder_request.go#L69
+ if _, found := mspBidders[bidder.BidderName]; found {
+ extBytes, err := jsonObject(bidder.BidRequest.Imp[idx].Ext, "bidder")
+ if err == nil {
+ var impExt MSPFloor
+ err = json.Unmarshal(extBytes, &impExt)
+ if err == nil {
+ impExt.Floor = math.Max(impExt.Floor, updatedFloor)
+ updatedBytes, _ := json.Marshal(impExt)
+ updatedBidderBytes, _ := jsonpatch.MergePatch(extBytes, updatedBytes)
+ updatedExtBytes, _ := jsonparser.Set(bidder.BidRequest.Imp[idx].Ext, updatedBidderBytes, "bidder")
+ bidder.BidRequest.Imp[idx].Ext = updatedExtBytes
+ }
+ }
+ }
+ }
+ }
+ return lastPeekBidderRequests
+}
+
+func jsonObject(data []byte, keys ...string) ([]byte, error) {
+ if result, dataType, _, err := jsonparser.Get(data, keys...); err == nil && dataType == jsonparser.Object {
+ return result, nil
+ } else {
+ return nil, err
+ }
+}
+
+func extractMSBInfoBidders(reqList []BidderRequest) MSBConfig {
+ if len(reqList) > 0 {
+ return ExtractMSBInfoReq(reqList[0].BidRequest)
+ }
+
+ return MSBConfig{}
+}
+
+func ExtractMSBInfoReq(req *openrtb2.BidRequest) MSBConfig {
+ var config MSBExt
+ if req != nil {
+ err := json.Unmarshal(req.Ext, &config)
+ if err != nil {
+ glog.Error("MSB extract bidder config:", err)
+ }
+ }
+ return config.MSB
+}
diff --git a/exchange/utils.go b/exchange/utils.go
index d43a4182451..5b29fd6292f 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -150,6 +150,15 @@ func (rs *requestSplitter) cleanOpenRTBRequests(ctx context.Context,
for _, bidderRequest := range allBidderRequests {
bidRequestAllowed := true
+ // fetchBids activity
+ fetchBidsActivityAllowed := auctionReq.Activities.Allow(privacy.ActivityFetchBids,
+ privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidderRequest.BidderName.String()})
+ if fetchBidsActivityAllowed == privacy.ActivityDeny {
+ // skip the call to a bidder if fetchBids activity is not allowed
+ // do not add this bidder to allowedBidderRequests
+ continue
+ }
+
// CCPA
privacyEnforcement.CCPA = ccpaEnforcer.ShouldEnforce(bidderRequest.BidderName.String())
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 8bb82fe412f..973edc53355 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -17,6 +17,7 @@ import (
"github.com/prebid/prebid-server/gdpr"
"github.com/prebid/prebid-server/metrics"
"github.com/prebid/prebid-server/openrtb_ext"
+ "github.com/prebid/prebid-server/privacy"
"github.com/prebid/prebid-server/util/ptrutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
@@ -505,7 +506,7 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) {
App: &openrtb2.App{Name: "fpdApnApp"},
User: &openrtb2.User{Keywords: "fpdApnUser"},
}
- fpd[openrtb_ext.BidderName("appnexus")] = &apnFpd
+ fpd[openrtb_ext.BidderName("rubicon")] = &apnFpd
brightrollFpd := firstpartydata.ResolvedFirstPartyData{
Site: &openrtb2.Site{Name: "fpdBrightrollSite"},
@@ -4273,3 +4274,72 @@ func TestGetMediaTypeForBid(t *testing.T) {
})
}
}
+
+func TemporarilyDisabledTestCleanOpenRTBRequestsActivitiesFetchBids(t *testing.T) {
+ testCases := []struct {
+ name string
+ req *openrtb2.BidRequest
+ componentName string
+ allow bool
+ expectedReqNumber int
+ }{
+ {
+ name: "request_with_one_bidder_allowed",
+ req: newBidRequest(t),
+ componentName: "appnexus",
+ allow: true,
+ expectedReqNumber: 1,
+ },
+ {
+ name: "request_with_one_bidder_not_allowed",
+ req: newBidRequest(t),
+ componentName: "appnexus",
+ allow: false,
+ expectedReqNumber: 0,
+ },
+ }
+
+ for _, test := range testCases {
+ privacyConfig := getDefaultActivityConfig(test.componentName, test.allow)
+ activities, err := privacy.NewActivityControl(privacyConfig)
+ assert.NoError(t, err, "")
+ auctionReq := AuctionRequest{
+ BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req},
+ UserSyncs: &emptyUsersync{},
+ Activities: activities,
+ }
+
+ bidderToSyncerKey := map[string]string{}
+ reqSplitter := &requestSplitter{
+ bidderToSyncerKey: bidderToSyncerKey,
+ me: &metrics.MetricsEngineMock{},
+ hostSChainNode: nil,
+ bidderInfo: config.BidderInfos{},
+ }
+
+ t.Run(test.name, func(t *testing.T) {
+ bidderRequests, _, errs := reqSplitter.cleanOpenRTBRequests(context.Background(), auctionReq, nil, gdpr.SignalNo)
+ assert.Empty(t, errs)
+ assert.Len(t, bidderRequests, test.expectedReqNumber)
+ })
+ }
+}
+
+func getDefaultActivityConfig(componentName string, allow bool) *config.AccountPrivacy {
+ return &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ FetchBids: config.Activity{
+ Default: ptrutil.ToPtr(true),
+ Rules: []config.ActivityRule{
+ {
+ Allow: allow,
+ Condition: config.ActivityCondition{
+ ComponentName: []string{componentName},
+ ComponentType: []string{"bidder"},
+ },
+ },
+ },
+ },
+ },
+ }
+}
diff --git a/go.mod b/go.mod
index 3833ef3d9e5..5c1344c1300 100644
--- a/go.mod
+++ b/go.mod
@@ -27,20 +27,20 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475
github.com/rs/cors v1.8.2
github.com/spf13/viper v1.12.0
- github.com/stretchr/testify v1.8.1
+ github.com/stretchr/testify v1.8.4
github.com/vrischmann/go-metrics-influxdb v0.1.1
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yudai/gojsondiff v1.0.0
golang.org/x/net v0.7.0
- golang.org/x/text v0.7.0
- google.golang.org/grpc v1.46.2
+ golang.org/x/text v0.13.0
+ google.golang.org/grpc v1.53.0
gopkg.in/evanphx/json-patch.v4 v4.12.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/beorn7/perks v1.0.1 // indirect
- github.com/cespare/xxhash/v2 v2.1.2 // indirect
+ github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/golang/protobuf v1.5.2 // indirect
@@ -67,9 +67,9 @@ require (
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
- golang.org/x/sys v0.5.0 // indirect
- google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd // indirect
- google.golang.org/protobuf v1.28.0 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect
+ google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
diff --git a/go.sum b/go.sum
index e1226ab1f3c..ccf5c4a70a3 100644
--- a/go.sum
+++ b/go.sum
@@ -86,8 +86,9 @@ github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA
github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
-github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
+github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chasex/glog v0.0.0-20160217080310-c62392af379c h1:eXqCBUHfmjbeDqcuvzjsd+bM6A+bnwo5N9FVbV6m5/s=
github.com/chasex/glog v0.0.0-20160217080310-c62392af379c/go.mod h1:omJZNg0Qu76bxJd+ExohVo8uXzNcGOk2bv7vel460xk=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
@@ -125,7 +126,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.1/go.mod h1:AY7fTTXNdv/aJ2O5jwpxAPOWUZ7hQAEvzN5Pf27BkQQ=
-github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.6.2/go.mod h1:2t7qjJNvHPx8IjnBOzl9E9/baC+qXE/TeeyBRzgJDws=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@@ -210,7 +210,7 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
-github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
@@ -392,8 +392,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
-github.com/prebid/go-gdpr v1.11.0 h1:QbMjscuw3Ul0mDVWeMy5tP0Kii6lmTSSVhV6fm8rY9s=
-github.com/prebid/go-gdpr v1.11.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY=
github.com/prebid/go-gdpr v1.12.0 h1:OrjQ7Uc+lCRYaOirQ48jjG/PBMvZsKNAaRTgzxN6iZ0=
github.com/prebid/go-gdpr v1.12.0/go.mod h1:mPZAdkRxn+iuSjaUuJAi9+0SppBOdM1PCzv/55UH3pY=
github.com/prebid/go-gpp v0.1.1 h1:uTMJ+eHmKWL9WvDuxFT4LDoOeJW1yOsfWITqi49ZuY0=
@@ -473,8 +471,9 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/subosito/gotenv v1.3.0 h1:mjC+YW8QpAdXibNi+vNWgzmgBH4+5l5dCXv8cNysBLI=
github.com/subosito/gotenv v1.3.0/go.mod h1:YzJjq/33h7nrwdY+iHMhEOEEbW0ovIz0tB6t6PwAXzs=
@@ -724,8 +723,8 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -738,8 +737,8 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -910,8 +909,8 @@ google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ6
google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd h1:e0TwkXOdbnH/1x5rc5MZ/VYyiZ4v+RdVfrGMqEwT68I=
-google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
+google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w=
+google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
@@ -942,9 +941,8 @@ google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9K
google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
-google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
-google.golang.org/grpc v1.46.2 h1:u+MLGgVf7vRdjEYZ8wDFhAVNmhkbJ5hmrA1LMWK1CAQ=
-google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
+google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc=
+google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
@@ -959,8 +957,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
-google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
+google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/metrics/prometheus/prometheus.go b/metrics/prometheus/prometheus.go
index eb387789e1d..c61a3e253e9 100644
--- a/metrics/prometheus/prometheus.go
+++ b/metrics/prometheus/prometheus.go
@@ -174,7 +174,7 @@ const (
// NewMetrics initializes a new Prometheus metrics instance with preloaded label values.
func NewMetrics(cfg config.PrometheusMetrics, disabledMetrics config.DisabledMetrics, syncerKeys []string, moduleStageNames map[string][]string) *Metrics {
- standardTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1}
+ standardTimeBuckets := []float64{0.05, 0.1, 0.15, 0.20, 0.25, 0.3, 0.4, 0.5, 0.75, 1, 1.2, 1.4, 1.6, 1.8, 2.0, 2.5, 3}
cacheWriteTimeBuckets := []float64{0.001, 0.002, 0.005, 0.01, 0.025, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1}
priceBuckets := []float64{250, 500, 750, 1000, 1500, 2000, 2500, 3000, 3500, 4000}
queuedRequestTimeBuckets := []float64{0, 1, 5, 30, 60, 120, 180, 240, 300}
diff --git a/modules/modules.go b/modules/modules.go
index ac60ec58082..ccc933b84ab 100644
--- a/modules/modules.go
+++ b/modules/modules.go
@@ -83,6 +83,8 @@ func (m *builder) Build(
}
}
+ modules = mspLoadModulePlugins(modules, cfg, deps)
+
collection, err := createModuleStageNamesCollection(modules)
if err != nil {
return nil, nil, err
diff --git a/modules/msp_plugin.go b/modules/msp_plugin.go
new file mode 100644
index 00000000000..214cb3b4ca6
--- /dev/null
+++ b/modules/msp_plugin.go
@@ -0,0 +1,48 @@
+package modules
+
+import (
+ "encoding/json"
+ "fmt"
+
+ "github.com/golang/glog"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/modules/moduledeps"
+ mspPlugin "github.com/prebid/prebid-server/msp/plugin"
+)
+
+type PluginBuilder interface {
+ Build(json.RawMessage, moduledeps.ModuleDeps) (interface{}, error)
+}
+
+func mspLoadModulePlugins(modules map[string]interface{}, cfg config.Modules, deps moduledeps.ModuleDeps) map[string]interface{} {
+ for vendor, moduleBuilders := range cfg {
+ for moduleName, moduleCfg := range moduleBuilders {
+ id := fmt.Sprintf("%s.%s", vendor, moduleName)
+
+ if _, ok := modules[id]; ok {
+ // skip loading modules that have already been loaded through hardcoded builder
+ continue
+ }
+
+ builder, cfgJson, skip, err := mspPlugin.LoadBuilder[PluginBuilder](id, moduleCfg)
+
+ if skip {
+ continue
+ }
+
+ if err != nil {
+ panic(err)
+ }
+
+ module, err := builder.Build(cfgJson, deps)
+ if err != nil {
+ panic(fmt.Sprintf("Failed to build Module plugin %s, error: %+v\n", id, err))
+ }
+
+ modules[id] = module
+ glog.Infof("Loaded Module plugin %s.\n", id)
+ }
+ }
+
+ return modules
+}
diff --git a/msp/plugin/config.go b/msp/plugin/config.go
new file mode 100644
index 00000000000..baf20f9a4e9
--- /dev/null
+++ b/msp/plugin/config.go
@@ -0,0 +1,31 @@
+package plugin
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// General config for all plugins. Different plugins may have different configs structure, but
+// two fields are mandatory for all of them:
+// 1. `so_path`, a string value telling Prebid-server where to load the shared object.
+// 2. `enabled`, a boolean value telling whether or not to load it.
+type Config struct {
+ SoPath string `mapstructure:"so_path" json:"so_path"`
+ Enabled bool `mapstructure:"enabled" json:"enabled"`
+}
+
+func ParseConfig(name string, cfgData interface{}) (Config, json.RawMessage) {
+ data, err := json.Marshal(cfgData)
+ if err != nil {
+ message := fmt.Sprintf("Failed to marshal config of plugin %s, err: %+v\n", name, err)
+ panic(message)
+ }
+
+ var cfg Config
+ if err := json.Unmarshal(data, &cfg); err != nil {
+ message := fmt.Sprintf("Config of plugin %s is invalid , err: %+v\n", name, err)
+ panic(message)
+ }
+
+ return cfg, data
+}
diff --git a/msp/plugin/load.go b/msp/plugin/load.go
new file mode 100644
index 00000000000..492e0d956c2
--- /dev/null
+++ b/msp/plugin/load.go
@@ -0,0 +1,63 @@
+package plugin
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "plugin"
+
+ "github.com/golang/glog"
+)
+
+// Every plugin must export a symbol named `Builder`
+func LoadBuilder[T any](name string, cfgData interface{}) (T, json.RawMessage, bool, error) {
+ var builder T
+
+ cfg, cfgJson := ParseConfig(name, cfgData)
+ if !cfg.Enabled {
+ glog.Infof("Skip loading plugin %s as it is disabled.", name)
+ return builder, cfgJson, true, nil
+ }
+
+ if cfg.SoPath == "" {
+ return builder, cfgJson, false, errors.New(fmt.Sprintf("The path to load plugin %s is empty.\n", name))
+ }
+
+ p, err := plugin.Open(cfg.SoPath)
+ if err != nil {
+ return builder, cfgJson, false, errors.New(fmt.Sprintf("Failed to open shared object of plugin %s, err: %+v.\n", name, err))
+ }
+
+ s, err := p.Lookup("Builder")
+ if err != nil {
+ return builder, cfgJson, false, errors.New(fmt.Sprintf("Failed to find Builder from plugin %s, err: %+v.\n", name, err))
+ }
+
+ builder, ok := s.(T)
+ if !ok {
+ return builder, cfgJson, false, errors.New(fmt.Sprintf("Failed to convert Builder from plugin %s, err: %+v.\n", name, err))
+ }
+
+ return builder, cfgJson, false, nil
+}
+
+func LoadBuilderFromPath[T any](name string, soPath string) (T, error) {
+ var builder T
+
+ p, err := plugin.Open(soPath)
+ if err != nil {
+ return builder, errors.New(fmt.Sprintf("Failed to open shared object of plugin %s, err: %+v.\n", name, err))
+ }
+
+ s, err := p.Lookup("Builder")
+ if err != nil {
+ return builder, errors.New(fmt.Sprintf("Failed to find Builder from plugin %s, err: %+v.\n", name, err))
+ }
+
+ builder, ok := s.(T)
+ if !ok {
+ return builder, errors.New(fmt.Sprintf("Failed to convert Builder from plugin %s, err: %+v.\n", name, err))
+ }
+
+ return builder, nil
+}
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index 03056123423..b3e57f17b44 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -114,6 +114,7 @@ const (
BidderAdView BidderName = "adview"
BidderAdxcg BidderName = "adxcg"
BidderAdyoulike BidderName = "adyoulike"
+ BidderAidem BidderName = "aidem"
BidderAJA BidderName = "aja"
BidderAlgorix BidderName = "algorix"
BidderAMX BidderName = "amx"
@@ -269,6 +270,7 @@ const (
BidderVisx BidderName = "visx"
BidderVrtcal BidderName = "vrtcal"
BidderXeworks BidderName = "xeworks"
+ BidderYahooAds BidderName = "yahooAds"
BidderYahooAdvertising BidderName = "yahooAdvertising"
BidderYahooSSP BidderName = "yahoossp"
BidderYeahmobi BidderName = "yeahmobi"
@@ -277,6 +279,16 @@ const (
BidderYieldone BidderName = "yieldone"
BidderZeroClickFraud BidderName = "zeroclickfraud"
BidderZetaGlobalSsp BidderName = "zeta_global_ssp"
+
+ // For MSP extension only
+ BidderMspGoogle BidderName = "msp_google"
+ BidderMspNova BidderName = "msp_nova"
+ BidderMspNovaAlpha BidderName = "msp_nova_alpha"
+ BidderMspNovaBeta BidderName = "msp_nova_beta"
+ BidderMspNovaGamma BidderName = "msp_nova_gamma"
+ BidderMspFbAlpha BidderName = "msp_fb_alpha"
+ BidderMspFbBeta BidderName = "msp_fb_beta"
+ BidderMspFbGamma BidderName = "msp_fb_gamma"
)
// CoreBidderNames returns a slice of all core bidders.
@@ -310,6 +322,7 @@ func CoreBidderNames() []BidderName {
BidderAdView,
BidderAdxcg,
BidderAdyoulike,
+ BidderAidem,
BidderAJA,
BidderAlgorix,
BidderAMX,
@@ -465,6 +478,7 @@ func CoreBidderNames() []BidderName {
BidderVisx,
BidderVrtcal,
BidderXeworks,
+ BidderYahooAds,
BidderYahooAdvertising,
BidderYahooSSP,
BidderYeahmobi,
@@ -483,6 +497,11 @@ func BuildBidderMap() map[string]BidderName {
for _, name := range CoreBidderNames() {
lookup[string(name)] = name
}
+
+ // Add MSP Bidders
+ for _, name := range mspBidderNames() {
+ lookup[string(name)] = name
+ }
return lookup
}
@@ -493,6 +512,11 @@ func BuildBidderStringSlice() []string {
for i, name := range CoreBidderNames() {
slice[i] = string(name)
}
+
+ // Add MSP Bidders
+ for i, name := range mspBidderNames() {
+ slice[i] = string(name)
+ }
return slice
}
@@ -501,6 +525,11 @@ func BuildBidderNameHashSet() map[string]struct{} {
for _, name := range CoreBidderNames() {
hashSet[string(name)] = struct{}{}
}
+
+ // Add MSP Bidders
+ for _, name := range mspBidderNames() {
+ hashSet[string(name)] = struct{}{}
+ }
return hashSet
}
@@ -511,6 +540,12 @@ var bidderNameLookup = func() map[string]BidderName {
bidderNameLower := strings.ToLower(string(name))
lookup[bidderNameLower] = name
}
+
+ // Add MSP Bidders
+ for _, name := range mspBidderNames() {
+ bidderNameLower := strings.ToLower(string(name))
+ lookup[bidderNameLower] = name
+ }
return lookup
}()
diff --git a/openrtb_ext/bidders_validate_test.go b/openrtb_ext/bidders_validate_test.go
index 530f260c761..87a8c96d6f8 100644
--- a/openrtb_ext/bidders_validate_test.go
+++ b/openrtb_ext/bidders_validate_test.go
@@ -50,7 +50,7 @@ func TestBidderUniquenessGatekeeping(t *testing.T) {
// - Exclude duplicates of adapters for the same bidder, as it's unlikely a publisher will use both.
var bidders []string
for _, bidder := range CoreBidderNames() {
- if bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderFreewheelSSPOld {
+ if bidder != BidderTripleliftNative && bidder != BidderAdkernelAdn && bidder != BidderFreewheelSSPOld && bidder != BidderYahooAdvertising {
bidders = append(bidders, string(bidder))
}
}
diff --git a/openrtb_ext/deal_tier.go b/openrtb_ext/deal_tier.go
index ef85b9b1df8..45285d21663 100644
--- a/openrtb_ext/deal_tier.go
+++ b/openrtb_ext/deal_tier.go
@@ -27,20 +27,6 @@ func ReadDealTiersFromImp(imp openrtb2.Imp) (DealTierBidderMap, error) {
return dealTiers, nil
}
- // imp.ext.{bidder}
- var impExt map[string]struct {
- DealTier *DealTier `json:"dealTier"`
- }
- if err := json.Unmarshal(imp.Ext, &impExt); err != nil {
- return nil, err
- }
- for bidder, param := range impExt {
- if param.DealTier != nil {
- dealTiers[BidderName(bidder)] = *param.DealTier
- }
- }
-
- // imp.ext.prebid.{bidder}
var impPrebidExt struct {
Prebid struct {
Bidders map[string]struct {
diff --git a/openrtb_ext/deal_tier_test.go b/openrtb_ext/deal_tier_test.go
index 6aaebbab687..0046b788ece 100644
--- a/openrtb_ext/deal_tier_test.go
+++ b/openrtb_ext/deal_tier_test.go
@@ -31,55 +31,50 @@ func TestReadDealTiersFromImp(t *testing.T) {
expectedResult: DealTierBidderMap{},
},
{
- description: "imp.ext - with other params",
- impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}`),
- expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}},
+ description: "imp.ext - no prebid but with other params",
+ impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}, "tid": "1234"}`),
+ expectedResult: DealTierBidderMap{},
},
{
- description: "imp.ext - multiple",
- impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "appnexusPrefix"}, "placementId": 12345}, "rubicon": {"dealTier": {"minDealTier": 8, "prefix": "rubiconPrefix"}, "placementId": 12345}}`),
- expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "appnexusPrefix", MinDealTier: 5}, BidderRubicon: {Prefix: "rubiconPrefix", MinDealTier: 8}},
+ description: "imp.ext.prebid - nil",
+ impExt: json.RawMessage(`{"prebid": null}`),
+ expectedResult: DealTierBidderMap{},
},
{
- description: "imp.ext - no deal tier",
- impExt: json.RawMessage(`{"appnexus": {"placementId": 12345}}`),
+ description: "imp.ext.prebid - empty",
+ impExt: json.RawMessage(`{"prebid": {}}`),
expectedResult: DealTierBidderMap{},
},
{
- description: "imp.ext - error",
- impExt: json.RawMessage(`{"appnexus": {"dealTier": "wrong type", "placementId": 12345}}`),
- expectedError: "json: cannot unmarshal string into Go struct field .dealTier of type openrtb_ext.DealTier",
+ description: "imp.ext.prebid - no bidder but with other params",
+ impExt: json.RawMessage(`{"prebid": {"supportdeals": true}}`),
+ expectedResult: DealTierBidderMap{},
},
{
- description: "imp.ext.prebid",
+ description: "imp.ext.prebid.bidder - one",
impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}}}`),
expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}},
},
{
- description: "imp.ext.prebid- multiple",
+ description: "imp.ext.prebid.bidder - one with other params",
+ impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "anyPrefix"}, "placementId": 12345}}, "supportdeals": true}, "tid": "1234"}`),
+ expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "anyPrefix", MinDealTier: 5}},
+ },
+ {
+ description: "imp.ext.prebid.bidder - multiple",
impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "appnexusPrefix"}, "placementId": 12345}, "rubicon": {"dealTier": {"minDealTier": 8, "prefix": "rubiconPrefix"}, "placementId": 12345}}}}`),
expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "appnexusPrefix", MinDealTier: 5}, BidderRubicon: {Prefix: "rubiconPrefix", MinDealTier: 8}},
},
{
- description: "imp.ext.prebid - no deal tier",
+ description: "imp.ext.prebid.bidder - one without deal tier",
impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"placementId": 12345}}}}`),
expectedResult: DealTierBidderMap{},
},
{
- description: "imp.ext.prebid - error",
+ description: "imp.ext.prebid.bidder - error",
impExt: json.RawMessage(`{"prebid": {"bidder": {"appnexus": {"dealTier": "wrong type", "placementId": 12345}}}}`),
expectedError: "json: cannot unmarshal string into Go struct field .prebid.bidder.dealTier of type openrtb_ext.DealTier",
},
- {
- description: "imp.ext.prebid wins over imp.ext",
- impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "impExt"}, "placementId": 12345}, "prebid": {"bidder": {"appnexus": {"dealTier": {"minDealTier": 8, "prefix": "impExtPrebid"}, "placementId": 12345}}}}`),
- expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "impExtPrebid", MinDealTier: 8}},
- },
- {
- description: "imp.ext.prebid coexists with imp.ext",
- impExt: json.RawMessage(`{"appnexus": {"dealTier": {"minDealTier": 5, "prefix": "impExt"}, "placementId": 12345}, "prebid": {"bidder": {"rubicon": {"dealTier": {"minDealTier": 8, "prefix": "impExtPrebid"}, "placementId": 12345}}}}`),
- expectedResult: DealTierBidderMap{BidderAppnexus: {Prefix: "impExt", MinDealTier: 5}, BidderRubicon: {Prefix: "impExtPrebid", MinDealTier: 8}},
- },
}
for _, test := range testCases {
diff --git a/openrtb_ext/imp_aidem.go b/openrtb_ext/imp_aidem.go
new file mode 100644
index 00000000000..59457f1eb4a
--- /dev/null
+++ b/openrtb_ext/imp_aidem.go
@@ -0,0 +1,8 @@
+package openrtb_ext
+
+type ImpExtFoo struct {
+ SiteID string `json:"siteId"`
+ PublisherID string `json:"publisherId"`
+ PlacementID string `json:"placementId"`
+ RateLimit string `json:"rateLimit"`
+}
diff --git a/openrtb_ext/imp_ix.go b/openrtb_ext/imp_ix.go
index 9f977fb0dcd..40c3f51867f 100644
--- a/openrtb_ext/imp_ix.go
+++ b/openrtb_ext/imp_ix.go
@@ -4,4 +4,5 @@ package openrtb_ext
type ExtImpIx struct {
SiteId string `json:"siteId"`
Size []int `json:"size"`
+ Sid string `json:"sid"`
}
diff --git a/openrtb_ext/imp_rise.go b/openrtb_ext/imp_rise.go
index 4bdd302ee99..de37e4f800d 100644
--- a/openrtb_ext/imp_rise.go
+++ b/openrtb_ext/imp_rise.go
@@ -3,4 +3,6 @@ package openrtb_ext
// ImpExtRise defines the contract for bidrequest.imp[i].ext.prebid.bidder.rise
type ImpExtRise struct {
PublisherID string `json:"publisher_id"`
+ Org string `json:"org"`
+ PlacementID string `json:"placementId"`
}
diff --git a/openrtb_ext/imp_yahooAds.go b/openrtb_ext/imp_yahooAds.go
new file mode 100644
index 00000000000..36a4a0f618b
--- /dev/null
+++ b/openrtb_ext/imp_yahooAds.go
@@ -0,0 +1,7 @@
+package openrtb_ext
+
+// ExtImpYahooAds defines the contract for bidrequest.imp[i].ext.prebid.bidder.yahooAds
+type ExtImpYahooAds struct {
+ Dcn string `json:"dcn"`
+ Pos string `json:"pos"`
+}
diff --git a/openrtb_ext/msp_bidders.go b/openrtb_ext/msp_bidders.go
new file mode 100644
index 00000000000..f0805ebb185
--- /dev/null
+++ b/openrtb_ext/msp_bidders.go
@@ -0,0 +1,19 @@
+package openrtb_ext
+
+func mspBidderNames() []BidderName {
+ return []BidderName{
+ BidderMspGoogle,
+ BidderMspNova,
+ BidderMspNovaAlpha,
+ BidderMspNovaBeta,
+ BidderMspNovaGamma,
+ BidderMspFbAlpha,
+ BidderMspFbBeta,
+ BidderMspFbGamma,
+ }
+}
+
+func MspAllBidderNames() []BidderName {
+ core := CoreBidderNames()
+ return append(core, mspBidderNames()...)
+}
diff --git a/privacy/activity.go b/privacy/activity.go
index 7cdef17590c..6ca48ae0b93 100644
--- a/privacy/activity.go
+++ b/privacy/activity.go
@@ -11,6 +11,7 @@ const (
ActivityTransmitUserFPD
ActivityTransmitPreciseGeo
ActivityTransmitUniqueRequestIds
+ ActivityTransmitTids
)
func (a Activity) String() string {
@@ -29,6 +30,8 @@ func (a Activity) String() string {
return "transmitPreciseGeo"
case ActivityTransmitUniqueRequestIds:
return "transmitUniqueRequestIds"
+ case ActivityTransmitTids:
+ return "transmitTid"
}
return ""
diff --git a/privacy/enforcer.go b/privacy/enforcer.go
index 0d5ecad5309..d63cd8de31f 100644
--- a/privacy/enforcer.go
+++ b/privacy/enforcer.go
@@ -1,43 +1,236 @@
package privacy
-// PolicyEnforcer determines if personally identifiable information (PII) should be removed or anonymized per the policy.
-type PolicyEnforcer interface {
- // CanEnforce returns true when policy information is specifically provided by the publisher.
- CanEnforce() bool
+import (
+ "fmt"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/errortypes"
+ "strings"
+)
- // ShouldEnforce returns true when the OpenRTB request should have personally identifiable
- // information (PII) removed or anonymized per the policy.
- ShouldEnforce(bidder string) bool
+type ActivityResult int
+
+const (
+ ActivityAbstain ActivityResult = iota
+ ActivityAllow
+ ActivityDeny
+)
+
+const (
+ ScopeTypeBidder = "bidder"
+ ScopeTypeAnalytics = "analytics"
+ ScopeTypeRTD = "rtd" // real time data
+ ScopeTypeUserID = "userid"
+ ScopeTypeGeneral = "general"
+)
+
+type ActivityControl struct {
+ plans map[Activity]ActivityPlan
}
-// NilPolicyEnforcer implements the PolicyEnforcer interface but will always return false.
-type NilPolicyEnforcer struct{}
+func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, error) {
+ ac := ActivityControl{}
+ var err error
-// CanEnforce is hardcoded to always return false.
-func (NilPolicyEnforcer) CanEnforce() bool {
- return false
+ if privacyConf == nil {
+ return ac, err
+ } else {
+ //temporarily disable Activities if they are specified at the account level
+ return ac, &errortypes.Warning{Message: "account.Privacy has no effect as the feature is under development."}
+ }
+
+ plans := make(map[Activity]ActivityPlan)
+
+ plans[ActivitySyncUser], err = buildEnforcementPlan(privacyConf.AllowActivities.SyncUser)
+ if err != nil {
+ return ac, err
+ }
+ plans[ActivityFetchBids], err = buildEnforcementPlan(privacyConf.AllowActivities.FetchBids)
+ if err != nil {
+ return ac, err
+ }
+ plans[ActivityEnrichUserFPD], err = buildEnforcementPlan(privacyConf.AllowActivities.EnrichUserFPD)
+ if err != nil {
+ return ac, err
+ }
+ plans[ActivityReportAnalytics], err = buildEnforcementPlan(privacyConf.AllowActivities.ReportAnalytics)
+ if err != nil {
+ return ac, err
+ }
+ plans[ActivityTransmitUserFPD], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitUserFPD)
+ if err != nil {
+ return ac, err
+ }
+ plans[ActivityTransmitPreciseGeo], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitPreciseGeo)
+ if err != nil {
+ return ac, err
+ }
+ plans[ActivityTransmitUniqueRequestIds], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitUniqueRequestIds)
+ if err != nil {
+ return ac, err
+ }
+ plans[ActivityTransmitTids], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitTids)
+ if err != nil {
+ return ac, err
+ }
+
+ ac.plans = plans
+
+ return ac, nil
+}
+
+func buildEnforcementPlan(activity config.Activity) (ActivityPlan, error) {
+ ef := ActivityPlan{}
+ rules, err := activityRulesToEnforcementRules(activity.Rules)
+ if err != nil {
+ return ef, err
+ }
+ ef.defaultResult = activityDefaultToDefaultResult(activity.Default)
+ ef.rules = rules
+ return ef, nil
}
-// ShouldEnforce is hardcoded to always return false.
-func (NilPolicyEnforcer) ShouldEnforce(bidder string) bool {
- return false
+func activityRulesToEnforcementRules(rules []config.ActivityRule) ([]ActivityRule, error) {
+ enfRules := make([]ActivityRule, 0)
+ for _, r := range rules {
+ cmpName, err := conditionToRuleComponentName(r.Condition.ComponentName)
+ if err != nil {
+ return nil, err
+ }
+ er := ComponentEnforcementRule{
+ allowed: r.Allow,
+ componentName: cmpName,
+ componentType: r.Condition.ComponentType,
+ }
+ enfRules = append(enfRules, er)
+ }
+ return enfRules, nil
}
-// EnabledPolicyEnforcer decorates a PolicyEnforcer with an enabled flag.
-type EnabledPolicyEnforcer struct {
- Enabled bool
- PolicyEnforcer PolicyEnforcer
+func conditionToRuleComponentName(conditions []string) ([]ScopedName, error) {
+ sn := make([]ScopedName, 0)
+ for _, condition := range conditions {
+ scope, err := NewScopedName(condition)
+ if err != nil {
+ return sn, err
+ }
+ sn = append(sn, scope)
+ }
+ return sn, nil
}
-// CanEnforce returns true when the PolicyEnforcer can enforce.
-func (p EnabledPolicyEnforcer) CanEnforce() bool {
- return p.PolicyEnforcer.CanEnforce()
+func activityDefaultToDefaultResult(activityDefault *bool) ActivityResult {
+ if activityDefault == nil {
+ // if default is unspecified, the hardcoded default-default is true.
+ return ActivityAllow
+ } else if *activityDefault {
+ return ActivityAllow
+ }
+ return ActivityDeny
}
-// ShouldEnforce returns true when the enforcer is enabled the PolicyEnforcer allows enforcement.
-func (p EnabledPolicyEnforcer) ShouldEnforce(bidder string) bool {
- if p.Enabled {
- return p.PolicyEnforcer.ShouldEnforce(bidder)
+func (e ActivityControl) Allow(activity Activity, target ScopedName) ActivityResult {
+ plan, planDefined := e.plans[activity]
+
+ if !planDefined {
+ return ActivityAbstain
}
- return false
+
+ return plan.Allow(target)
+}
+
+type ActivityPlan struct {
+ defaultResult ActivityResult
+ rules []ActivityRule
+}
+
+func (p ActivityPlan) Allow(target ScopedName) ActivityResult {
+ for _, rule := range p.rules {
+ result := rule.Allow(target)
+ if result == ActivityDeny || result == ActivityAllow {
+ return result
+ }
+ }
+ return p.defaultResult
+}
+
+type ActivityRule interface {
+ Allow(target ScopedName) ActivityResult
+}
+
+type ComponentEnforcementRule struct {
+ componentName []ScopedName
+ componentType []string
+ // include gppSectionId from 3.5
+ // include geo from 3.5
+ allowed bool
+}
+
+func (r ComponentEnforcementRule) Allow(target ScopedName) ActivityResult {
+ if len(r.componentName) == 0 && len(r.componentType) == 0 {
+ return ActivityAbstain
+ }
+
+ nameClauseExists := len(r.componentName) > 0
+ typeClauseExists := len(r.componentType) > 0
+
+ componentNameFound := false
+ for _, scope := range r.componentName {
+ if strings.EqualFold(scope.Scope, target.Scope) &&
+ (scope.Name == "*" || strings.EqualFold(scope.Name, target.Name)) {
+ componentNameFound = true
+ break
+ }
+ }
+
+ componentTypeFound := false
+ for _, componentType := range r.componentType {
+ if strings.EqualFold(componentType, target.Scope) {
+ componentTypeFound = true
+ break
+ }
+ }
+ // behavior if rule matches: can be either true=allow or false=deny. result is abstain if the rule doesn't match
+ matchFound := (componentNameFound || !nameClauseExists) && (componentTypeFound || !typeClauseExists)
+ if matchFound {
+ if r.allowed {
+ return ActivityAllow
+ } else {
+ return ActivityDeny
+ }
+ }
+ return ActivityAbstain
+}
+
+type ScopedName struct {
+ Scope string
+ Name string
+}
+
+func NewScopedName(condition string) (ScopedName, error) {
+ if condition == "" {
+ return ScopedName{}, fmt.Errorf("unable to parse empty condition")
+ }
+ var scope, name string
+ split := strings.Split(condition, ".")
+ if len(split) == 2 {
+ s := strings.ToLower(split[0])
+ if s == ScopeTypeBidder || s == ScopeTypeAnalytics || s == ScopeTypeUserID {
+ scope = s
+ } else if strings.Contains(s, ScopeTypeRTD) {
+ scope = ScopeTypeRTD
+ } else {
+ scope = ScopeTypeGeneral
+ }
+ name = split[1]
+ } else if len(split) == 1 {
+ scope = ScopeTypeBidder
+ name = split[0]
+ } else {
+ return ScopedName{}, fmt.Errorf("unable to parse condition: %s", condition)
+ }
+
+ return ScopedName{
+ Scope: scope,
+ Name: name,
+ }, nil
}
diff --git a/privacy/enforcer_test.go b/privacy/enforcer_test.go
index b0c4032c714..e87a9eb2bff 100644
--- a/privacy/enforcer_test.go
+++ b/privacy/enforcer_test.go
@@ -1,18 +1,427 @@
package privacy
import (
- "testing"
-
+ "errors"
+ "github.com/prebid/prebid-server/config"
+ "github.com/prebid/prebid-server/util/ptrutil"
"github.com/stretchr/testify/assert"
+ "testing"
)
-func TestNilEnforcerCanEnforce(t *testing.T) {
- nilEnforcer := &NilPolicyEnforcer{}
- assert.False(t, nilEnforcer.CanEnforce())
+func TemporarilyDisabledTestNewActivityControl(t *testing.T) {
+
+ testCases := []struct {
+ name string
+ privacyConf *config.AccountPrivacy
+ activityControl ActivityControl
+ err error
+ }{
+ {
+ name: "privacy_config_is_nil",
+ privacyConf: nil,
+ activityControl: ActivityControl{plans: nil},
+ err: nil,
+ },
+ {
+ name: "privacy_config_is_specified_and_correct",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ SyncUser: getDefaultActivityConfig(),
+ FetchBids: getDefaultActivityConfig(),
+ EnrichUserFPD: getDefaultActivityConfig(),
+ ReportAnalytics: getDefaultActivityConfig(),
+ TransmitUserFPD: getDefaultActivityConfig(),
+ TransmitPreciseGeo: getDefaultActivityConfig(),
+ TransmitUniqueRequestIds: getDefaultActivityConfig(),
+ TransmitTids: getDefaultActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: map[Activity]ActivityPlan{
+ ActivitySyncUser: getDefaultActivityPlan(),
+ ActivityFetchBids: getDefaultActivityPlan(),
+ ActivityEnrichUserFPD: getDefaultActivityPlan(),
+ ActivityReportAnalytics: getDefaultActivityPlan(),
+ ActivityTransmitUserFPD: getDefaultActivityPlan(),
+ ActivityTransmitPreciseGeo: getDefaultActivityPlan(),
+ ActivityTransmitUniqueRequestIds: getDefaultActivityPlan(),
+ ActivityTransmitTids: getDefaultActivityPlan(),
+ }},
+ err: nil,
+ },
+ {
+ name: "privacy_config_is_specified_and_SyncUser_is_incorrect",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ SyncUser: getIncorrectActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: nil},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ {
+ name: "privacy_config_is_specified_and_FetchBids_is_incorrect",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ FetchBids: getIncorrectActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: nil},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ {
+ name: "privacy_config_is_specified_and_EnrichUserFPD_is_incorrect",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ EnrichUserFPD: getIncorrectActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: nil},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ {
+ name: "privacy_config_is_specified_and_ReportAnalytics_is_incorrect",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ ReportAnalytics: getIncorrectActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: nil},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ {
+ name: "privacy_config_is_specified_and_TransmitUserFPD_is_incorrect",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ TransmitUserFPD: getIncorrectActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: nil},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ {
+ name: "privacy_config_is_specified_and_TransmitPreciseGeo_is_incorrect",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ TransmitPreciseGeo: getIncorrectActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: nil},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ {
+ name: "privacy_config_is_specified_and_TransmitUniqueRequestIds_is_incorrect",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ TransmitUniqueRequestIds: getIncorrectActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: nil},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ {
+ name: "privacy_config_is_specified_and_TransmitTids_is_incorrect",
+ privacyConf: &config.AccountPrivacy{
+ AllowActivities: config.AllowActivities{
+ TransmitTids: getIncorrectActivityConfig(),
+ },
+ },
+ activityControl: ActivityControl{plans: nil},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ actualAC, actualErr := NewActivityControl(test.privacyConf)
+ if test.err == nil {
+ assert.Equal(t, test.activityControl, actualAC)
+ assert.NoError(t, actualErr)
+ } else {
+ assert.EqualError(t, actualErr, test.err.Error())
+ }
+ })
+ }
+}
+
+func TestActivityDefaultToDefaultResult(t *testing.T) {
+
+ testCases := []struct {
+ name string
+ activityDefault *bool
+ expectedResult ActivityResult
+ }{
+ {
+ name: "activityDefault_is_nil",
+ activityDefault: nil,
+ expectedResult: ActivityAllow,
+ },
+ {
+ name: "activityDefault_is_true",
+ activityDefault: ptrutil.ToPtr(true),
+ expectedResult: ActivityAllow,
+ },
+ {
+ name: "activityDefault_is_false",
+ activityDefault: ptrutil.ToPtr(false),
+ expectedResult: ActivityDeny,
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ actualResult := activityDefaultToDefaultResult(test.activityDefault)
+ assert.Equal(t, test.expectedResult, actualResult)
+ })
+ }
+}
+
+func TestAllowActivityControl(t *testing.T) {
+
+ testCases := []struct {
+ name string
+ activityControl ActivityControl
+ activity Activity
+ target ScopedName
+ activityResult ActivityResult
+ }{
+ {
+ name: "plans_is_nil",
+ activityControl: ActivityControl{plans: nil},
+ activity: ActivityFetchBids,
+ target: ScopedName{Scope: "bidder", Name: "bidderA"},
+ activityResult: ActivityAbstain,
+ },
+ {
+ name: "activity_not_defined",
+ activityControl: ActivityControl{plans: map[Activity]ActivityPlan{
+ ActivitySyncUser: getDefaultActivityPlan()}},
+ activity: ActivityFetchBids,
+ target: ScopedName{Scope: "bidder", Name: "bidderA"},
+ activityResult: ActivityAbstain,
+ },
+ {
+ name: "activity_defined_but_not_found_default_returned",
+ activityControl: ActivityControl{plans: map[Activity]ActivityPlan{
+ ActivityFetchBids: getDefaultActivityPlan()}},
+ activity: ActivityFetchBids,
+ target: ScopedName{Scope: "bidder", Name: "bidderB"},
+ activityResult: ActivityAllow,
+ },
+ {
+ name: "activity_defined_and_allowed",
+ activityControl: ActivityControl{plans: map[Activity]ActivityPlan{
+ ActivityFetchBids: getDefaultActivityPlan()}},
+ activity: ActivityFetchBids,
+ target: ScopedName{Scope: "bidder", Name: "bidderA"},
+ activityResult: ActivityAllow,
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ actualResult := test.activityControl.Allow(test.activity, test.target)
+ assert.Equal(t, test.activityResult, actualResult)
+
+ })
+ }
+}
+
+func TestAllowComponentEnforcementRule(t *testing.T) {
+
+ testCases := []struct {
+ name string
+ componentRule ComponentEnforcementRule
+ target ScopedName
+ activityResult ActivityResult
+ }{
+ {
+ name: "activity_is_allowed",
+ componentRule: ComponentEnforcementRule{
+ allowed: true,
+ componentName: []ScopedName{
+ {Scope: "bidder", Name: "bidderA"},
+ },
+ componentType: []string{"bidder"},
+ },
+ target: ScopedName{Scope: "bidder", Name: "bidderA"},
+ activityResult: ActivityAllow,
+ },
+ {
+ name: "activity_is_not_allowed",
+ componentRule: ComponentEnforcementRule{
+ allowed: false,
+ componentName: []ScopedName{
+ {Scope: "bidder", Name: "bidderA"},
+ },
+ componentType: []string{"bidder"},
+ },
+ target: ScopedName{Scope: "bidder", Name: "bidderA"},
+ activityResult: ActivityDeny,
+ },
+ {
+ name: "abstain_both_clauses_do_not_match",
+ componentRule: ComponentEnforcementRule{
+ allowed: true,
+ componentName: []ScopedName{
+ {Scope: "bidder", Name: "bidderA"},
+ },
+ componentType: []string{"bidder"},
+ },
+ target: ScopedName{Scope: "bidder", Name: "bidderB"},
+ activityResult: ActivityAbstain,
+ },
+ {
+ name: "activity_is_not_allowed_componentName_only",
+ componentRule: ComponentEnforcementRule{
+ allowed: true,
+ componentName: []ScopedName{
+ {Scope: "bidder", Name: "bidderA"},
+ },
+ },
+ target: ScopedName{Scope: "bidder", Name: "bidderA"},
+ activityResult: ActivityAllow,
+ },
+ {
+ name: "activity_is_allowed_componentType_only",
+ componentRule: ComponentEnforcementRule{
+ allowed: true,
+ componentType: []string{"bidder"},
+ },
+ target: ScopedName{Scope: "bidder", Name: "bidderB"},
+ activityResult: ActivityAllow,
+ },
+ {
+ name: "abstain_activity_no_componentType_and_no_componentName",
+ componentRule: ComponentEnforcementRule{
+ allowed: true,
+ },
+ target: ScopedName{Scope: "bidder", Name: "bidderB"},
+ activityResult: ActivityAbstain,
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ actualResult := test.componentRule.Allow(test.target)
+ assert.Equal(t, test.activityResult, actualResult)
+
+ })
+ }
+}
+
+func TestNewScopedName(t *testing.T) {
+
+ testCases := []struct {
+ name string
+ condition string
+ expectedScopeName ScopedName
+ err error
+ }{
+ {
+ name: "condition_is_empty",
+ condition: "",
+ expectedScopeName: ScopedName{},
+ err: errors.New("unable to parse empty condition"),
+ },
+ {
+ name: "condition_is_incorrect",
+ condition: "bidder.bidderA.bidderB",
+ expectedScopeName: ScopedName{},
+ err: errors.New("unable to parse condition: bidder.bidderA.bidderB"),
+ },
+ {
+ name: "condition_is_scoped_to_bidder",
+ condition: "bidder.bidderA",
+ expectedScopeName: ScopedName{Scope: "bidder", Name: "bidderA"},
+ err: nil,
+ },
+ {
+ name: "condition_is_scoped_to_analytics",
+ condition: "analytics.bidderA",
+ expectedScopeName: ScopedName{Scope: "analytics", Name: "bidderA"},
+ err: nil,
+ },
+ {
+ name: "condition_is_scoped_to_userid",
+ condition: "userid.bidderA",
+ expectedScopeName: ScopedName{Scope: "userid", Name: "bidderA"},
+ err: nil,
+ },
+ {
+ name: "condition_is_bidder_name",
+ condition: "bidderA",
+ expectedScopeName: ScopedName{Scope: "bidder", Name: "bidderA"},
+ err: nil,
+ },
+ {
+ name: "condition_is_module_tag_rtd",
+ condition: "rtd.test",
+ expectedScopeName: ScopedName{Scope: "rtd", Name: "test"},
+ err: nil,
+ },
+ {
+ name: "condition_scope_defaults_to_genera",
+ condition: "test.test",
+ expectedScopeName: ScopedName{Scope: "general", Name: "test"},
+ err: nil,
+ },
+ }
+
+ for _, test := range testCases {
+ t.Run(test.name, func(t *testing.T) {
+ actualSN, actualErr := NewScopedName(test.condition)
+ if test.err == nil {
+ assert.Equal(t, test.expectedScopeName, actualSN)
+ assert.NoError(t, actualErr)
+ } else {
+ assert.EqualError(t, actualErr, test.err.Error())
+ }
+ })
+ }
+}
+
+// constants
+func getDefaultActivityConfig() config.Activity {
+ return config.Activity{
+ Default: ptrutil.ToPtr(true),
+ Rules: []config.ActivityRule{
+ {
+ Allow: true,
+ Condition: config.ActivityCondition{
+ ComponentName: []string{"bidderA"},
+ ComponentType: []string{"bidder"},
+ },
+ },
+ },
+ }
+}
+
+func getDefaultActivityPlan() ActivityPlan {
+ return ActivityPlan{
+ defaultResult: ActivityAllow,
+ rules: []ActivityRule{
+ ComponentEnforcementRule{
+ allowed: true,
+ componentName: []ScopedName{
+ {Scope: "bidder", Name: "bidderA"},
+ },
+ componentType: []string{"bidder"},
+ },
+ },
+ }
}
-func TestNilEnforcerShouldEnforce(t *testing.T) {
- nilEnforcer := &NilPolicyEnforcer{}
- assert.False(t, nilEnforcer.ShouldEnforce(""))
- assert.False(t, nilEnforcer.ShouldEnforce("anyBidder"))
+func getIncorrectActivityConfig() config.Activity {
+ return config.Activity{
+ Default: ptrutil.ToPtr(true),
+ Rules: []config.ActivityRule{
+ {
+ Allow: true,
+ Condition: config.ActivityCondition{
+ ComponentName: []string{"bidder.bidderA.bidderB"},
+ ComponentType: []string{"bidder"},
+ },
+ },
+ },
+ }
}
diff --git a/privacy/policyenforcer.go b/privacy/policyenforcer.go
new file mode 100644
index 00000000000..e70c0d3d190
--- /dev/null
+++ b/privacy/policyenforcer.go
@@ -0,0 +1,45 @@
+package privacy
+
+// NOTE: Reanme this package. Will eventually replace in its entirety with Activites.
+
+// PolicyEnforcer determines if personally identifiable information (PII) should be removed or anonymized per the policy.
+type PolicyEnforcer interface {
+ // CanEnforce returns true when policy information is specifically provided by the publisher.
+ CanEnforce() bool
+
+ // ShouldEnforce returns true when the OpenRTB request should have personally identifiable
+ // information (PII) removed or anonymized per the policy.
+ ShouldEnforce(bidder string) bool
+}
+
+// NilPolicyEnforcer implements the PolicyEnforcer interface but will always return false.
+type NilPolicyEnforcer struct{}
+
+// CanEnforce is hardcoded to always return false.
+func (NilPolicyEnforcer) CanEnforce() bool {
+ return false
+}
+
+// ShouldEnforce is hardcoded to always return false.
+func (NilPolicyEnforcer) ShouldEnforce(bidder string) bool {
+ return false
+}
+
+// EnabledPolicyEnforcer decorates a PolicyEnforcer with an enabled flag.
+type EnabledPolicyEnforcer struct {
+ Enabled bool
+ PolicyEnforcer PolicyEnforcer
+}
+
+// CanEnforce returns true when the PolicyEnforcer can enforce.
+func (p EnabledPolicyEnforcer) CanEnforce() bool {
+ return p.PolicyEnforcer.CanEnforce()
+}
+
+// ShouldEnforce returns true when the enforcer is enabled the PolicyEnforcer allows enforcement.
+func (p EnabledPolicyEnforcer) ShouldEnforce(bidder string) bool {
+ if p.Enabled {
+ return p.PolicyEnforcer.ShouldEnforce(bidder)
+ }
+ return false
+}
diff --git a/privacy/policyenforcer_test.go b/privacy/policyenforcer_test.go
new file mode 100644
index 00000000000..b0c4032c714
--- /dev/null
+++ b/privacy/policyenforcer_test.go
@@ -0,0 +1,18 @@
+package privacy
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestNilEnforcerCanEnforce(t *testing.T) {
+ nilEnforcer := &NilPolicyEnforcer{}
+ assert.False(t, nilEnforcer.CanEnforce())
+}
+
+func TestNilEnforcerShouldEnforce(t *testing.T) {
+ nilEnforcer := &NilPolicyEnforcer{}
+ assert.False(t, nilEnforcer.ShouldEnforce(""))
+ assert.False(t, nilEnforcer.ShouldEnforce("anyBidder"))
+}
diff --git a/router/router.go b/router/router.go
index 8fc99589c22..250dc3750cd 100644
--- a/router/router.go
+++ b/router/router.go
@@ -177,7 +177,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R
}
// Metrics engine
- r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.CoreBidderNames(), syncerKeys, moduleStageNames)
+ r.MetricsEngine = metricsConf.NewMetricsEngine(cfg, openrtb_ext.MspAllBidderNames(), syncerKeys, moduleStageNames)
shutdown, fetcher, ampFetcher, accounts, categoriesFetcher, videoFetcher, storedRespFetcher := storedRequestsConf.NewStoredRequests(cfg, r.MetricsEngine, generalHttpClient, r.Router)
// todo(zachbadgett): better shutdown
r.Shutdown = shutdown
diff --git a/server/server.go b/server/server.go
index 31764cc40e9..3d71ae1a6b4 100644
--- a/server/server.go
+++ b/server/server.go
@@ -16,6 +16,7 @@ import (
"github.com/prebid/prebid-server/config"
"github.com/prebid/prebid-server/metrics"
metricsconfig "github.com/prebid/prebid-server/metrics/config"
+ mspPlugin "github.com/prebid/prebid-server/msp/plugin"
)
// Listen blocks forever, serving PBS requests on the given port. This will block forever, until the process is shut down.
@@ -27,6 +28,8 @@ func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.H
stopAdmin := make(chan os.Signal)
stopMain := make(chan os.Signal)
stopPrometheus := make(chan os.Signal)
+ stopMsp := make(chan os.Signal)
+ allStops := []chan<- os.Signal{stopAdmin, stopMain}
done := make(chan struct{})
adminServer := newAdminServer(cfg, adminHandler)
@@ -63,6 +66,21 @@ func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.H
}
go runServer(adminServer, "Admin", adminListener)
+ if cfg.MSPMetricsConfig.Enabled {
+ var (
+ mspListener net.Listener
+ mspServer = newMSPServer(cfg)
+ )
+ go shutdownAfterSignals(mspServer, stopMsp, done)
+ if mspListener, err = newTCPListener(mspServer.Addr, nil); err != nil {
+ glog.Errorf("Error listening for TCP connections on %s: %v for MSP Metrics server", adminServer.Addr, err)
+ return
+ }
+
+ go runServer(mspServer, "MSP Metrics", mspListener)
+ allStops = append(allStops, stopMsp)
+ }
+
if cfg.Metrics.Prometheus.Port != 0 {
var (
prometheusListener net.Listener
@@ -75,10 +93,9 @@ func Listen(cfg *config.Configuration, handler http.Handler, adminHandler http.H
}
go runServer(prometheusServer, "Prometheus", prometheusListener)
- wait(stopSignals, done, stopMain, stopAdmin, stopPrometheus)
- } else {
- wait(stopSignals, done, stopMain, stopAdmin)
+ allStops = append(allStops, stopPrometheus)
}
+ wait(stopSignals, done, allStops...)
return
}
@@ -206,3 +223,19 @@ func shutdownAfterSignals(server *http.Server, stopper <-chan os.Signal, done ch
func sendSignal(to chan<- os.Signal, sig os.Signal) {
to <- sig
}
+
+func newMSPServer(cfg *config.Configuration) *http.Server {
+ mspBuilder, err := mspPlugin.LoadBuilderFromPath[MSPBuilder]("MSP Metrics", cfg.MSPMetricsConfig.SoPath)
+ if err != nil {
+ glog.Errorf("Failed to initialize MSP Metrics Builder")
+ }
+ mspServer, err := mspBuilder.Build(cfg)
+ if err != nil {
+ glog.Errorf("Failed to initialize MSP Metrics Server")
+ }
+ return mspServer
+}
+
+type MSPBuilder interface {
+ Build(*config.Configuration) (*http.Server, error)
+}
diff --git a/static/bidder-info/adsinteractive.yaml b/static/bidder-info/adsinteractive.yaml
index ae60ec88f10..d430e63b2f0 100644
--- a/static/bidder-info/adsinteractive.yaml
+++ b/static/bidder-info/adsinteractive.yaml
@@ -12,6 +12,6 @@ capabilities:
- banner
userSync:
redirect:
- url: "http://bid.adsinteractive.com/getuid?{{.RedirectURL}}"
+ url: "https://bid.adsinteractive.com/getuid?{{.RedirectURL}}"
userMacro: "$AUID"
diff --git a/static/bidder-info/aidem.yaml b/static/bidder-info/aidem.yaml
new file mode 100644
index 00000000000..35ea898aa8a
--- /dev/null
+++ b/static/bidder-info/aidem.yaml
@@ -0,0 +1,19 @@
+endpoint: "https://zero.aidemsrv.com/ortb/v2.6/bid/request"
+maintainer:
+ email: prebid@aidem.com
+modifyingVastXmlAllowed: true
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ - native
+ site:
+ mediaTypes:
+ - banner
+ - video
+ - native
+userSync:
+ redirect:
+ url: https://gum.aidemsrv.com/prebid_sync?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redirect={{.RedirectURL}}
+ userMacro: "$UID"
diff --git a/static/bidder-info/consumable.yaml b/static/bidder-info/consumable.yaml
index e1c4fc9b986..cc290149be2 100644
--- a/static/bidder-info/consumable.yaml
+++ b/static/bidder-info/consumable.yaml
@@ -11,6 +11,8 @@ capabilities:
- banner
userSync:
redirect:
- url: "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}"
+ url: "https://e.serverbid.com/udb/9969/match?gdpr={{.GDPR}}&euconsent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
userMacro: ""
# consumable appends the user id to end of the redirect url and does not utilize a macro
+openrtb:
+ gpp-supported: true
diff --git a/static/bidder-info/imds.yaml b/static/bidder-info/imds.yaml
index e608a6c6c8c..491a5bd0ac6 100644
--- a/static/bidder-info/imds.yaml
+++ b/static/bidder-info/imds.yaml
@@ -13,8 +13,10 @@ capabilities:
userSync:
supportCors: true
iframe:
- url: "https://ad-cdn.technoratimedia.com/html/usersync.html?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
+ url: "https://ad-cdn.technoratimedia.com/html/usersync.html?gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gppsid={{.GPPSID}}&cb={{.RedirectURL}}"
userMacro: "[USER_ID]"
redirect:
- url: "https://sync.technoratimedia.com/services?srv=cs&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
+ url: "https://sync.technoratimedia.com/services?srv=cs&gdpr={{.GDPR}}&consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gppsid={{.GPPSID}}&cb={{.RedirectURL}}"
userMacro: "[USER_ID]"
+openrtb:
+ gpp-supported: true
diff --git a/static/bidder-info/ix.yaml b/static/bidder-info/ix.yaml
index 9ac7dba32dc..4ab520ad81c 100644
--- a/static/bidder-info/ix.yaml
+++ b/static/bidder-info/ix.yaml
@@ -17,8 +17,10 @@ capabilities:
- audio
userSync:
redirect:
- url: "https://ssum.casalemedia.com/usermatchredir?s=194962&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
+ url: "https://ssum.casalemedia.com/usermatchredir?s=194962&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gppsid={{.GPPSID}}&cb={{.RedirectURL}}"
userMacro: ""
iframe:
- url: "https://ssum-sec.casalemedia.com/usermatch?s=184674&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&cb={{.RedirectURL}}"
+ url: "https://ssum-sec.casalemedia.com/usermatch?s=184674&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gppsid={{.GPPSID}}&cb={{.RedirectURL}}"
# ix appends the user id to end of the redirect url and does not utilize a macro
+openrtb:
+ gpp-supported: true
diff --git a/static/bidder-info/mediafuse.yaml b/static/bidder-info/mediafuse.yaml
index b78b21e16ea..be19cc6c68a 100644
--- a/static/bidder-info/mediafuse.yaml
+++ b/static/bidder-info/mediafuse.yaml
@@ -12,5 +12,5 @@ capabilities:
- banner
- video
- native
-userSync:
- key: "adnxs"
+# userSync:
+# key: "adnxs"
diff --git a/static/bidder-info/pwbid.yaml b/static/bidder-info/pwbid.yaml
index ac6872738b4..a172ff39c83 100644
--- a/static/bidder-info/pwbid.yaml
+++ b/static/bidder-info/pwbid.yaml
@@ -1,4 +1,4 @@
-endpoint: "https://bid.pubwise.io/prebid"
+endpoint: "https://bidder.east2.pubwise.io/bid/pubwisedirect"
maintainer:
email: info@pubwise.io
gvlVendorID: 842
@@ -17,4 +17,4 @@ userSync:
# PubWise supports user syncing, but requires configuration by the host. contact this
# bidder directly at the email address in this file to ask about enabling user sync.
supports:
- - redirect
\ No newline at end of file
+ - redirect
diff --git a/static/bidder-info/triplelift.yaml b/static/bidder-info/triplelift.yaml
index 45811c0c868..605bcc71e6e 100644
--- a/static/bidder-info/triplelift.yaml
+++ b/static/bidder-info/triplelift.yaml
@@ -16,9 +16,11 @@ userSync:
# Contact this bidder directly at the email address above to ask about enabling user sync.
#
iframe:
- url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}"
+ url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
userMacro: $UID
redirect:
- url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}"
+ url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
userMacro: "$UID"
-endpointCompression: "GZIP"
\ No newline at end of file
+endpointCompression: "GZIP"
+openrtb:
+ gpp-supported: true
\ No newline at end of file
diff --git a/static/bidder-info/triplelift_native.yaml b/static/bidder-info/triplelift_native.yaml
index 85ebd1a52cc..ff93b544c4c 100644
--- a/static/bidder-info/triplelift_native.yaml
+++ b/static/bidder-info/triplelift_native.yaml
@@ -16,8 +16,10 @@ userSync:
# Contact this bidder directly at the email address above to ask about enabling user sync.
#
iframe:
- url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}"
+ url: "https://eb2.3lift.com/sync?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
userMacro: $UID
redirect:
- url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&redir={{.RedirectURL}}"
- userMacro: "$UID"
\ No newline at end of file
+ url: "https://eb2.3lift.com/getuid?gdpr={{.GDPR}}&cmp_cs={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redir={{.RedirectURL}}"
+ userMacro: "$UID"
+openrtb:
+ gpp-supported: true
\ No newline at end of file
diff --git a/static/bidder-info/yahooAds.yaml b/static/bidder-info/yahooAds.yaml
new file mode 100644
index 00000000000..a2581387152
--- /dev/null
+++ b/static/bidder-info/yahooAds.yaml
@@ -0,0 +1,18 @@
+endpoint: "https://s2shb.ssp.yahoo.com/admax/bid/partners/PBS"
+maintainer:
+ email: "hb-fe-tech@yahooinc.com"
+gvlVendorID: 25
+capabilities:
+ app:
+ mediaTypes:
+ - banner
+ - video
+ site:
+ mediaTypes:
+ - banner
+ - video
+userSync:
+ # yahooAds supports user syncing, but requires configuration by the host. contact this
+ # bidder directly at the email address in this file to ask about enabling user sync.
+ supports:
+ - redirect
\ No newline at end of file
diff --git a/static/bidder-params/aidem.json b/static/bidder-params/aidem.json
new file mode 100644
index 00000000000..221e5ff7a92
--- /dev/null
+++ b/static/bidder-params/aidem.json
@@ -0,0 +1,29 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "AIDEM Adapter Params",
+ "description": "A schema which validates params accepted by the AIDEM adapter",
+ "type": "object",
+ "properties": {
+ "siteId": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Unique site ID"
+ },
+ "publisherId": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Unique publisher ID"
+ },
+ "placementId": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Unique publisher ttag ID"
+ },
+ "rateLimit": {
+ "type": "number",
+ "minimum": 0,
+ "maximum": 1
+ }
+ },
+ "required": ["siteId", "publisherId"]
+}
\ No newline at end of file
diff --git a/static/bidder-params/ix.json b/static/bidder-params/ix.json
index a7a5cb7308a..172690cca32 100644
--- a/static/bidder-params/ix.json
+++ b/static/bidder-params/ix.json
@@ -27,6 +27,11 @@
"minItems": 2,
"maxItems": 2,
"description": "An array of two integer containing the dimension"
+ },
+ "sid": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Slot ID"
}
},
"oneOf": [
diff --git a/static/bidder-params/rise.json b/static/bidder-params/rise.json
index 8e745d89fd0..ee8a469cbbc 100644
--- a/static/bidder-params/rise.json
+++ b/static/bidder-params/rise.json
@@ -4,10 +4,29 @@
"description": "A schema which validates params accepted by the Rise adapter",
"type": "object",
"properties": {
+ "org": {
+ "type": "string",
+ "description": "The organization ID."
+ },
"publisher_id": {
"type": "string",
- "description": "The publisher ID"
+ "description": "Deprecated, use org instead."
+ },
+ "placementId": {
+ "type": "string",
+ "description": "Placement ID."
}
},
- "required": ["publisher_id"]
+ "oneOf": [
+ {
+ "required": [
+ "org"
+ ]
+ },
+ {
+ "required": [
+ "publisher_id"
+ ]
+ }
+ ]
}
diff --git a/static/bidder-params/yahooAds.json b/static/bidder-params/yahooAds.json
new file mode 100644
index 00000000000..77e7107350c
--- /dev/null
+++ b/static/bidder-params/yahooAds.json
@@ -0,0 +1,19 @@
+{
+ "$schema": "http://json-schema.org/draft-04/schema#",
+ "title": "YahooAds Adapter Params",
+ "description": "A schema which validates params accepted by the YahooAds adapter",
+ "type": "object",
+ "properties": {
+ "dcn": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Site ID provided by One Mobile"
+ },
+ "pos": {
+ "type": "string",
+ "minLength": 1,
+ "description": "Placement ID"
+ }
+ },
+ "required": ["dcn", "pos"]
+}
diff --git a/stored_requests/backends/file_fetcher/fetcher.go b/stored_requests/backends/file_fetcher/fetcher.go
index 56f5bdf853c..cde790a9228 100644
--- a/stored_requests/backends/file_fetcher/fetcher.go
+++ b/stored_requests/backends/file_fetcher/fetcher.go
@@ -35,7 +35,9 @@ func (fetcher *eagerFetcher) FetchRequests(ctx context.Context, requestIDs []str
}
func (fetcher *eagerFetcher) FetchResponses(ctx context.Context, ids []string) (data map[string]json.RawMessage, errs []error) {
- return nil, nil
+ storedResponse := fetcher.FileSystem.Directories["stored_responses"].Files
+ errs = appendErrors("Response", ids, storedResponse, errs)
+ return storedResponse, errs
}
// FetchAccount fetches the host account configuration for a publisher