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 &copy
   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 &copy
   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 &copy
   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 &copy
   722  }