From 59271c9f9968372690aab40796b79c90e6a9ea12 Mon Sep 17 00:00:00 2001 From: guscarreon Date: Wed, 14 Aug 2019 10:43:30 -0400 Subject: [PATCH] Add an app.id blacklist to PBS and reject requests from it (#969) --- config/config.go | 12 +++ config/config_test.go | 10 +++ endpoints/openrtb2/auction.go | 41 +++++---- endpoints/openrtb2/auction_test.go | 2 +- .../blacklisted/blacklisted-app.json | 84 +++++++++++++++++++ .../invalid-whole/audio-mimes-empty.json | 5 +- .../invalid-whole/banner-hmax.json | 5 +- .../invalid-whole/banner-hmin.json | 5 +- .../invalid-whole/banner-null.json | 5 +- .../invalid-whole/banner-wmax.json | 5 +- .../invalid-whole/banner-wmin.json | 5 +- .../invalid-whole/deal-no-id.json | 5 +- .../invalid-whole/format-empty-object.json | 5 +- .../invalid-whole/format-no-height.json | 5 +- .../invalid-whole/format-no-hratio.json | 5 +- .../invalid-whole/format-two-widths.json | 5 +- .../invalid-whole/imp-empty-object.json | 5 +- .../invalid-whole/imp-ext-empty.json | 5 +- .../invalid-whole/imp-ext-invalid-params.json | 5 +- .../invalid-whole/imp-ext-unknown-bidder.json | 5 +- .../invalid-whole/imp-no-ext.json | 5 +- .../invalid-whole/imp-no-type.json | 5 +- .../invalid-whole/metric-empty-object.json | 5 +- .../invalid-whole/native-empty.json | 5 +- .../invalid-whole/video-empty.json | 5 +- .../invalid-whole/video-mimes-empty.json | 5 +- endpoints/openrtb2/video_auction.go | 29 +++++-- errortypes/errortypes.go | 17 ++++ 28 files changed, 256 insertions(+), 44 deletions(-) create mode 100644 endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json diff --git a/config/config.go b/config/config.go index 20e5b386e3b..8751f38c1ba 100644 --- a/config/config.go +++ b/config/config.go @@ -50,6 +50,10 @@ type Configuration struct { DefReqConfig DefReqConfig `mapstructure:"default_request"` VideoStoredRequestRequired bool `mapstructure:"video_stored_request_required"` + + // Array of blacklisted apps that is used to create the hash table BlacklistedAppMap so App.ID's can be instantly accessed. + BlacklistedApps []string `mapstructure:"blacklisted_apps,flow"` + BlacklistedAppMap map[string]bool } type HTTPClient struct { @@ -393,6 +397,13 @@ func New(v *viper.Viper) (*Configuration, error) { for i := 0; i < len(c.GDPR.NonStandardPublishers); i++ { c.GDPR.NonStandardPublisherMap[c.GDPR.NonStandardPublishers[i]] = 1 } + + // To look for a request's app_id in O(1) time, we fill this hash table located in the + // the BlacklistedApps field of the Configuration struct defined in this file + c.BlacklistedAppMap = make(map[string]bool) + for i := 0; i < len(c.BlacklistedApps); i++ { + c.BlacklistedAppMap[c.BlacklistedApps[i]] = true + } return &c, nil } @@ -637,6 +648,7 @@ func SetupViper(v *viper.Viper, filename string) { v.SetDefault("default_request.type", "") v.SetDefault("default_request.file.name", "") v.SetDefault("default_request.alias_info", false) + v.SetDefault("blacklisted_apps", []string{""}) // Set environment variable support: v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) diff --git a/config/config_test.go b/config/config_test.go index ef7ebe78c1a..10ba3258c19 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -89,6 +89,7 @@ adapters: endpoint: http://test-bid.ybp.yahoo.com/bid/appnexuspbs adkerneladn: usersync_url: https://tag.adkernel.com/syncr?gdpr={{.GDPR}}&gdpr_consent={{.GDPRConsent}}&r= +blacklisted_apps: ["spamAppID","sketchy-app-id"] `) var invalidAdapterEndpointConfig = []byte(` @@ -176,6 +177,15 @@ func TestFullConfig(t *testing.T) { _, found = cfg.GDPR.NonStandardPublisherMap["appnexus"] cmpBools(t, "cfg.GDPR.NonStandardPublisherMap", found, false) + //Assert the NonStandardPublishers was correctly unmarshalled + cmpStrings(t, "blacklisted_apps", cfg.BlacklistedApps[0], "spamAppID") + cmpStrings(t, "blacklisted_apps", cfg.BlacklistedApps[1], "sketchy-app-id") + + //Assert the BlacklistedAppMap hash table was built correctly + for i := 0; i < len(cfg.BlacklistedApps); i++ { + cmpBools(t, "cfg.BlacklistedAppMap", cfg.BlacklistedAppMap[cfg.BlacklistedApps[i]], true) + } + cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://currency.prebid.org") cmpInts(t, "currency_converter.fetch_interval_seconds", cfg.CurrencyConverter.FetchIntervalSeconds, 1800) cmpStrings(t, "recaptcha_secret", cfg.RecaptchaSecret, "asdfasdfasdfasdf") diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go index 3ce5380c2c3..d479d70877e 100644 --- a/endpoints/openrtb2/auction.go +++ b/endpoints/openrtb2/auction.go @@ -271,22 +271,6 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { } } - impIDs := make(map[string]int, len(req.Imp)) - for index := range req.Imp { - imp := &req.Imp[index] - if firstIndex, ok := impIDs[imp.ID]; ok { - errL = append(errL, fmt.Errorf(`request.imp[%d].id and request.imp[%d].id are both "%s". Imp IDs must be unique.`, firstIndex, index, imp.ID)) - } - impIDs[imp.ID] = index - errs := deps.validateImp(imp, aliases, index) - if len(errs) > 0 { - errL = append(errL, errs...) - } - if fatalError(errs) { - return errL - } - } - if (req.Site == nil && req.App == nil) || (req.Site != nil && req.App != nil) { errL = append(errL, errors.New("request.site or request.app must be defined, but not both.")) return errL @@ -312,6 +296,22 @@ func (deps *endpointDeps) validateRequest(req *openrtb.BidRequest) []error { return errL } + impIDs := make(map[string]int, len(req.Imp)) + for index := range req.Imp { + imp := &req.Imp[index] + if firstIndex, ok := impIDs[imp.ID]; ok { + errL = append(errL, fmt.Errorf(`request.imp[%d].id and request.imp[%d].id are both "%s". Imp IDs must be unique.`, firstIndex, index, imp.ID)) + } + impIDs[imp.ID] = index + errs := deps.validateImp(imp, aliases, index) + if len(errs) > 0 { + errL = append(errL, errs...) + } + if fatalError(errs) { + return errL + } + } + return errL } @@ -789,6 +789,12 @@ func (deps *endpointDeps) validateApp(app *openrtb.App) error { return nil } + if app.ID != "" { + if _, found := deps.cfg.BlacklistedAppMap[app.ID]; found { + return &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", app.ID)} + } + } + if len(app.Ext) > 0 { var a openrtb_ext.ExtApp if err := json.Unmarshal(app.Ext, &a); err != nil { @@ -1166,7 +1172,8 @@ func writeError(errs []error, w http.ResponseWriter) bool { // Checks to see if an error in an error list is a fatal error func fatalError(errL []error) bool { for _, err := range errL { - if errortypes.DecodeError(err) != errortypes.BidderTemporarilyDisabledCode { + errCode := errortypes.DecodeError(err) + if errCode != errortypes.BidderTemporarilyDisabledCode || errCode == errortypes.BlacklistedAppCode { return true } } diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go index 42de2ad5bb1..27cda3b989d 100644 --- a/endpoints/openrtb2/auction_test.go +++ b/endpoints/openrtb2/auction_test.go @@ -291,7 +291,7 @@ func (gr *getResponseFromDirectory) doRequest(t *testing.T, requestData []byte) // NewMetrics() will create a new go_metrics MetricsEngine, bypassing the need for a crafted configuration set to support it. // As a side effect this gives us some coverage of the go_metrics piece of the metrics engine. theMetrics := pbsmetrics.NewMetrics(metrics.NewRegistry(), openrtb_ext.BidderList()) - endpoint, _ := NewEndpoint(&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap) + endpoint, _ := NewEndpoint(&nobidExchange{}, newParamsValidator(t), &mockStoredReqFetcher{}, empty_fetcher.EmptyFetcher{}, &config.Configuration{MaxRequestSize: maxSize, BlacklistedApps: []string{"spam_app"}, BlacklistedAppMap: map[string]bool{"spam_app": true}}, theMetrics, analyticsConf.NewPBSAnalytics(&config.Analytics{}), disabledBidders, aliasJSON, bidderMap) request := httptest.NewRequest("POST", "/openrtb2/auction", bytes.NewReader(requestData)) recorder := httptest.NewRecorder() diff --git a/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json b/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json new file mode 100644 index 00000000000..219595e409a --- /dev/null +++ b/endpoints/openrtb2/sample-requests/blacklisted/blacklisted-app.json @@ -0,0 +1,84 @@ +{ + "description": "This is a perfectly valid request except that it comes from a blacklisted App", + "message": "Invalid request: Prebid-server does not process requests from App ID: spam_app\n", + + "requestPayload": { + "id": "some-request-id", + "user": { + "ext": { + "consent": "gdpr-consent-string", + "prebid": { + "buyeruids": { + "appnexus": "override-appnexus-id-in-cookie" + } + } + } + }, + "app": { + "id": "spam_app" + }, + "regs": { + "ext": { + "gdpr": 1 + } + }, + "imp": [ + { + "id": "some-impression-id", + "banner": { + "format": [ + { + "w": 300, + "h": 250 + }, + { + "w": 300, + "h": 600 + } + ] + }, + "ext": { + "appnexus": { + "placementId": 10433394 + }, + "districtm": { + "placementId": 105 + }, + "rubicon": { + "accountId": 1001, + "siteId": 113932, + "zoneId": 535510 + } + } + } + ], + "tmax": 500, + "ext": { + "prebid": { + "aliases": { + "districtm": "appnexus" + }, + "bidadjustmentfactors": { + "appnexus": 1.01, + "districtm": 0.98, + "rubicon": 0.99 + }, + "cache": { + "bids": {} + }, + "targeting": { + "includewinners": false, + "pricegranularity": { + "precision": 2, + "ranges": [ + { + "max": 20, + "increment": 0.10 + } + ] + } + } + } + } + } +} diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/audio-mimes-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/audio-mimes-empty.json index c8170ec409f..2e0e299923a 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/audio-mimes-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/audio-mimes-empty.json @@ -9,6 +9,9 @@ "mimes": [] } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-hmax.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-hmax.json index 8de1f0ece8c..1cb04fc5c1d 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/banner-hmax.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-hmax.json @@ -9,6 +9,9 @@ "hmax":50 } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-hmin.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-hmin.json index d0531dc8f0d..38eeeb612e0 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/banner-hmin.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-hmin.json @@ -9,6 +9,9 @@ "hmin":50 } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-null.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-null.json index eb54bccddb6..bd8beef8868 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/banner-null.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-null.json @@ -7,6 +7,9 @@ "id": "imp-id", "banner": null } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-wmax.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-wmax.json index 8105a33c005..f1b7d0aeb96 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/banner-wmax.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-wmax.json @@ -9,6 +9,9 @@ "wmax": 50 } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/banner-wmin.json b/endpoints/openrtb2/sample-requests/invalid-whole/banner-wmin.json index a60118000b0..a39e1539ab9 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/banner-wmin.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/banner-wmin.json @@ -9,6 +9,9 @@ "wmin":50 } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/deal-no-id.json b/endpoints/openrtb2/sample-requests/invalid-whole/deal-no-id.json index 42c0a84b82e..ffb3e19cfbc 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/deal-no-id.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/deal-no-id.json @@ -19,6 +19,9 @@ ] } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-empty-object.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-empty-object.json index bd1d5bfab67..9117cad5d45 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/format-empty-object.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-empty-object.json @@ -9,6 +9,9 @@ "format": [{}] } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-no-height.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-no-height.json index a2f5fb19ece..5b5b2e64e29 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/format-no-height.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-no-height.json @@ -14,6 +14,9 @@ ] } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-no-hratio.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-no-hratio.json index 90abf13a0f5..d5a404d2059 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/format-no-hratio.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-no-hratio.json @@ -13,6 +13,9 @@ ] } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/format-two-widths.json b/endpoints/openrtb2/sample-requests/invalid-whole/format-two-widths.json index 703b3744c20..cbe7b4af663 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/format-two-widths.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/format-two-widths.json @@ -14,6 +14,9 @@ ] } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-empty-object.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-empty-object.json index 8217b1d0fbd..f2fc508910d 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-empty-object.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-empty-object.json @@ -4,6 +4,9 @@ "id": "req-id", "imp": [ { } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-empty.json index bfe72226024..af04e79d6cb 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-empty.json @@ -12,6 +12,9 @@ }, "ext": {} } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json index aa48096eac8..c65e9b3ba59 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-invalid-params.json @@ -14,6 +14,9 @@ "appnexus": "invalidParams" } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json index 54fc34eb640..436e62f7174 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-ext-unknown-bidder.json @@ -16,6 +16,9 @@ } } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-no-ext.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-no-ext.json index 023d46fd4ee..3249f077c2c 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-no-ext.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-no-ext.json @@ -11,6 +11,9 @@ ] } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/imp-no-type.json b/endpoints/openrtb2/sample-requests/invalid-whole/imp-no-type.json index e43574408f1..c7b005ca5d3 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/imp-no-type.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/imp-no-type.json @@ -6,6 +6,9 @@ { "id":"imp-id" } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/metric-empty-object.json b/endpoints/openrtb2/sample-requests/invalid-whole/metric-empty-object.json index ad16e5e7f40..b8cc1f7983a 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/metric-empty-object.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/metric-empty-object.json @@ -7,6 +7,9 @@ "id":"imp-id", "metric": [{}] } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/native-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/native-empty.json index bf1ba219b2f..fa2bdded0e7 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/native-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/native-empty.json @@ -7,6 +7,9 @@ "id": "imp-id", "native": {} } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-empty.json index 2ef42591bc1..30fd1f13245 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/video-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-empty.json @@ -7,6 +7,9 @@ "id": "imp-id", "video": {} } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/sample-requests/invalid-whole/video-mimes-empty.json b/endpoints/openrtb2/sample-requests/invalid-whole/video-mimes-empty.json index 84279c3ce7a..5eb5c36f514 100644 --- a/endpoints/openrtb2/sample-requests/invalid-whole/video-mimes-empty.json +++ b/endpoints/openrtb2/sample-requests/invalid-whole/video-mimes-empty.json @@ -9,6 +9,9 @@ "mimes": [] } } - ] + ], + "app": { + "id": "app_001" + } } } diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go index 403bc147979..1e4ece807b4 100644 --- a/endpoints/openrtb2/video_auction.go +++ b/endpoints/openrtb2/video_auction.go @@ -242,14 +242,23 @@ func cleanupVideoBidRequest(videoReq *openrtb_ext.BidRequestVideo, podErrors []P func handleError(labels pbsmetrics.Labels, w http.ResponseWriter, errL []error, ao analytics.AuctionObject) { labels.RequestStatus = pbsmetrics.RequestStatusErr - w.WriteHeader(http.StatusInternalServerError) var errors string + var foundBlacklisted bool = false for _, er := range errL { + if errortypes.DecodeError(er) == errortypes.BlacklistedAppCode { + foundBlacklisted = true + } errors = fmt.Sprintf("%s %s", errors, er.Error()) } + if foundBlacklisted { + w.WriteHeader(http.StatusServiceUnavailable) + ao.Status = http.StatusServiceUnavailable + } else { + w.WriteHeader(http.StatusInternalServerError) + ao.Status = http.StatusInternalServerError + } fmt.Fprintf(w, "Critical error while running the video endpoint: %v", errors) glog.Errorf("/openrtb2/video Critical error: %v", errors) - ao.Status = http.StatusInternalServerError ao.Errors = append(ao.Errors, errL...) } @@ -617,9 +626,19 @@ func (deps *endpointDeps) validateVideoRequest(req *openrtb_ext.BidRequestVideo) } else if req.Site != nil && req.Site.ID == "" && req.Site.Page == "" { err := errors.New("request.site missing required field: id or page") errL = append(errL, err) - } else if req.App != nil && req.App.ID == "" && req.App.Bundle == "" { - err := errors.New("request.app missing required field: id or bundle") - errL = append(errL, err) + } else if req.App != nil { + if req.App.ID != "" { + if _, found := deps.cfg.BlacklistedAppMap[req.App.ID]; found { + err := &errortypes.BlacklistedApp{Message: fmt.Sprintf("Prebid-server does not process requests from App ID: %s", req.App.ID)} + errL = append(errL, err) + return errL, podErrors + } + } else { + if req.App.Bundle == "" { + err := errors.New("request.app missing required field: id or bundle") + errL = append(errL, err) + } + } } if len(req.Video.Mimes) == 0 { diff --git a/errortypes/errortypes.go b/errortypes/errortypes.go index b761a64f5d5..865c885d5d9 100644 --- a/errortypes/errortypes.go +++ b/errortypes/errortypes.go @@ -6,6 +6,7 @@ const ( NoErrorCode = iota TimeoutCode BadInputCode + BlacklistedAppCode BadServerResponseCode FailedToRequestBidsCode BidderTemporarilyDisabledCode @@ -51,6 +52,22 @@ func (err *BadInput) Code() int { return BadInputCode } +// BlacklistedApp should be used when a request App.ID matches an entry in the BlacklistedApps +// environment variable array +// +// These errors will be written to http.ResponseWriter before canceling execution +type BlacklistedApp struct { + Message string +} + +func (err *BlacklistedApp) Error() string { + return err.Message +} + +func (err *BlacklistedApp) Code() int { + return BlacklistedAppCode +} + // BadServerResponse should be used when returning errors which are caused by bad/unexpected behavior on the remote server. // // For example: