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