github.com/prebid/prebid-server/v2@v2.18.0/config/bidderinfo.go (about) 1 package config 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "os" 8 "path/filepath" 9 "strings" 10 "text/template" 11 12 "github.com/prebid/prebid-server/v2/macros" 13 "github.com/prebid/prebid-server/v2/openrtb_ext" 14 15 validator "github.com/asaskevich/govalidator" 16 "gopkg.in/yaml.v3" 17 ) 18 19 // BidderInfos contains a mapping of bidder name to bidder info. 20 type BidderInfos map[string]BidderInfo 21 22 // BidderInfo specifies all configuration for a bidder except for enabled status, endpoint, and extra information. 23 type BidderInfo struct { 24 AliasOf string `yaml:"aliasOf" mapstructure:"aliasOf"` 25 Disabled bool `yaml:"disabled" mapstructure:"disabled"` 26 Endpoint string `yaml:"endpoint" mapstructure:"endpoint"` 27 ExtraAdapterInfo string `yaml:"extra_info" mapstructure:"extra_info"` 28 29 Maintainer *MaintainerInfo `yaml:"maintainer" mapstructure:"maintainer"` 30 Capabilities *CapabilitiesInfo `yaml:"capabilities" mapstructure:"capabilities"` 31 ModifyingVastXmlAllowed bool `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"` 32 Debug *DebugInfo `yaml:"debug" mapstructure:"debug"` 33 GVLVendorID uint16 `yaml:"gvlVendorID" mapstructure:"gvlVendorID"` 34 35 Syncer *Syncer `yaml:"userSync" mapstructure:"userSync"` 36 37 Experiment BidderInfoExperiment `yaml:"experiment" mapstructure:"experiment"` 38 39 // needed for Rubicon 40 XAPI AdapterXAPI `yaml:"xapi" mapstructure:"xapi"` 41 42 // needed for Facebook 43 PlatformID string `yaml:"platform_id" mapstructure:"platform_id"` 44 AppSecret string `yaml:"app_secret" mapstructure:"app_secret"` 45 // EndpointCompression determines, if set, the type of compression the bid request will undergo before being sent to the corresponding bid server 46 EndpointCompression string `yaml:"endpointCompression" mapstructure:"endpointCompression"` 47 OpenRTB *OpenRTBInfo `yaml:"openrtb" mapstructure:"openrtb"` 48 } 49 50 type aliasNillableFields struct { 51 Disabled *bool `yaml:"disabled" mapstructure:"disabled"` 52 ModifyingVastXmlAllowed *bool `yaml:"modifyingVastXmlAllowed" mapstructure:"modifyingVastXmlAllowed"` 53 Experiment *BidderInfoExperiment `yaml:"experiment" mapstructure:"experiment"` 54 XAPI *AdapterXAPI `yaml:"xapi" mapstructure:"xapi"` 55 } 56 57 // BidderInfoExperiment specifies non-production ready feature config for a bidder 58 type BidderInfoExperiment struct { 59 AdsCert BidderAdsCert `yaml:"adsCert" mapstructure:"adsCert"` 60 } 61 62 // BidderAdsCert enables Call Sign feature for bidder 63 type BidderAdsCert struct { 64 Enabled bool `yaml:"enabled" mapstructure:"enabled"` 65 } 66 67 // MaintainerInfo specifies the support email address for a bidder. 68 type MaintainerInfo struct { 69 Email string `yaml:"email" mapstructure:"email"` 70 } 71 72 // CapabilitiesInfo specifies the supported platforms for a bidder. 73 type CapabilitiesInfo struct { 74 App *PlatformInfo `yaml:"app" mapstructure:"app"` 75 Site *PlatformInfo `yaml:"site" mapstructure:"site"` 76 DOOH *PlatformInfo `yaml:"dooh" mapstructure:"dooh"` 77 } 78 79 // PlatformInfo specifies the supported media types for a bidder. 80 type PlatformInfo struct { 81 MediaTypes []openrtb_ext.BidType `yaml:"mediaTypes" mapstructure:"mediaTypes"` 82 } 83 84 // DebugInfo specifies the supported debug options for a bidder. 85 type DebugInfo struct { 86 Allow bool `yaml:"allow" mapstructure:"allow"` 87 } 88 89 type AdapterXAPI struct { 90 Username string `yaml:"username" mapstructure:"username"` 91 Password string `yaml:"password" mapstructure:"password"` 92 Tracker string `yaml:"tracker" mapstructure:"tracker"` 93 } 94 95 // OpenRTBInfo specifies the versions/aspects of openRTB that a bidder supports 96 // Version is not yet actively supported 97 // GPPSupported is not yet actively supported 98 type OpenRTBInfo struct { 99 Version string `yaml:"version" mapstructure:"version"` 100 GPPSupported bool `yaml:"gpp-supported" mapstructure:"gpp-supported"` 101 } 102 103 // Syncer specifies the user sync settings for a bidder. This struct is shared by the account config, 104 // so it needs to have both yaml and mapstructure mappings. 105 type Syncer struct { 106 // Key is used as the record key for the user sync cookie. We recommend using the bidder name 107 // as the key for consistency, but that is not enforced as a requirement. 108 Key string `yaml:"key" mapstructure:"key"` 109 110 // Supports allows bidders to specify which user sync endpoints they support but which don't have 111 // good defaults. Host companies should contact the bidder for the endpoint configuration. Hosts 112 // may not override this value. 113 Supports []string `yaml:"supports" mapstructure:"supports"` 114 115 // IFrame configures an iframe endpoint for user syncing. 116 IFrame *SyncerEndpoint `yaml:"iframe" mapstructure:"iframe"` 117 118 // Redirect configures an redirect endpoint for user syncing. This is also known as an image 119 // endpoint in the Prebid.js project. 120 Redirect *SyncerEndpoint `yaml:"redirect" mapstructure:"redirect"` 121 122 // ExternalURL is available as a macro to the RedirectURL template. 123 ExternalURL string `yaml:"externalUrl" mapstructure:"external_url"` 124 125 // SupportCORS identifies if CORS is supported for the user syncing endpoints. 126 SupportCORS *bool `yaml:"supportCors" mapstructure:"support_cors"` 127 128 // FormatOverride allows a bidder to override their callback type "b" for iframe, "i" for redirect 129 FormatOverride string `yaml:"formatOverride" mapstructure:"format_override"` 130 131 // Enabled signifies whether a bidder is enabled/disabled for user sync 132 Enabled *bool `yaml:"enabled" mapstructure:"enabled"` 133 134 // SkipWhen allows bidders to specify when they don't want to sync 135 SkipWhen *SkipWhen `yaml:"skipwhen" mapstructure:"skipwhen"` 136 } 137 138 type SkipWhen struct { 139 GDPR bool `yaml:"gdpr" mapstructure:"gdpr"` 140 GPPSID []string `yaml:"gpp_sid" mapstructure:"gpp_sid"` 141 } 142 143 // SyncerEndpoint specifies the configuration of the URL returned by the /cookie_sync endpoint 144 // for a specific bidder. Bidders must specify at least one endpoint configuration to be eligible 145 // for selection during a user sync request. 146 // 147 // URL is the only required field, although we highly recommend to use the available macros to 148 // make the configuration readable and maintainable. User sync urls include a redirect url back to 149 // Prebid Server which is url escaped and can be very diffcult for humans to read. 150 // 151 // In most cases, bidders will specify a URL with a `{{.RedirectURL}}` macro for the call back to 152 // Prebid Server and a UserMacro which the bidder server will replace with the user's id. Example: 153 // 154 // url: "https://sync.bidderserver.com/usersync?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&us_privacy={{.USPrivacy}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&redirect={{.RedirectURL}}" 155 // userMacro: "$UID" 156 // 157 // Prebid Server is configured with a default RedirectURL template matching the /setuid call. This 158 // may be overridden for all bidders with the `user_sync.redirect_url` host configuration or for a 159 // specific bidder with the RedirectURL value in this struct. 160 type SyncerEndpoint struct { 161 // URL is the endpoint on the bidder server the user will be redirected to when a user sync is 162 // requested. The following macros are resolved at application startup: 163 // 164 // {{.RedirectURL}} - This will be replaced with a redirect url generated using the RedirectURL 165 // template and url escaped for safe inclusion in any part of the URL. 166 // 167 // The following macros are specific to individual requests and are resolved at runtime using the 168 // Go template engine. For more information on Go templates, see: https://golang.org/pkg/text/template/ 169 // 170 // {{.GDPR}} - This will be replaced with the "gdpr" property sent to /cookie_sync. 171 // {{.Consent}} - This will be replaced with the "consent" property sent to /cookie_sync. 172 // {{.USPrivacy}} - This will be replaced with the "us_privacy" property sent to /cookie_sync. 173 // {{.GPP}} - This will be replaced with the "gpp" property sent to /cookie_sync. 174 // {{.GPPSID}} - This will be replaced with the "gpp_sid" property sent to /cookie_sync. 175 URL string `yaml:"url" mapstructure:"url"` 176 177 // RedirectURL is an endpoint on the host server the user will be redirected to when a user sync 178 // request has been completed by the bidder server. The following macros are resolved at application 179 // startup: 180 // 181 // {{.ExternalURL}} - This will be replaced with the host server's externally reachable http path. 182 // {{.BidderName}} - This will be replaced with the bidder name. 183 // {{.SyncType}} - This will be replaced with the sync type, either 'b' for iframe syncs or 'i' 184 // for redirect/image syncs. 185 // {{.UserMacro}} - This will be replaced with the bidder server's user id macro. 186 // 187 // The endpoint on the host server is usually Prebid Server's /setuid endpoint. The default value is: 188 // `{{.ExternalURL}}/setuid?bidder={{.SyncerKey}}&gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}&f={{.SyncType}}&uid={{.UserMacro}}` 189 RedirectURL string `yaml:"redirectUrl" mapstructure:"redirect_url"` 190 191 // ExternalURL is available as a macro to the RedirectURL template. If not specified, either the syncer configuration 192 // value or the host configuration value is used. 193 ExternalURL string `yaml:"externalUrl" mapstructure:"external_url"` 194 195 // UserMacro is available as a macro to the RedirectURL template. This value is specific to the bidder server 196 // and has no default. 197 UserMacro string `yaml:"userMacro" mapstructure:"user_macro"` 198 } 199 200 func (bi BidderInfo) IsEnabled() bool { 201 return !bi.Disabled 202 } 203 204 type InfoReader interface { 205 Read() (map[string][]byte, error) 206 } 207 208 type InfoReaderFromDisk struct { 209 Path string 210 } 211 212 const ( 213 SyncResponseFormatIFrame = "b" // b = blank HTML response 214 SyncResponseFormatRedirect = "i" // i = image response 215 ) 216 217 func (r InfoReaderFromDisk) Read() (map[string][]byte, error) { 218 bidderConfigs, err := os.ReadDir(r.Path) 219 if err != nil { 220 log.Fatal(err) 221 } 222 223 bidderInfos := make(map[string][]byte) 224 for _, bidderConfig := range bidderConfigs { 225 if bidderConfig.IsDir() { 226 continue //ignore directories 227 } 228 fileName := bidderConfig.Name() 229 filePath := filepath.Join(r.Path, fileName) 230 data, err := os.ReadFile(filePath) 231 if err != nil { 232 return nil, err 233 } 234 bidderInfos[fileName] = data 235 } 236 return bidderInfos, nil 237 } 238 239 func LoadBidderInfoFromDisk(path string) (BidderInfos, error) { 240 bidderInfoReader := InfoReaderFromDisk{Path: path} 241 return LoadBidderInfo(bidderInfoReader) 242 } 243 244 func LoadBidderInfo(reader InfoReader) (BidderInfos, error) { 245 return processBidderInfos(reader, openrtb_ext.NormalizeBidderName) 246 } 247 248 func processBidderInfos(reader InfoReader, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { 249 bidderConfigs, err := reader.Read() 250 if err != nil { 251 return nil, fmt.Errorf("error loading bidders data") 252 } 253 254 bidderInfos := BidderInfos{} 255 aliasNillableFieldsByBidder := map[string]aliasNillableFields{} 256 for fileName, data := range bidderConfigs { 257 bidderName := strings.Split(fileName, ".") 258 if len(bidderName) == 2 && bidderName[1] == "yaml" { 259 info := BidderInfo{} 260 if err := yaml.Unmarshal(data, &info); err != nil { 261 return nil, fmt.Errorf("error parsing config for bidder %s: %v", fileName, err) 262 } 263 264 //need to maintain nullable fields from BidderInfo struct into bidderInfoNullableFields 265 //to handle the default values in aliases yaml 266 if len(info.AliasOf) > 0 { 267 aliasFields := aliasNillableFields{} 268 if err := yaml.Unmarshal(data, &aliasFields); err != nil { 269 return nil, fmt.Errorf("error parsing config for aliased bidder %s: %v", fileName, err) 270 } 271 272 //required for CoreBidderNames function to also return aliasBiddernames 273 if err := openrtb_ext.SetAliasBidderName(bidderName[0], openrtb_ext.BidderName(info.AliasOf)); err != nil { 274 return nil, err 275 } 276 277 normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0]) 278 if !bidderNameExists { 279 return nil, fmt.Errorf("error parsing config for an alias %s: unknown bidder", fileName) 280 } 281 282 aliasNillableFieldsByBidder[string(normalizedBidderName)] = aliasFields 283 bidderInfos[string(normalizedBidderName)] = info 284 } else { 285 normalizedBidderName, bidderNameExists := normalizeBidderName(bidderName[0]) 286 if !bidderNameExists { 287 return nil, fmt.Errorf("error parsing config for bidder %s: unknown bidder", fileName) 288 } 289 290 bidderInfos[string(normalizedBidderName)] = info 291 } 292 } 293 } 294 return processBidderAliases(aliasNillableFieldsByBidder, bidderInfos) 295 } 296 297 func processBidderAliases(aliasNillableFieldsByBidder map[string]aliasNillableFields, bidderInfos BidderInfos) (BidderInfos, error) { 298 for bidderName, alias := range aliasNillableFieldsByBidder { 299 aliasBidderInfo, ok := bidderInfos[bidderName] 300 if !ok { 301 return nil, fmt.Errorf("bidder info not found for an alias: %s", bidderName) 302 } 303 if err := validateAliases(aliasBidderInfo, bidderInfos, bidderName); err != nil { 304 return nil, err 305 } 306 307 parentBidderInfo := bidderInfos[aliasBidderInfo.AliasOf] 308 if aliasBidderInfo.AppSecret == "" { 309 aliasBidderInfo.AppSecret = parentBidderInfo.AppSecret 310 } 311 if aliasBidderInfo.Capabilities == nil { 312 aliasBidderInfo.Capabilities = parentBidderInfo.Capabilities 313 } 314 if aliasBidderInfo.Debug == nil { 315 aliasBidderInfo.Debug = parentBidderInfo.Debug 316 } 317 if aliasBidderInfo.Endpoint == "" { 318 aliasBidderInfo.Endpoint = parentBidderInfo.Endpoint 319 } 320 if aliasBidderInfo.EndpointCompression == "" { 321 aliasBidderInfo.EndpointCompression = parentBidderInfo.EndpointCompression 322 } 323 if aliasBidderInfo.ExtraAdapterInfo == "" { 324 aliasBidderInfo.ExtraAdapterInfo = parentBidderInfo.ExtraAdapterInfo 325 } 326 if aliasBidderInfo.GVLVendorID == 0 { 327 aliasBidderInfo.GVLVendorID = parentBidderInfo.GVLVendorID 328 } 329 if aliasBidderInfo.Maintainer == nil { 330 aliasBidderInfo.Maintainer = parentBidderInfo.Maintainer 331 } 332 if aliasBidderInfo.OpenRTB == nil { 333 aliasBidderInfo.OpenRTB = parentBidderInfo.OpenRTB 334 } 335 if aliasBidderInfo.PlatformID == "" { 336 aliasBidderInfo.PlatformID = parentBidderInfo.PlatformID 337 } 338 if aliasBidderInfo.Syncer == nil && parentBidderInfo.Syncer != nil { 339 syncerKey := aliasBidderInfo.AliasOf 340 if parentBidderInfo.Syncer.Key != "" { 341 syncerKey = parentBidderInfo.Syncer.Key 342 } 343 syncer := Syncer{Key: syncerKey} 344 aliasBidderInfo.Syncer = &syncer 345 } 346 if alias.Disabled == nil { 347 aliasBidderInfo.Disabled = parentBidderInfo.Disabled 348 } 349 if alias.Experiment == nil { 350 aliasBidderInfo.Experiment = parentBidderInfo.Experiment 351 } 352 if alias.ModifyingVastXmlAllowed == nil { 353 aliasBidderInfo.ModifyingVastXmlAllowed = parentBidderInfo.ModifyingVastXmlAllowed 354 } 355 if alias.XAPI == nil { 356 aliasBidderInfo.XAPI = parentBidderInfo.XAPI 357 } 358 bidderInfos[bidderName] = aliasBidderInfo 359 } 360 return bidderInfos, nil 361 } 362 363 // ToGVLVendorIDMap transforms a BidderInfos object to a map of bidder names to GVL id. 364 // Disabled bidders are omitted from the result. 365 func (infos BidderInfos) ToGVLVendorIDMap() map[openrtb_ext.BidderName]uint16 { 366 gvlVendorIds := make(map[openrtb_ext.BidderName]uint16, len(infos)) 367 for name, info := range infos { 368 if info.IsEnabled() && info.GVLVendorID != 0 { 369 gvlVendorIds[openrtb_ext.BidderName(name)] = info.GVLVendorID 370 } 371 } 372 return gvlVendorIds 373 } 374 375 // validateBidderInfos validates bidder endpoint, info and syncer data 376 func (infos BidderInfos) validate(errs []error) []error { 377 for bidderName, bidder := range infos { 378 if bidder.IsEnabled() { 379 errs = validateAdapterEndpoint(bidder.Endpoint, bidderName, errs) 380 381 if err := validateInfo(bidder, infos, bidderName); err != nil { 382 errs = append(errs, err) 383 } 384 385 if err := validateSyncer(bidder); err != nil { 386 errs = append(errs, err) 387 } 388 } 389 } 390 return errs 391 } 392 393 func validateAliases(aliasBidderInfo BidderInfo, infos BidderInfos, bidderName string) error { 394 if len(aliasBidderInfo.AliasOf) > 0 { 395 if parentBidder, ok := infos[aliasBidderInfo.AliasOf]; ok { 396 if len(parentBidder.AliasOf) > 0 { 397 return fmt.Errorf("bidder: %s cannot be an alias of an alias: %s", aliasBidderInfo.AliasOf, bidderName) 398 } 399 } else { 400 return fmt.Errorf("bidder: %s not found for an alias: %s", aliasBidderInfo.AliasOf, bidderName) 401 } 402 } 403 return nil 404 } 405 406 var testEndpointTemplateParams = macros.EndpointTemplateParams{ 407 Host: "anyHost", 408 PublisherID: "anyPublisherID", 409 AccountID: "anyAccountID", 410 ZoneID: "anyZoneID", 411 SourceId: "anySourceID", 412 AdUnit: "anyAdUnit", 413 MediaType: "MediaType", 414 } 415 416 // validateAdapterEndpoint makes sure that an adapter has a valid endpoint 417 // associated with it 418 func validateAdapterEndpoint(endpoint string, bidderName string, errs []error) []error { 419 if endpoint == "" { 420 return append(errs, fmt.Errorf("There's no default endpoint available for %s. Calls to this bidder/exchange will fail. "+ 421 "Please set adapters.%s.endpoint in your app config", bidderName, bidderName)) 422 } 423 424 // Create endpoint template 425 endpointTemplate, err := template.New("endpointTemplate").Parse(endpoint) 426 if err != nil { 427 return append(errs, fmt.Errorf("Invalid endpoint template: %s for adapter: %s. %v", endpoint, bidderName, err)) 428 } 429 // Resolve macros (if any) in the endpoint URL 430 resolvedEndpoint, err := macros.ResolveMacros(endpointTemplate, testEndpointTemplateParams) 431 if err != nil { 432 return append(errs, fmt.Errorf("Unable to resolve endpoint: %s for adapter: %s. %v", endpoint, bidderName, err)) 433 } 434 // Validate the resolved endpoint 435 // 436 // Validating using both IsURL and IsRequestURL because IsURL allows relative paths 437 // whereas IsRequestURL requires absolute path but fails to check other valid URL 438 // format constraints. 439 // 440 // For example: IsURL will allow "abcd.com" but IsRequestURL won't 441 // IsRequestURL will allow "http://http://abcd.com" but IsURL won't 442 if !validator.IsURL(resolvedEndpoint) || !validator.IsRequestURL(resolvedEndpoint) { 443 errs = append(errs, fmt.Errorf("The endpoint: %s for %s is not a valid URL", resolvedEndpoint, bidderName)) 444 } 445 return errs 446 } 447 448 func validateInfo(bidder BidderInfo, infos BidderInfos, bidderName string) error { 449 if err := validateMaintainer(bidder.Maintainer, bidderName); err != nil { 450 return err 451 } 452 if err := validateCapabilities(bidder.Capabilities, bidderName); err != nil { 453 return err 454 } 455 if len(bidder.AliasOf) > 0 { 456 if err := validateAliasCapabilities(bidder, infos, bidderName); err != nil { 457 return err 458 } 459 } 460 return nil 461 } 462 463 func validateMaintainer(info *MaintainerInfo, bidderName string) error { 464 if info == nil || info.Email == "" { 465 return fmt.Errorf("missing required field: maintainer.email for adapter: %s", bidderName) 466 } 467 return nil 468 } 469 470 func validateAliasCapabilities(aliasBidderInfo BidderInfo, infos BidderInfos, bidderName string) error { 471 parentBidder, parentFound := infos[aliasBidderInfo.AliasOf] 472 if !parentFound { 473 return fmt.Errorf("parent bidder: %s not found for an alias: %s", aliasBidderInfo.AliasOf, bidderName) 474 } 475 476 if aliasBidderInfo.Capabilities != nil { 477 if parentBidder.Capabilities == nil { 478 return fmt.Errorf("capabilities for alias: %s should be a subset of capabilities for parent bidder: %s", bidderName, aliasBidderInfo.AliasOf) 479 } 480 481 if (aliasBidderInfo.Capabilities.App != nil && parentBidder.Capabilities.App == nil) || 482 (aliasBidderInfo.Capabilities.Site != nil && parentBidder.Capabilities.Site == nil) || 483 (aliasBidderInfo.Capabilities.DOOH != nil && parentBidder.Capabilities.DOOH == nil) { 484 return fmt.Errorf("capabilities for alias: %s should be a subset of capabilities for parent bidder: %s", bidderName, aliasBidderInfo.AliasOf) 485 } 486 487 if aliasBidderInfo.Capabilities.Site != nil && parentBidder.Capabilities.Site != nil { 488 if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.Site, *aliasBidderInfo.Capabilities.Site, bidderName, aliasBidderInfo.AliasOf); err != nil { 489 return err 490 } 491 } 492 493 if aliasBidderInfo.Capabilities.App != nil && parentBidder.Capabilities.App != nil { 494 if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.App, *aliasBidderInfo.Capabilities.App, bidderName, aliasBidderInfo.AliasOf); err != nil { 495 return err 496 } 497 } 498 499 if aliasBidderInfo.Capabilities.DOOH != nil && parentBidder.Capabilities.DOOH != nil { 500 if err := isAliasPlatformInfoSubsetOfParent(*parentBidder.Capabilities.DOOH, *aliasBidderInfo.Capabilities.DOOH, bidderName, aliasBidderInfo.AliasOf); err != nil { 501 return err 502 } 503 } 504 } 505 506 return nil 507 } 508 509 func isAliasPlatformInfoSubsetOfParent(parentInfo PlatformInfo, aliasInfo PlatformInfo, bidderName string, parentBidderName string) error { 510 parentMediaTypes := make(map[openrtb_ext.BidType]struct{}) 511 for _, info := range parentInfo.MediaTypes { 512 parentMediaTypes[info] = struct{}{} 513 } 514 515 for _, info := range aliasInfo.MediaTypes { 516 if _, found := parentMediaTypes[info]; !found { 517 return fmt.Errorf("mediaTypes for alias: %s should be a subset of MediaTypes for parent bidder: %s", bidderName, parentBidderName) 518 } 519 } 520 521 return nil 522 } 523 524 func validateCapabilities(info *CapabilitiesInfo, bidderName string) error { 525 if info == nil { 526 return fmt.Errorf("missing required field: capabilities for adapter: %s", bidderName) 527 } 528 529 if info.App == nil && info.Site == nil && info.DOOH == nil { 530 return fmt.Errorf("at least one of capabilities.site, capabilities.app, or capabilities.dooh must exist for adapter: %s", bidderName) 531 } 532 533 if info.App != nil { 534 if err := validatePlatformInfo(info.App); err != nil { 535 return fmt.Errorf("capabilities.app failed validation: %v for adapter: %s", err, bidderName) 536 } 537 } 538 539 if info.Site != nil { 540 if err := validatePlatformInfo(info.Site); err != nil { 541 return fmt.Errorf("capabilities.site failed validation: %v for adapter: %s", err, bidderName) 542 } 543 } 544 545 if info.DOOH != nil { 546 if err := validatePlatformInfo(info.DOOH); err != nil { 547 return fmt.Errorf("capabilities.dooh failed validation: %v for adapter: %s", err, bidderName) 548 } 549 } 550 551 return nil 552 } 553 554 func validatePlatformInfo(info *PlatformInfo) error { 555 if len(info.MediaTypes) == 0 { 556 return errors.New("at least one media type needs to be specified") 557 } 558 559 for index, mediaType := range info.MediaTypes { 560 if mediaType != "banner" && mediaType != "video" && mediaType != "native" && mediaType != "audio" { 561 return fmt.Errorf("unrecognized media type at index %d: %s", index, mediaType) 562 } 563 } 564 565 return nil 566 } 567 568 func validateSyncer(bidderInfo BidderInfo) error { 569 if bidderInfo.Syncer == nil { 570 return nil 571 } 572 573 if bidderInfo.Syncer.FormatOverride != SyncResponseFormatIFrame && bidderInfo.Syncer.FormatOverride != SyncResponseFormatRedirect && bidderInfo.Syncer.FormatOverride != "" { 574 return fmt.Errorf("syncer could not be created, invalid format override value: %s", bidderInfo.Syncer.FormatOverride) 575 } 576 577 for _, supports := range bidderInfo.Syncer.Supports { 578 if !strings.EqualFold(supports, "iframe") && !strings.EqualFold(supports, "redirect") { 579 return fmt.Errorf("syncer could not be created, invalid supported endpoint: %s", supports) 580 } 581 } 582 583 return nil 584 } 585 586 func applyBidderInfoConfigOverrides(configBidderInfos nillableFieldBidderInfos, fsBidderInfos BidderInfos, normalizeBidderName func(string) (openrtb_ext.BidderName, bool)) (BidderInfos, error) { 587 mergedBidderInfos := make(map[string]BidderInfo, len(fsBidderInfos)) 588 589 for bidderName, configBidderInfo := range configBidderInfos { 590 normalizedBidderName, exists := normalizeBidderName(bidderName) 591 if !exists { 592 return nil, fmt.Errorf("error setting configuration for bidder %s: unknown bidder", bidderName) 593 } 594 fsBidderInfo, exists := fsBidderInfos[string(normalizedBidderName)] 595 if !exists { 596 return nil, fmt.Errorf("error finding configuration for bidder %s: unknown bidder", bidderName) 597 } 598 599 mergedBidderInfo := fsBidderInfo 600 mergedBidderInfo.Syncer = configBidderInfo.bidderInfo.Syncer.Override(fsBidderInfo.Syncer) 601 if len(configBidderInfo.bidderInfo.Endpoint) > 0 { 602 mergedBidderInfo.Endpoint = configBidderInfo.bidderInfo.Endpoint 603 } 604 if len(configBidderInfo.bidderInfo.ExtraAdapterInfo) > 0 { 605 mergedBidderInfo.ExtraAdapterInfo = configBidderInfo.bidderInfo.ExtraAdapterInfo 606 } 607 if configBidderInfo.bidderInfo.Maintainer != nil { 608 mergedBidderInfo.Maintainer = configBidderInfo.bidderInfo.Maintainer 609 } 610 if configBidderInfo.bidderInfo.Capabilities != nil { 611 mergedBidderInfo.Capabilities = configBidderInfo.bidderInfo.Capabilities 612 } 613 if configBidderInfo.bidderInfo.Debug != nil { 614 mergedBidderInfo.Debug = configBidderInfo.bidderInfo.Debug 615 } 616 if configBidderInfo.bidderInfo.GVLVendorID > 0 { 617 mergedBidderInfo.GVLVendorID = configBidderInfo.bidderInfo.GVLVendorID 618 } 619 if configBidderInfo.bidderInfo.XAPI.Username != "" { 620 mergedBidderInfo.XAPI.Username = configBidderInfo.bidderInfo.XAPI.Username 621 } 622 if configBidderInfo.bidderInfo.XAPI.Password != "" { 623 mergedBidderInfo.XAPI.Password = configBidderInfo.bidderInfo.XAPI.Password 624 } 625 if configBidderInfo.bidderInfo.XAPI.Tracker != "" { 626 mergedBidderInfo.XAPI.Tracker = configBidderInfo.bidderInfo.XAPI.Tracker 627 } 628 if configBidderInfo.bidderInfo.PlatformID != "" { 629 mergedBidderInfo.PlatformID = configBidderInfo.bidderInfo.PlatformID 630 } 631 if configBidderInfo.bidderInfo.AppSecret != "" { 632 mergedBidderInfo.AppSecret = configBidderInfo.bidderInfo.AppSecret 633 } 634 if configBidderInfo.nillableFields.Disabled != nil { 635 mergedBidderInfo.Disabled = configBidderInfo.bidderInfo.Disabled 636 } 637 if configBidderInfo.nillableFields.ModifyingVastXmlAllowed != nil { 638 mergedBidderInfo.ModifyingVastXmlAllowed = configBidderInfo.bidderInfo.ModifyingVastXmlAllowed 639 } 640 if configBidderInfo.bidderInfo.Experiment.AdsCert.Enabled == true { 641 mergedBidderInfo.Experiment.AdsCert.Enabled = true 642 } 643 if configBidderInfo.bidderInfo.EndpointCompression != "" { 644 mergedBidderInfo.EndpointCompression = configBidderInfo.bidderInfo.EndpointCompression 645 } 646 if configBidderInfo.bidderInfo.OpenRTB != nil { 647 mergedBidderInfo.OpenRTB = configBidderInfo.bidderInfo.OpenRTB 648 } 649 650 mergedBidderInfos[string(normalizedBidderName)] = mergedBidderInfo 651 } 652 for bidderName, fsBidderInfo := range fsBidderInfos { 653 if _, exists := mergedBidderInfos[bidderName]; !exists { 654 mergedBidderInfos[bidderName] = fsBidderInfo 655 } 656 } 657 658 return mergedBidderInfos, nil 659 } 660 661 // Override returns a new Syncer object where values in the original are replaced by non-empty/non-default 662 // values in the override, except for the Supports field which may not be overridden. No changes are made 663 // to the original or override Syncer. 664 func (s *Syncer) Override(original *Syncer) *Syncer { 665 if s == nil && original == nil { 666 return nil 667 } 668 669 var copy Syncer 670 if original != nil { 671 copy = *original 672 } 673 674 if s == nil { 675 return © 676 } 677 678 if s.Key != "" { 679 copy.Key = s.Key 680 } 681 682 if original == nil { 683 copy.IFrame = s.IFrame.Override(nil) 684 copy.Redirect = s.Redirect.Override(nil) 685 } else { 686 copy.IFrame = s.IFrame.Override(original.IFrame) 687 copy.Redirect = s.Redirect.Override(original.Redirect) 688 } 689 690 if s.ExternalURL != "" { 691 copy.ExternalURL = s.ExternalURL 692 } 693 694 if s.SupportCORS != nil { 695 copy.SupportCORS = s.SupportCORS 696 } 697 698 return © 699 } 700 701 // Override returns a new SyncerEndpoint object where values in the original are replaced by non-empty/non-default 702 // values in the override. No changes are made to the original or override SyncerEndpoint. 703 func (s *SyncerEndpoint) Override(original *SyncerEndpoint) *SyncerEndpoint { 704 if s == nil && original == nil { 705 return nil 706 } 707 708 var copy SyncerEndpoint 709 if original != nil { 710 copy = *original 711 } 712 713 if s == nil { 714 return © 715 } 716 717 if s.URL != "" { 718 copy.URL = s.URL 719 } 720 721 if s.RedirectURL != "" { 722 copy.RedirectURL = s.RedirectURL 723 } 724 725 if s.ExternalURL != "" { 726 copy.ExternalURL = s.ExternalURL 727 } 728 729 if s.UserMacro != "" { 730 copy.UserMacro = s.UserMacro 731 } 732 733 return © 734 }