Repository: prebid/prebid-server
Pull request #3081 (2023-09-07 01:37:56)

Activities: Back To Spec
The componentName matching was built for an earlier draft where the format was "scope.name". This PR updates to just "name". This also means parsing an Activity Config can no longer return an error. The code is simplified as a result.


Patch:
diff --git a/adapters/huaweiads/huaweiads.go b/adapters/huaweiads/huaweiads.go
index 071f0761..2b7c3c25 100644
--- a/adapters/huaweiads/huaweiads.go
+++ b/adapters/huaweiads/huaweiads.go
@@ -185,6 +185,7 @@ type metaData struct {
 	ApkInfo     apkInfo     `json:"apkInfo"`
 	Duration    int64       `json:"duration"`
 	MediaFile   mediaFile   `json:"mediaFile"`
+	Cta         string      `json:"cta"`
 }
 
 type imageInfo struct {
@@ -535,8 +536,7 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error {
 	// only compute the main image number, type = native1.ImageAssetTypeMain
 	var numMainImage = 0
 	var numVideo = 0
-	var width int64
-	var height int64
+
 	for _, asset := range nativePayload.Assets {
 		// Only one of the {title,img,video,data} objects should be present in each object.
 		if asset.Video != nil {
@@ -547,19 +547,10 @@ func getNativeFormat(adslot30 *adslot30, openRTBImp *openrtb2.Imp) error {
 		if asset.Img != nil {
 			if asset.Img.Type == native1.ImageAssetTypeMain {
 				numMainImage++
-				if asset.Img.H != 0 && asset.Img.W != 0 {
-					width = asset.Img.W
-					height = asset.Img.H
-				} else if asset.Img.WMin != 0 && asset.Img.HMin != 0 {
-					width = asset.Img.WMin
-					height = asset.Img.HMin
-				}
 			}
 			continue
 		}
 	}
-	adslot30.W = width
-	adslot30.H = height
 
 	var detailedCreativeTypeList = make([]string, 0, 2)
 	if numVideo >= 1 {
@@ -1169,6 +1160,11 @@ func (a *adapter) extractAdmNative(adType int32, content *content, bidType openr
 				dataObject.Label = "desc"
 				dataObject.Value = getDecodeValue(content.MetaData.Description)
 			}
+
+			if asset.Data.Type == native1.DataAssetTypeCTAText {
+				dataObject.Type = native1.DataAssetTypeCTAText
+				dataObject.Value = getDecodeValue(content.MetaData.Cta)
+			}
 			responseAsset.Data = &dataObject
 		}
 		var id = asset.ID
diff --git a/adapters/response.go b/adapters/response.go
index 080f4627..8b7d59bd 100644
--- a/adapters/response.go
+++ b/adapters/response.go
@@ -15,7 +15,9 @@ func CheckResponseStatusCodeForErrors(response *ResponseData) error {
 	}
 
 	if response.StatusCode != http.StatusOK {
-		return fmt.Errorf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode)
+		return &errortypes.BadServerResponse{
+			Message: fmt.Sprintf("Unexpected status code: %d. Run with request.debug = 1 for more info", response.StatusCode),
+		}
 	}
 
 	return nil
diff --git a/adapters/sovrn/sovrn.go b/adapters/sovrn/sovrn.go
index 24022490..405e5ebf 100644
--- a/adapters/sovrn/sovrn.go
+++ b/adapters/sovrn/sovrn.go
@@ -20,6 +20,11 @@ type SovrnAdapter struct {
 	URI string
 }
 
+type sovrnImpExt struct {
+	Bidder     openrtb_ext.ExtImpSovrn `json:"bidder"`
+	AdUnitCode string                  `json:"adunitcode,omitempty"`
+}
+
 func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapters.ExtraRequestInfo) ([]*adapters.RequestData, []error) {
 	headers := http.Header{}
 	headers.Add("Content-Type", "application/json")
@@ -74,6 +79,20 @@ func (s *SovrnAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *adapt
 			imp.BidFloor = sovrnExt.BidFloor
 		}
 
+		var impExtBuffer []byte
+		impExtBuffer, err = json.Marshal(&sovrnImpExt{
+			Bidder:     sovrnExt,
+			AdUnitCode: sovrnExt.AdUnitCode,
+		})
+		if err != nil {
+			errs = append(errs, &errortypes.BadInput{
+				Message: err.Error(),
+			})
+			continue
+		}
+
+		imp.Ext = impExtBuffer
+
 		// Validate video params if appropriate
 		video := imp.Video
 		if video != nil {
diff --git a/adapters/undertone/undertone.go b/adapters/undertone/undertone.go
index 62934fa5..7f8dde35 100644
--- a/adapters/undertone/undertone.go
+++ b/adapters/undertone/undertone.go
@@ -26,6 +26,11 @@ type undertoneParams struct {
 	Version string `json:"version"`
 }
 
+type impExt struct {
+	Bidder *openrtb_ext.ExtImpUndertone `json:"bidder,omitempty"`
+	Gpid   string                       `json:"gpid,omitempty"`
+}
+
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	bidder := &adapter{
 		endpoint: config.Endpoint,
@@ -158,24 +163,29 @@ func getImpsAndPublisherId(bidRequest *openrtb2.BidRequest) ([]openrtb2.Imp, int
 	var validImps []openrtb2.Imp
 
 	for _, imp := range bidRequest.Imp {
-		var extImpBidder adapters.ExtImpBidder
-		if err := json.Unmarshal(imp.Ext, &extImpBidder); err != nil {
-			errs = append(errs, getInvalidImpErr(imp.ID, err))
-			continue
-		}
-
-		var extImpUndertone openrtb_ext.ExtImpUndertone
-		if err := json.Unmarshal(extImpBidder.Bidder, &extImpUndertone); err != nil {
+		var ext impExt
+		if err := json.Unmarshal(imp.Ext, &ext); err != nil {
 			errs = append(errs, getInvalidImpErr(imp.ID, err))
 			continue
 		}
 
 		if publisherId == 0 {
-			publisherId = extImpUndertone.PublisherID
+			publisherId = ext.Bidder.PublisherID
 		}
 
-		imp.TagID = strconv.Itoa(extImpUndertone.PlacementID)
+		imp.TagID = strconv.Itoa(ext.Bidder.PlacementID)
 		imp.Ext = nil
+
+		if ext.Gpid != "" {
+			ext.Bidder = nil
+			impExtJson, err := json.Marshal(&ext)
+			if err != nil {
+				errs = append(errs, getInvalidImpErr(imp.ID, err))
+				continue
+			}
+			imp.Ext = impExtJson
+		}
+
 		validImps = append(validImps, imp)
 	}
 
diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go
index 50cb4491..0321fea3 100644
--- a/endpoints/cookie_sync.go
+++ b/endpoints/cookie_sync.go
@@ -149,12 +149,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
 		}
 	}
 
-	activityControl, activitiesErr := privacy.NewActivityControl(&account.Privacy)
-	if activitiesErr != nil {
-		if errortypes.ContainsFatalError([]error{activitiesErr}) {
-			activityControl = privacy.ActivityControl{}
-		}
-	}
+	activityControl := privacy.NewActivityControl(&account.Privacy)
 
 	syncTypeFilter, err := parseTypeFilter(request.FilterSettings)
 	if err != nil {
diff --git a/endpoints/openrtb2/amp_auction.go b/endpoints/openrtb2/amp_auction.go
index 142f7320..2879610d 100644
--- a/endpoints/openrtb2/amp_auction.go
+++ b/endpoints/openrtb2/amp_auction.go
@@ -6,12 +6,13 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/prebid/prebid-server/privacy"
 	"net/http"
 	"net/url"
 	"strings"
 	"time"
 
+	"github.com/prebid/prebid-server/privacy"
+
 	"github.com/buger/jsonparser"
 	"github.com/golang/glog"
 	"github.com/julienschmidt/httprouter"
@@ -229,12 +230,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 
 	tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR)
 
-	activities, activitiesErr := privacy.NewActivityControl(&account.Privacy)
-	if activitiesErr != nil {
-		errL = append(errL, activitiesErr)
-		writeError(errL, w, &labels)
-		return
-	}
+	activityControl := privacy.NewActivityControl(&account.Privacy)
 
 	secGPC := r.Header.Get("Sec-GPC")
 
@@ -253,7 +249,7 @@ func (deps *endpointDeps) AmpAuction(w http.ResponseWriter, r *http.Request, _ h
 		HookExecutor:               hookExecutor,
 		QueryParams:                r.URL.Query(),
 		TCF2Config:                 tcf2Config,
-		Activities:                 activities,
+		Activities:                 activityControl,
 		TmaxAdjustments:            deps.tmaxAdjustments,
 	}
 
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 6fe5aab9..aeb5c20e 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -194,14 +194,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 
 	tcf2Config := gdpr.NewTCF2Config(deps.cfg.GDPR.TCF2, account.GDPR)
 
-	activities, activitiesErr := privacy.NewActivityControl(&account.Privacy)
-	if activitiesErr != nil {
-		errL = append(errL, activitiesErr)
-		if errortypes.ContainsFatalError(errL) {
-			writeError(errL, w, &labels)
-			return
-		}
-	}
+	activityControl := privacy.NewActivityControl(&account.Privacy)
 
 	ctx := context.Background()
 
@@ -252,7 +245,7 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		PubID:                      labels.PubID,
 		HookExecutor:               hookExecutor,
 		TCF2Config:                 tcf2Config,
-		Activities:                 activities,
+		Activities:                 activityControl,
 		TmaxAdjustments:            deps.tmaxAdjustments,
 	}
 	auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
diff --git a/endpoints/openrtb2/video_auction.go b/endpoints/openrtb2/video_auction.go
index 5f848446..a2a519fa 100644
--- a/endpoints/openrtb2/video_auction.go
+++ b/endpoints/openrtb2/video_auction.go
@@ -303,14 +303,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		return
 	}
 
-	activities, activitiesErr := privacy.NewActivityControl(&account.Privacy)
-	if activitiesErr != nil {
-		errL = append(errL, activitiesErr)
-		if errortypes.ContainsFatalError(errL) {
-			handleError(&labels, w, errL, &vo, &debugLog)
-			return
-		}
-	}
+	activityControl := privacy.NewActivityControl(&account.Privacy)
 
 	secGPC := r.Header.Get("Sec-GPC")
 	auctionRequest := &exchange.AuctionRequest{
@@ -324,7 +317,7 @@ func (deps *endpointDeps) VideoAuctionEndpoint(w http.ResponseWriter, r *http.Re
 		PubID:                      labels.PubID,
 		HookExecutor:               hookexecution.EmptyHookExecutor{},
 		TmaxAdjustments:            deps.tmaxAdjustments,
-		Activities:                 activities,
+		Activities:                 activityControl,
 	}
 
 	auctionResponse, err := deps.ex.HoldAuction(ctx, auctionRequest, &debugLog)
diff --git a/endpoints/setuid.go b/endpoints/setuid.go
index 30a1cce2..b9023cfc 100644
--- a/endpoints/setuid.go
+++ b/endpoints/setuid.go
@@ -104,14 +104,9 @@ func NewSetUIDEndpoint(cfg *config.Configuration, syncersByBidder map[string]use
 			return
 		}
 
-		activities, activitiesErr := privacy.NewActivityControl(&account.Privacy)
-		if activitiesErr != nil {
-			if errortypes.ContainsFatalError([]error{activitiesErr}) {
-				activities = privacy.ActivityControl{}
-			}
-		}
+		activityControl := privacy.NewActivityControl(&account.Privacy)
 
-		userSyncActivityAllowed := activities.Allow(privacy.ActivitySyncUser,
+		userSyncActivityAllowed := activityControl.Allow(privacy.ActivitySyncUser,
 			privacy.Component{Type: privacy.ComponentTypeBidder, Name: bidderName})
 		if !userSyncActivityAllowed {
 			w.WriteHeader(http.StatusUnavailableForLegalReasons)
diff --git a/exchange/adapter_builders.go b/exchange/adapter_builders.go
index 84b1f1af..484fe59c 100755
--- a/exchange/adapter_builders.go
+++ b/exchange/adapter_builders.go
@@ -230,6 +230,7 @@ func newAdapterBuilders() map[openrtb_ext.BidderName]adapters.Builder {
 		openrtb_ext.BidderApacdex:           apacdex.Builder,
 		openrtb_ext.BidderApplogy:           applogy.Builder,
 		openrtb_ext.BidderAppnexus:          appnexus.Builder,
+		openrtb_ext.BidderAppstock:          limelightDigital.Builder,
 		openrtb_ext.BidderAppush:            appush.Builder,
 		openrtb_ext.BidderAudienceNetwork:   audienceNetwork.Builder,
 		openrtb_ext.BidderAutomatad:         automatad.Builder,
diff --git a/hooks/hookexecution/executor.go b/hooks/hookexecution/executor.go
index ce516630..5074d4b9 100644
--- a/hooks/hookexecution/executor.go
+++ b/hooks/hookexecution/executor.go
@@ -160,7 +160,7 @@ func (e *hookExecutor) ExecuteProcessedAuctionStage(request *openrtb_ext.Request
 
 	stageName := hooks.StageProcessedAuctionRequest.String()
 	executionCtx := e.newContext(stageName)
-	payload := hookstage.ProcessedAuctionRequestPayload{BidRequest: request.BidRequest}
+	payload := hookstage.ProcessedAuctionRequestPayload{RequestWrapper: request}
 
 	outcome, _, contexts, reject := executeStage(executionCtx, plan, payload, handler, e.metricEngine)
 	outcome.Entity = entityAuctionRequest
diff --git a/hooks/hookstage/processedauctionrequest.go b/hooks/hookstage/processedauctionrequest.go
index 30cb0dab..fe06bc6f 100644
--- a/hooks/hookstage/processedauctionrequest.go
+++ b/hooks/hookstage/processedauctionrequest.go
@@ -3,7 +3,7 @@ package hookstage
 import (
 	"context"
 
-	"github.com/prebid/openrtb/v19/openrtb2"
+	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
 // ProcessedAuctionRequest hooks are invoked after the request is parsed
@@ -23,8 +23,8 @@ type ProcessedAuctionRequest interface {
 	) (HookResult[ProcessedAuctionRequestPayload], error)
 }
 
-// ProcessedAuctionRequestPayload consists of the openrtb2.BidRequest object.
-// Hooks are allowed to modify openrtb2.BidRequest using mutations.
+// ProcessedAuctionRequestPayload consists of the openrtb_ext.RequestWrapper object.
+// Hooks are allowed to modify openrtb_ext.RequestWrapper using mutations.
 type ProcessedAuctionRequestPayload struct {
-	BidRequest *openrtb2.BidRequest
+	RequestWrapper *openrtb_ext.RequestWrapper
 }
diff --git a/openrtb_ext/bid.go b/openrtb_ext/bid.go
index 85cfed5c..2e190389 100644
--- a/openrtb_ext/bid.go
+++ b/openrtb_ext/bid.go
@@ -67,6 +67,7 @@ type ExtBidPrebidMeta struct {
 	RendererName         string          `json:"rendererName,omitempty"`
 	RendererVersion      string          `json:"rendererVersion,omitempty"`
 	RendererData         json.RawMessage `json:"rendererData,omitempty"`
+	RendererUrl          string          `json:"rendererUrl,omitempty"`
 	SecondaryCategoryIDs []string        `json:"secondaryCatIds,omitempty"`
 }
 
diff --git a/openrtb_ext/bidders.go b/openrtb_ext/bidders.go
index c60058eb..deeb1c58 100644
--- a/openrtb_ext/bidders.go
+++ b/openrtb_ext/bidders.go
@@ -54,6 +54,7 @@ var coreBidderNames []BidderName = []BidderName{
 	BidderApacdex,
 	BidderApplogy,
 	BidderAppnexus,
+	BidderAppstock,
 	BidderAppush,
 	BidderAudienceNetwork,
 	BidderAutomatad,
@@ -345,6 +346,7 @@ const (
 	BidderApacdex           BidderName = "apacdex"
 	BidderApplogy           BidderName = "applogy"
 	BidderAppnexus          BidderName = "appnexus"
+	BidderAppstock          BidderName = "appstock"
 	BidderAppush            BidderName = "appush"
 	BidderAudienceNetwork   BidderName = "audienceNetwork"
 	BidderAutomatad         BidderName = "automatad"
diff --git a/openrtb_ext/imp_sovrn.go b/openrtb_ext/imp_sovrn.go
index 38d2f785..362463f9 100644
--- a/openrtb_ext/imp_sovrn.go
+++ b/openrtb_ext/imp_sovrn.go
@@ -1,7 +1,8 @@
 package openrtb_ext
 
 type ExtImpSovrn struct {
-	TagId    string  `json:"tagId,omitempty"`
-	Tagid    string  `json:"tagid,omitempty"`
-	BidFloor float64 `json:"bidfloor"`
+	TagId      string  `json:"tagId,omitempty"`
+	Tagid      string  `json:"tagid,omitempty"`
+	BidFloor   float64 `json:"bidfloor,omitempty"`
+	AdUnitCode string  `json:"adunitcode,omitempty"`
 }
diff --git a/privacy/activitycontrol.go b/privacy/activitycontrol.go
index 5d68ce71..33c138f7 100644
--- a/privacy/activitycontrol.go
+++ b/privacy/activitycontrol.go
@@ -18,66 +18,35 @@ type ActivityControl struct {
 	plans map[Activity]ActivityPlan
 }
 
-func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, error) {
+func NewActivityControl(cfg *config.AccountPrivacy) ActivityControl {
 	ac := ActivityControl{}
-	var err error
 
-	if privacyConf == nil || privacyConf.AllowActivities == nil {
-		return ac, nil
-	}
-
-	plans := make(map[Activity]ActivityPlan)
-
-	plans[ActivitySyncUser], err = buildEnforcementPlan(privacyConf.AllowActivities.SyncUser)
-	if err != nil {
-		return ac, err
-	}
-	plans[ActivityFetchBids], err = buildEnforcementPlan(privacyConf.AllowActivities.FetchBids)
-	if err != nil {
-		return ac, err
-	}
-	plans[ActivityEnrichUserFPD], err = buildEnforcementPlan(privacyConf.AllowActivities.EnrichUserFPD)
-	if err != nil {
-		return ac, err
-	}
-	plans[ActivityReportAnalytics], err = buildEnforcementPlan(privacyConf.AllowActivities.ReportAnalytics)
-	if err != nil {
-		return ac, err
-	}
-	plans[ActivityTransmitUserFPD], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitUserFPD)
-	if err != nil {
-		return ac, err
-	}
-	plans[ActivityTransmitPreciseGeo], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitPreciseGeo)
-	if err != nil {
-		return ac, err
-	}
-	plans[ActivityTransmitUniqueRequestIds], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitUniqueRequestIds)
-	if err != nil {
-		return ac, err
-	}
-	plans[ActivityTransmitTids], err = buildEnforcementPlan(privacyConf.AllowActivities.TransmitTids)
-	if err != nil {
-		return ac, err
+	if cfg == nil || cfg.AllowActivities == nil {
+		return ac
 	}
 
+	plans := make(map[Activity]ActivityPlan, 8)
+	plans[ActivitySyncUser] = buildPlan(cfg.AllowActivities.SyncUser)
+	plans[ActivityFetchBids] = buildPlan(cfg.AllowActivities.FetchBids)
+	plans[ActivityEnrichUserFPD] = buildPlan(cfg.AllowActivities.EnrichUserFPD)
+	plans[ActivityReportAnalytics] = buildPlan(cfg.AllowActivities.ReportAnalytics)
+	plans[ActivityTransmitUserFPD] = buildPlan(cfg.AllowActivities.TransmitUserFPD)
+	plans[ActivityTransmitPreciseGeo] = buildPlan(cfg.AllowActivities.TransmitPreciseGeo)
+	plans[ActivityTransmitUniqueRequestIds] = buildPlan(cfg.AllowActivities.TransmitUniqueRequestIds)
+	plans[ActivityTransmitTids] = buildPlan(cfg.AllowActivities.TransmitTids)
 	ac.plans = plans
 
-	return ac, nil
+	return ac
 }
 
-func buildEnforcementPlan(activity config.Activity) (ActivityPlan, error) {
-	ef := ActivityPlan{}
-	rules, err := activityRulesToEnforcementRules(activity.Rules)
-	if err != nil {
-		return ef, err
+func buildPlan(activity config.Activity) ActivityPlan {
+	return ActivityPlan{
+		rules:         cfgToRules(activity.Rules),
+		defaultResult: cfgToDefaultResult(activity.Default),
 	}
-	ef.defaultResult = activityDefaultToDefaultResult(activity.Default)
-	ef.rules = rules
-	return ef, nil
 }
 
-func activityRulesToEnforcementRules(rules []config.ActivityRule) ([]Rule, error) {
+func cfgToRules(rules []config.ActivityRule) []Rule {
 	var enfRules []Rule
 
 	for _, r := range rules {
@@ -88,34 +57,17 @@ func activityRulesToEnforcementRules(rules []config.ActivityRule) ([]Rule, error
 			result = ActivityDeny
 		}
 
-		componentName, err := conditionToRuleComponentNames(r.Condition.ComponentName)
-		if err != nil {
-			return nil, err
-		}
-
-		er := ComponentEnforcementRule{
+		er := ConditionRule{
 			result:        result,
-			componentName: componentName,
+			componentName: r.Condition.ComponentName,
 			componentType: r.Condition.ComponentType,
 		}
 		enfRules = append(enfRules, er)
 	}
-	return enfRules, nil
-}
-
-func conditionToRuleComponentNames(conditions []string) ([]Component, error) {
-	sn := make([]Component, 0)
-	for _, condition := range conditions {
-		scope, err := ParseComponent(condition)
-		if err != nil {
-			return nil, err
-		}
-		sn = append(sn, scope)
-	}
-	return sn, nil
+	return enfRules
 }
 
-func activityDefaultToDefaultResult(activityDefault *bool) bool {
+func cfgToDefaultResult(activityDefault *bool) bool {
 	if activityDefault == nil {
 		return defaultActivityResult
 	}
diff --git a/privacy/component.go b/privacy/component.go
index a203c6bd..33767938 100644
--- a/privacy/component.go
+++ b/privacy/component.go
@@ -1,8 +1,6 @@
 package privacy
 
 import (
-	"errors"
-	"fmt"
 	"strings"
 )
 
@@ -18,48 +16,10 @@ type Component struct {
 	Name string
 }
 
-func (c Component) Matches(target Component) bool {
-	return strings.EqualFold(c.Type, target.Type) && (c.Name == "*" || strings.EqualFold(c.Name, target.Name))
+func (c Component) MatchesName(v string) bool {
+	return strings.EqualFold(c.Name, v)
 }
 
-var ErrComponentEmpty = errors.New("unable to parse empty component")
-
-func ParseComponent(v string) (Component, error) {
-	if len(v) == 0 {
-		return Component{}, ErrComponentEmpty
-	}
-
-	split := strings.Split(v, ".")
-
-	if len(split) == 2 {
-		if !validComponentType(split[0]) {
-			return Component{}, fmt.Errorf("unable to parse component (invalid type): %s", v)
-		}
-		return Component{
-			Type: split[0],
-			Name: split[1],
-		}, nil
-	}
-
-	if len(split) == 1 {
-		return Component{
-			Type: ComponentTypeBidder,
-			Name: split[0],
-		}, nil
-	}
-
-	return Component{}, fmt.Errorf("unable to parse component: %s", v)
-}
-
-func validComponentType(t string) bool {
-	t = strings.ToLower(t)
-
-	if t == ComponentTypeBidder ||
-		t == ComponentTypeAnalytics ||
-		t == ComponentTypeRealTimeData ||
-		t == ComponentTypeGeneral {
-		return true
-	}
-
-	return false
+func (c Component) MatchesType(v string) bool {
+	return strings.EqualFold(c.Type, v)
 }
diff --git a/privacy/rule.go b/privacy/rule.go
new file mode 100644
index 00000000..47d0913c
--- /dev/null
+++ b/privacy/rule.go
@@ -0,0 +1,5 @@
+package privacy
+
+type Rule interface {
+	Evaluate(target Component) ActivityResult
+}
diff --git a/privacy/rules.go b/privacy/rule_condition.go
similarity index 64%
rename from privacy/rules.go
rename to privacy/rule_condition.go
index 502e10ef..705543e5 100644
--- a/privacy/rules.go
+++ b/privacy/rule_condition.go
@@ -1,20 +1,12 @@
 package privacy
 
-import (
-	"strings"
-)
-
-type Rule interface {
-	Evaluate(target Component) ActivityResult
-}
-
-type ComponentEnforcementRule struct {
+type ConditionRule struct {
 	result        ActivityResult
-	componentName []Component
+	componentName []string
 	componentType []string
 }
 
-func (r ComponentEnforcementRule) Evaluate(target Component) ActivityResult {
+func (r ConditionRule) Evaluate(target Component) ActivityResult {
 	if matched := evaluateComponentName(target, r.componentName); !matched {
 		return ActivityAbstain
 	}
@@ -26,7 +18,7 @@ func (r ComponentEnforcementRule) Evaluate(target Component) ActivityResult {
 	return r.result
 }
 
-func evaluateComponentName(target Component, componentNames []Component) bool {
+func evaluateComponentName(target Component, componentNames []string) bool {
 	// no clauses are considered a match
 	if len(componentNames) == 0 {
 		return true
@@ -34,7 +26,7 @@ func evaluateComponentName(target Component, componentNames []Component) bool {
 
 	// if there are clauses, at least one needs to match
 	for _, n := range componentNames {
-		if n.Matches(target) {
+		if target.MatchesName(n) {
 			return true
 		}
 	}
@@ -49,8 +41,8 @@ func evaluateComponentType(target Component, componentTypes []string) bool {
 	}
 
 	// if there are clauses, at least one needs to match
-	for _, s := range componentTypes {
-		if strings.EqualFold(s, target.Type) {
+	for _, t := range componentTypes {
+		if target.MatchesType(t) {
 			return true
 		}
 	}
diff --git a/static/bidder-info/appstock.yaml b/static/bidder-info/appstock.yaml
new file mode 100644
index 00000000..28f420e9
--- /dev/null
+++ b/static/bidder-info/appstock.yaml
@@ -0,0 +1,16 @@
+endpoint: "http://ads-pbs.pre.vr-tb.com/openrtb/{{.PublisherID}}?host={{.Host}}"
+maintainer:
+  email: "engineering@project-limelight.com"
+capabilities:
+  app:
+    mediaTypes:
+      - banner
+      - video
+      - audio
+      - native
+  site:
+    mediaTypes:
+      - banner
+      - video
+      - audio
+      - native
diff --git a/static/bidder-params/appstock.json b/static/bidder-params/appstock.json
new file mode 100644
index 00000000..eb2251e4
--- /dev/null
+++ b/static/bidder-params/appstock.json
@@ -0,0 +1,23 @@
+{
+  "$schema": "http://json-schema.org/draft-04/schema#",
+  "title": "Appstock Adapter Params",
+  "description": "A schema which validates params accepted by the Appstock adapter",
+  "type": "object",
+
+  "properties": {
+    "host": {
+      "type": "string",
+      "description": "Ad network's RTB host",
+      "format": "hostname",
+      "pattern": "^.+\\..+$"
+    },
+    "publisherId": {
+      "type": ["integer", "string"],
+      "description": "Publisher ID",
+      "minimum": 1,
+      "pattern": "^[1-9][0-9]*$"
+    }
+  },
+
+  "required": ["host", "publisherId"]
+}
diff --git a/static/bidder-params/sovrn.json b/static/bidder-params/sovrn.json
index 6b345b55..d821a808 100644
--- a/static/bidder-params/sovrn.json
+++ b/static/bidder-params/sovrn.json
@@ -16,6 +16,10 @@
     "bidfloor": {
       "type": "number",
       "description": "The minimium acceptable bid, in CPM, using US Dollars"
+    },
+    "adunitcode": {
+      "type": "string",
+      "description": "The string which identifies Ad Unit"
     }
   },
   "oneOf": [


Test patch:
diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json
index 8a7ca1b0..24aad066 100644
--- a/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json
+++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeIncludeVideo.json
@@ -95,9 +95,7 @@
               "detailedCreativeTypeList": [
                 "903"
               ],
-              "h": 200,
-              "test": 1,
-              "w": 200
+              "test": 1
             }
           ],
           "device": {
diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json
index 240098e0..8e86675b 100644
--- a/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json
+++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeSingleImage.json
@@ -5,7 +5,7 @@
       {
         "id": "test-imp-id",
         "native": {
-          "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":100,\"title\":{\"len\":90},\"required\":1},{\"id\":103,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":105,\"data\":{\"type\":2,\"len\":90},\"required\":1}],\"ver\":\"1.2\"}",
+          "request": "{\"context\":2,\"contextsubtype\":20,\"plcmttype\":1,\"plcmtcnt\":1,\"seq\":0,\"aurlsupport\":0,\"durlsupport\":0,\"eventtrackers\":[{\"event\":1,\"methods\":[1,2]}],\"privacy\":0,\"assets\":[{\"id\":101,\"title\":{\"len\":90},\"required\":1},{\"id\":102,\"img\":{\"type\":3,\"wmin\":200,\"hmin\":200},\"required\":1},{\"id\":103,\"data\":{\"type\":2,\"len\":90},\"required\":1},{\"id\":105,\"data\":{\"type\":12,\"len\":90}}],\"ver\":\"1.2\"}",
           "ver": "1.2"
         },
         "ext": {
@@ -93,9 +93,7 @@
               "detailedCreativeTypeList": [
                 "901"
               ],
-              "h": 200,
-              "test": 1,
-              "w": 200
+              "test": 1
             }
           ],
           "device": {
@@ -166,6 +164,7 @@
                     "appId": "101219405",
                     "appPromotionChannel": "401721412",
                     "clickUrl": "https://ads.huawei.com/usermgtportal/home/index.html#/",
+                    "cta": "install",
                     "duration": 6038,
                     "description": "",
                     "icon": [
@@ -300,7 +299,7 @@
               "huaweiads"
             ],
             "crid": "58022259",
-            "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":100,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":103,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":720,\"h\":1280}},{\"id\":105,\"data\":{\"label\":\"desc\",\"value\":\"\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}",
+            "adm": "{\"ver\":\"1.2\",\"assets\":[{\"id\":101,\"title\":{\"text\":\"/test/\",\"len\":6}},{\"id\":102,\"img\":{\"type\":3,\"url\":\"http://image.jpg\",\"w\":720,\"h\":1280}},{\"id\":103,\"data\":{\"label\":\"desc\",\"value\":\"\"}},{\"id\":105,\"data\":{\"type\":12,\"value\":\"install\"}}],\"link\":{\"url\":\"https://ads.huawei.com/usermgtportal/home/index.html#/\",\"clicktrackers\":[\"http://test/click\"]},\"eventtrackers\":[{\"event\":1,\"method\":1,\"url\":\"http://test/imp\"}]}",
             "id": "test-imp-id",
             "impid": "test-imp-id",
             "price": 2.8,
diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json
index 9b46072d..c29b2352 100644
--- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json
+++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImage.json
@@ -92,9 +92,7 @@
               "detailedCreativeTypeList": [
                 "904"
               ],
-              "h": 200,
-              "test": 1,
-              "w": 200
+              "test": 1
             }
           ],
           "device": {
diff --git a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json
index f78f50e2..5d3af506 100644
--- a/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json
+++ b/adapters/huaweiads/huaweiadstest/exemplary/nativeThreeImageIncludeIcon.json
@@ -93,9 +93,7 @@
               "detailedCreativeTypeList": [
                 "904"
               ],
-              "h": 200,
-              "test": 1,
-              "w": 200
+              "test": 1
             }
           ],
           "device": {
diff --git a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json
index c38bc9f6..14667370 100644
--- a/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json
+++ b/adapters/huaweiads/huaweiadstest/supplemental/bad_response_not_native.json
@@ -93,9 +93,7 @@
               "test": 1,
               "detailedCreativeTypeList": [
                 "903"
-              ],
-              "h": 200,
-              "w": 200
+              ]
             }
           ],
           "device": {
diff --git a/adapters/response_test.go b/adapters/response_test.go
index 824a295e..0651ff26 100644
--- a/adapters/response_test.go
+++ b/adapters/response_test.go
@@ -8,17 +8,33 @@ import (
 )
 
 func TestCheckResponseStatusCodeForErrors(t *testing.T) {
-	t.Run("bad_input", func(t *testing.T) {
-		err := CheckResponseStatusCodeForErrors(&ResponseData{StatusCode: 400})
-		expectedErr := &errortypes.BadInput{Message: "Unexpected status code: 400. Run with request.debug = 1 for more info"}
-		assert.Equal(t, expectedErr.Error(), err.Error())
-	})
+	testCases := []struct {
+		name           string
+		responseStatus int
+		expectedErr    error
+	}{
+		{
+			name:           "bad_input",
+			responseStatus: 400,
+			expectedErr: &errortypes.BadInput{
+				Message: "Unexpected status code: 400. Run with request.debug = 1 for more info",
+			},
+		},
+		{
+			name:           "internal_server_error",
+			responseStatus: 500,
+			expectedErr: &errortypes.BadServerResponse{
+				Message: "Unexpected status code: 500. Run with request.debug = 1 for more info",
+			},
+		},
+	}
 
-	t.Run("internal_server_error", func(t *testing.T) {
-		err := CheckResponseStatusCodeForErrors(&ResponseData{StatusCode: 500})
-		expectedErrMessage := "Unexpected status code: 500. Run with request.debug = 1 for more info"
-		assert.Equal(t, expectedErrMessage, err.Error())
-	})
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := CheckResponseStatusCodeForErrors(&ResponseData{StatusCode: tc.responseStatus})
+			assert.Equal(t, tc.expectedErr, err)
+		})
+	}
 }
 
 func TestIsResponseStatusCodeNoContent(t *testing.T) {
diff --git a/adapters/sovrn/sovrntest/supplemental/adunitcode.json b/adapters/sovrn/sovrntest/supplemental/adunitcode.json
new file mode 100644
index 00000000..47197520
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/adunitcode.json
@@ -0,0 +1,125 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "bidder": {
+            "tagid": "123456",
+            "adunitcode": "sovrn_auc"
+          }
+        }
+      }
+    ],
+    "device": { },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": ["application/json"],
+          "Cookie": ["ljt_reader=test_reader_id"]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid": "123456",
+              "ext": {
+                "adunitcode": "sovrn_auc",
+                "bidder": {
+                  "tagid": "123456",
+                  "adunitcode": "sovrn_auc"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": { }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "some-test-ad",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id",
+            "price": 3.5,
+            "adm": "some-test-ad",
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/sovrn/sovrntest/supplemental/fpd.json b/adapters/sovrn/sovrntest/supplemental/fpd.json
index 005d159d..e4368c13 100644
--- a/adapters/sovrn/sovrntest/supplemental/fpd.json
+++ b/adapters/sovrn/sovrntest/supplemental/fpd.json
@@ -86,8 +86,7 @@
               "tagid": "123456",
               "ext": {
                 "bidder": {
-                  "tagid": "123456",
-                  "anotherid": "654321"
+                  "tagid": "123456"
                 }
               }
             }
diff --git a/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json b/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json
new file mode 100644
index 00000000..fdb14371
--- /dev/null
+++ b/adapters/sovrn/sovrntest/supplemental/invalid-adunitcode.json
@@ -0,0 +1,123 @@
+{
+  "mockBidRequest": {
+    "id": "test-request-id",
+    "imp": [
+      {
+        "id": "test-imp-id",
+        "banner": {
+          "format": [
+            {
+              "w": 300,
+              "h": 250
+            },
+            {
+              "w": 300,
+              "h": 600
+            }
+          ]
+        },
+        "ext": {
+          "AdUnitCode": "sovrn_auc",
+          "bidder": {
+            "tagid": "123456"
+          }
+        }
+      }
+    ],
+    "device": { },
+    "site": {
+      "domain": "www.publisher.com",
+      "page": "http://www.publisher.com/awesome/site"
+    },
+    "user": {
+      "buyeruid": "test_reader_id"
+    }
+  },
+
+  "httpCalls": [
+    {
+      "expectedRequest": {
+        "headers": {
+          "Content-Type": ["application/json"],
+          "Cookie": ["ljt_reader=test_reader_id"]
+        },
+        "uri": "http://sovrn.com/test/endpoint",
+        "body": {
+          "id": "test-request-id",
+          "imp": [
+            {
+              "id": "test-imp-id",
+              "banner": {
+                "format": [
+                  {
+                    "w": 300,
+                    "h": 250
+                  },
+                  {
+                    "w": 300,
+                    "h": 600
+                  }
+                ]
+              },
+              "tagid": "123456",
+              "ext": {
+                "bidder": {
+                  "tagid": "123456"
+                }
+              }
+            }
+          ],
+          "site": {
+            "domain": "www.publisher.com",
+            "page": "http://www.publisher.com/awesome/site"
+          },
+          "user": {
+            "buyeruid": "test_reader_id"
+          },
+          "device": { }
+        }
+      },
+      "mockResponse": {
+        "status": 200,
+        "body": {
+          "id": "test-request-id",
+          "seatbid": [
+            {
+              "bid": [
+                {
+                  "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "impid": "test-imp-id",
+                  "price": 3.5,
+                  "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+                  "adm": "some-test-ad",
+                  "w": 300,
+                  "h": 250
+                }
+              ]
+            }
+          ]
+        }
+      }
+    }
+  ],
+
+  "expectedBidResponses": [
+    {
+      "currency": "USD",
+      "bids": [
+        {
+          "bid": {
+            "id": "a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "impid": "test-imp-id",
+            "price": 3.5,
+            "adm": "some-test-ad",
+            "nurl": "http://sovrn.com/rtb/impression?bannerid=138743&campaignid=3699&zoneid=449642&cb=69493397&tid=a_449642_554a13d3b9f348fba707cf83f0f63800",
+            "w": 300,
+            "h": 250
+          },
+          "type": "banner"
+        }
+      ]
+    }
+  ]
+}
diff --git a/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json b/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json
index e6b4a83a..71ad2ae1 100644
--- a/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json
+++ b/adapters/undertone/undertonetest/exemplary/multi-imp-app-request.json
@@ -54,7 +54,8 @@
           "bidder": {
             "publisherId": 1234,
             "placementId": 123456
-          }
+          },
+          "gpid": "gpid-value"
         }
       }
     ],
@@ -116,7 +117,10 @@
                   4
                 ]
               },
-              "tagid": "123456"
+              "tagid": "123456",
+              "ext": {
+                "gpid": "gpid-value"
+              }
             }
           ],
           "app": {
diff --git a/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json b/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json
index 0de38caa..9e59ffbc 100644
--- a/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json
+++ b/adapters/undertone/undertonetest/exemplary/multi-imp-site-request.json
@@ -20,7 +20,8 @@
           "bidder": {
             "publisherId": 1234,
             "placementId": 12345
-          }
+          },
+          "gpid": "gpid-value"
         }
       },
       {
@@ -87,7 +88,10 @@
                   }
                 ]
               },
-              "tagid": "12345"
+              "tagid": "12345",
+              "ext": {
+                "gpid": "gpid-value"
+              }
             },
             {
               "id": "test-imp-video-id",
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index 45c27f0e..a674e9db 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -974,38 +974,6 @@ func TestCookieSyncParseRequest(t *testing.T) {
 			expectedError:        errCookieSyncAccountBlocked.Error(),
 			givenAccountRequired: true,
 		},
-
-		{
-			description: "Account Defaults - Invalid Activities",
-			givenBody: strings.NewReader(`{` +
-				`"bidders":["a", "b"],` +
-				`"account":"ValidAccountInvalidActivities"` +
-				`}`),
-			givenGDPRConfig:  config.GDPR{Enabled: true, DefaultValue: "0"},
-			givenCCPAEnabled: true,
-			givenConfig: config.UserSync{
-				Cooperative: config.UserSyncCooperative{
-					EnabledByDefault: false,
-					PriorityGroups:   [][]string{{"a", "b", "c"}},
-				},
-			},
-			expectedPrivacy: privacy.Policies{},
-			expectedRequest: usersync.Request{
-				Bidders: []string{"a", "b"},
-				Cooperative: usersync.Cooperative{
-					Enabled:        false,
-					PriorityGroups: [][]string{{"a", "b", "c"}},
-				},
-				Limit: 0,
-				Privacy: usersyncPrivacy{
-					gdprPermissions: &fakePermissions{},
-				},
-				SyncTypeFilter: usersync.SyncTypeFilter{
-					IFrame:   usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
-					Redirect: usersync.NewUniformBidderFilter(usersync.BidderFilterModeInclude),
-				},
-			},
-		},
 	}
 
 	for _, test := range testCases {
@@ -1934,8 +1902,7 @@ func TestCookieSyncActivityControlIntegration(t *testing.T) {
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			activities, err := privacy.NewActivityControl(test.accountPrivacy)
-			assert.NoError(t, err)
+			activities := privacy.NewActivityControl(test.accountPrivacy)
 			up := usersyncPrivacy{
 				activityControl: activities,
 			}
diff --git a/endpoints/openrtb2/video_auction_test.go b/endpoints/openrtb2/video_auction_test.go
index 3c2528e2..da4522eb 100644
--- a/endpoints/openrtb2/video_auction_test.go
+++ b/endpoints/openrtb2/video_auction_test.go
@@ -65,20 +65,6 @@ func TestVideoEndpointImpressionsNumber(t *testing.T) {
 	assert.Equal(t, resp.AdPods[0].Targeting[0].HbDeal, "ABC_123", "If DealID exists in bid response, hb_deal targeting needs to be added to resp")
 }
 
-func TestVideoEndpointInvalidPrivacyConfig(t *testing.T) {
-	ex := &mockExchangeVideo{}
-	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample.json")
-	req := httptest.NewRequest("POST", "/openrtb2/video", strings.NewReader(reqBody))
-	recorder := httptest.NewRecorder()
-
-	deps := mockDepsInvalidPrivacy(t, ex)
-	deps.VideoAuctionEndpoint(recorder, req, nil)
-
-	respBytes := recorder.Body.Bytes()
-	expectedErrorMessage := "Critical error while running the video endpoint:  unable to parse component: bidderA.BidderB.bidderC"
-	assert.Equal(t, expectedErrorMessage, string(respBytes), "error message is incorrect")
-}
-
 func TestVideoEndpointImpressionsDuration(t *testing.T) {
 	ex := &mockExchangeVideo{}
 	reqBody := readVideoTestFile(t, "sample-requests/video/video_valid_sample_different_durations.json")
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 919f8dce..8ca96880 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -2352,10 +2352,7 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 		impExtInfoMap[impID] = ImpExtInfo{}
 	}
 
-	activityControl, err := privacy.NewActivityControl(spec.AccountPrivacy)
-	if err != nil {
-		t.Errorf("%s: Exchange returned an unexpected error. Got %s", filename, err.Error())
-	}
+	activityControl := privacy.NewActivityControl(spec.AccountPrivacy)
 
 	auctionRequest := &AuctionRequest{
 		BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: &spec.IncomingRequest.OrtbRequest},
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 1ab86fcd..a1ca0a60 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -4364,8 +4364,7 @@ func TestCleanOpenRTBRequestsActivities(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		activities, err := privacy.NewActivityControl(&test.privacyConfig)
-		assert.NoError(t, err, "")
+		activities := privacy.NewActivityControl(&test.privacyConfig)
 		auctionReq := AuctionRequest{
 			BidRequestWrapper: &openrtb_ext.RequestWrapper{BidRequest: test.req},
 			UserSyncs:         &emptyUsersync{},
diff --git a/hooks/hookexecution/mocks_test.go b/hooks/hookexecution/mocks_test.go
index e8670ff8..ff543c38 100644
--- a/hooks/hookexecution/mocks_test.go
+++ b/hooks/hookexecution/mocks_test.go
@@ -131,7 +131,7 @@ func (e mockTimeoutHook) HandleProcessedAuctionHook(_ context.Context, _ hooksta
 	time.Sleep(20 * time.Millisecond)
 	c := hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{}
 	c.AddMutation(func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) {
-		payload.BidRequest.User.CustomData = "some-custom-data"
+		payload.RequestWrapper.User.CustomData = "some-custom-data"
 		return payload, nil
 	}, hookstage.MutationUpdate, "bidRequest", "user.customData")
 
@@ -305,12 +305,20 @@ func (e mockUpdateBidRequestHook) HandleProcessedAuctionHook(_ context.Context,
 	c := hookstage.ChangeSet[hookstage.ProcessedAuctionRequestPayload]{}
 	c.AddMutation(
 		func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) {
-			payload.BidRequest.User.Yob = 2000
+			payload.RequestWrapper.User.Yob = 2000
+			userExt, err := payload.RequestWrapper.GetUserExt()
+			if err != nil {
+				return payload, err
+			}
+			newPrebidExt := &openrtb_ext.ExtUserPrebid{
+				BuyerUIDs: map[string]string{"some": "id"},
+			}
+			userExt.SetPrebid(newPrebidExt)
 			return payload, nil
 		}, hookstage.MutationUpdate, "bidRequest", "user.yob",
 	).AddMutation(
 		func(payload hookstage.ProcessedAuctionRequestPayload) (hookstage.ProcessedAuctionRequestPayload, error) {
-			payload.BidRequest.User.Consent = "true"
+			payload.RequestWrapper.User.Consent = "true"
 			return payload, nil
 		}, hookstage.MutationUpdate, "bidRequest", "user.consent",
 	)
diff --git a/privacy/activitycontrol_test.go b/privacy/activitycontrol_test.go
index db5d588d..11a9bdc7 100644
--- a/privacy/activitycontrol_test.go
+++ b/privacy/activitycontrol_test.go
@@ -1,7 +1,6 @@
 package privacy
 
 import (
-	"errors"
 	"testing"
 
 	"github.com/prebid/prebid-server/config"
@@ -14,136 +13,48 @@ func TestNewActivityControl(t *testing.T) {
 		name            string
 		privacyConf     config.AccountPrivacy
 		activityControl ActivityControl
-		err             error
 	}{
 		{
-			name:            "privacy_config_is_empty",
+			name:            "empty",
 			privacyConf:     config.AccountPrivacy{},
 			activityControl: ActivityControl{plans: nil},
-			err:             nil,
 		},
 		{
-			name: "privacy_config_is_specified_and_correct",
+			name: "specified_and_correct",
 			privacyConf: config.AccountPrivacy{
 				AllowActivities: &config.AllowActivities{
-					SyncUser:                 getDefaultActivityConfig(),
-					FetchBids:                getDefaultActivityConfig(),
-					EnrichUserFPD:            getDefaultActivityConfig(),
-					ReportAnalytics:          getDefaultActivityConfig(),
-					TransmitUserFPD:          getDefaultActivityConfig(),
-					TransmitPreciseGeo:       getDefaultActivityConfig(),
-					TransmitUniqueRequestIds: getDefaultActivityConfig(),
-					TransmitTids:             getDefaultActivityConfig(),
+					SyncUser:                 getTestActivityConfig(true),
+					FetchBids:                getTestActivityConfig(true),
+					EnrichUserFPD:            getTestActivityConfig(true),
+					ReportAnalytics:          getTestActivityConfig(true),
+					TransmitUserFPD:          getTestActivityConfig(true),
+					TransmitPreciseGeo:       getTestActivityConfig(false),
+					TransmitUniqueRequestIds: getTestActivityConfig(true),
+					TransmitTids:             getTestActivityConfig(true),
 				},
 			},
 			activityControl: ActivityControl{plans: map[Activity]ActivityPlan{
-				ActivitySyncUser:                 getDefaultActivityPlan(),
-				ActivityFetchBids:                getDefaultActivityPlan(),
-				ActivityEnrichUserFPD:            getDefaultActivityPlan(),
-				ActivityReportAnalytics:          getDefaultActivityPlan(),
-				ActivityTransmitUserFPD:          getDefaultActivityPlan(),
-				ActivityTransmitPreciseGeo:       getDefaultActivityPlan(),
-				ActivityTransmitUniqueRequestIds: getDefaultActivityPlan(),
-				ActivityTransmitTids:             getDefaultActivityPlan(),
+				ActivitySyncUser:                 getTestActivityPlan(ActivityAllow),
+				ActivityFetchBids:                getTestActivityPlan(ActivityAllow),
+				ActivityEnrichUserFPD:            getTestActivityPlan(ActivityAllow),
+				ActivityReportAnalytics:          getTestActivityPlan(ActivityAllow),
+				ActivityTransmitUserFPD:          getTestActivityPlan(ActivityAllow),
+				ActivityTransmitPreciseGeo:       getTestActivityPlan(ActivityDeny),
+				ActivityTransmitUniqueRequestIds: getTestActivityPlan(ActivityAllow),
+				ActivityTransmitTids:             getTestActivityPlan(ActivityAllow),
 			}},
-			err: nil,
-		},
-		{
-			name: "privacy_config_is_specified_and_SyncUser_is_incorrect",
-			privacyConf: config.AccountPrivacy{
-				AllowActivities: &config.AllowActivities{
-					SyncUser: getIncorrectActivityConfig(),
-				},
-			},
-			activityControl: ActivityControl{plans: nil},
-			err:             errors.New("unable to parse component: bidder.bidderA.bidderB"),
-		},
-		{
-			name: "privacy_config_is_specified_and_FetchBids_is_incorrect",
-			privacyConf: config.AccountPrivacy{
-				AllowActivities: &config.AllowActivities{
-					FetchBids: getIncorrectActivityConfig(),
-				},
-			},
-			activityControl: ActivityControl{plans: nil},
-			err:             errors.New("unable to parse component: bidder.bidderA.bidderB"),
-		},
-		{
-			name: "privacy_config_is_specified_and_EnrichUserFPD_is_incorrect",
-			privacyConf: config.AccountPrivacy{
-				AllowActivities: &config.AllowActivities{
-					EnrichUserFPD: getIncorrectActivityConfig(),
-				},
-			},
-			activityControl: ActivityControl{plans: nil},
-			err:             errors.New("unable to parse component: bidder.bidderA.bidderB"),
-		},
-		{
-			name: "privacy_config_is_specified_and_ReportAnalytics_is_incorrect",
-			privacyConf: config.AccountPrivacy{
-				AllowActivities: &config.AllowActivities{
-					ReportAnalytics: getIncorrectActivityConfig(),
-				},
-			},
-			activityControl: ActivityControl{plans: nil},
-			err:             errors.New("unable to parse component: bidder.bidderA.bidderB"),
-		},
-		{
-			name: "privacy_config_is_specified_and_TransmitUserFPD_is_incorrect",
-			privacyConf: config.AccountPrivacy{
-				AllowActivities: &config.AllowActivities{
-					TransmitUserFPD: getIncorrectActivityConfig(),
-				},
-			},
-			activityControl: ActivityControl{plans: nil},
-			err:             errors.New("unable to parse component: bidder.bidderA.bidderB"),
-		},
-		{
-			name: "privacy_config_is_specified_and_TransmitPreciseGeo_is_incorrect",
-			privacyConf: config.AccountPrivacy{
-				AllowActivities: &config.AllowActivities{
-					TransmitPreciseGeo: getIncorrectActivityConfig(),
-				},
-			},
-			activityControl: ActivityControl{plans: nil},
-			err:             errors.New("unable to parse component: bidder.bidderA.bidderB"),
-		},
-		{
-			name: "privacy_config_is_specified_and_TransmitUniqueRequestIds_is_incorrect",
-			privacyConf: config.AccountPrivacy{
-				AllowActivities: &config.AllowActivities{
-					TransmitUniqueRequestIds: getIncorrectActivityConfig(),
-				},
-			},
-			activityControl: ActivityControl{plans: nil},
-			err:             errors.New("unable to parse component: bidder.bidderA.bidderB"),
-		},
-		{
-			name: "privacy_config_is_specified_and_TransmitTids_is_incorrect",
-			privacyConf: config.AccountPrivacy{
-				AllowActivities: &config.AllowActivities{
-					TransmitTids: getIncorrectActivityConfig(),
-				},
-			},
-			activityControl: ActivityControl{plans: nil},
-			err:             errors.New("unable to parse component: bidder.bidderA.bidderB"),
 		},
 	}
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			actualAC, actualErr := NewActivityControl(&test.privacyConf)
-			if test.err == nil {
-				assert.Equal(t, test.activityControl, actualAC)
-				assert.NoError(t, actualErr)
-			} else {
-				assert.EqualError(t, actualErr, test.err.Error())
-			}
+			actualAC := NewActivityControl(&test.privacyConf)
+			assert.Equal(t, test.activityControl, actualAC)
 		})
 	}
 }
 
-func TestActivityDefaultToDefaultResult(t *testing.T) {
+func TestCfgToDefaultResult(t *testing.T) {
 	testCases := []struct {
 		name            string
 		activityDefault *bool
@@ -168,14 +79,13 @@ func TestActivityDefaultToDefaultResult(t *testing.T) {
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			actualResult := activityDefaultToDefaultResult(test.activityDefault)
+			actualResult := cfgToDefaultResult(test.activityDefault)
 			assert.Equal(t, test.expectedResult, actualResult)
 		})
 	}
 }
 
-func TestAllowActivityControl(t *testing.T) {
-
+func TestActivityControlAllow(t *testing.T) {
 	testCases := []struct {
 		name            string
 		activityControl ActivityControl
@@ -193,7 +103,7 @@ func TestAllowActivityControl(t *testing.T) {
 		{
 			name: "activity_not_defined",
 			activityControl: ActivityControl{plans: map[Activity]ActivityPlan{
-				ActivitySyncUser: getDefaultActivityPlan()}},
+				ActivitySyncUser: getTestActivityPlan(ActivityAllow)}},
 			activity:       ActivityFetchBids,
 			target:         Component{Type: "bidder", Name: "bidderA"},
 			activityResult: true,
@@ -201,7 +111,7 @@ func TestAllowActivityControl(t *testing.T) {
 		{
 			name: "activity_defined_but_not_found_default_returned",
 			activityControl: ActivityControl{plans: map[Activity]ActivityPlan{
-				ActivityFetchBids: getDefaultActivityPlan()}},
+				ActivityFetchBids: getTestActivityPlan(ActivityAllow)}},
 			activity:       ActivityFetchBids,
 			target:         Component{Type: "bidder", Name: "bidderB"},
 			activityResult: true,
@@ -209,7 +119,7 @@ func TestAllowActivityControl(t *testing.T) {
 		{
 			name: "activity_defined_and_allowed",
 			activityControl: ActivityControl{plans: map[Activity]ActivityPlan{
-				ActivityFetchBids: getDefaultActivityPlan()}},
+				ActivityFetchBids: getTestActivityPlan(ActivityAllow)}},
 			activity:       ActivityFetchBids,
 			target:         Component{Type: "bidder", Name: "bidderA"},
 			activityResult: true,
@@ -225,13 +135,12 @@ func TestAllowActivityControl(t *testing.T) {
 	}
 }
 
-// constants
-func getDefaultActivityConfig() config.Activity {
+func getTestActivityConfig(allow bool) config.Activity {
 	return config.Activity{
 		Default: ptrutil.ToPtr(true),
 		Rules: []config.ActivityRule{
 			{
-				Allow: true,
+				Allow: allow,
 				Condition: config.ActivityCondition{
 					ComponentName: []string{"bidderA"},
 					ComponentType: []string{"bidder"},
@@ -241,32 +150,15 @@ func getDefaultActivityConfig() config.Activity {
 	}
 }
 
-func getDefaultActivityPlan() ActivityPlan {
+func getTestActivityPlan(result ActivityResult) ActivityPlan {
 	return ActivityPlan{
 		defaultResult: true,
 		rules: []Rule{
-			ComponentEnforcementRule{
-				result: ActivityAllow,
-				componentName: []Component{
-					{Type: "bidder", Name: "bidderA"},
-				},
+			ConditionRule{
+				result:        result,
+				componentName: []string{"bidderA"},
 				componentType: []string{"bidder"},
 			},
 		},
 	}
 }
-
-func getIncorrectActivityConfig() config.Activity {
-	return config.Activity{
-		Default: ptrutil.ToPtr(true),
-		Rules: []config.ActivityRule{
-			{
-				Allow: true,
-				Condition: config.ActivityCondition{
-					ComponentName: []string{"bidder.bidderA.bidderB"},
-					ComponentType: []string{"bidder"},
-				},
-			},
-		},
-	}
-}
diff --git a/privacy/component_test.go b/privacy/component_test.go
index 38aa105d..9d9d1644 100644
--- a/privacy/component_test.go
+++ b/privacy/component_test.go
@@ -1,124 +1,87 @@
 package privacy
 
 import (
-	"errors"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
 )
 
-func TestComponentMatches(t *testing.T) {
+func TestComponentMatchesName(t *testing.T) {
 	testCases := []struct {
 		name   string
 		given  Component
-		target Component
+		target string
 		result bool
 	}{
 		{
-			name:   "full",
+			name:   "match",
 			given:  Component{Type: "a", Name: "b"},
-			target: Component{Type: "a", Name: "b"},
+			target: "b",
 			result: true,
 		},
 		{
-			name:   "name-wildcard",
-			given:  Component{Type: "a", Name: "*"},
-			target: Component{Type: "a", Name: "b"},
-			result: true,
-		},
-		{
-			name:   "different",
+			name:   "wrong-field",
 			given:  Component{Type: "a", Name: "b"},
-			target: Component{Type: "foo", Name: "bar"},
+			target: "a",
 			result: false,
 		},
 		{
-			name:   "different-type",
+			name:   "different-value",
 			given:  Component{Type: "a", Name: "b"},
-			target: Component{Type: "foo", Name: "b"},
+			target: "foo",
 			result: false,
 		},
 		{
-			name:   "different-name",
+			name:   "different-case",
 			given:  Component{Type: "a", Name: "b"},
-			target: Component{Type: "a", Name: "foo"},
-			result: false,
+			target: "B",
+			result: true,
 		},
 	}
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			assert.Equal(t, test.result, test.given.Matches(test.target))
+			assert.Equal(t, test.result, test.given.MatchesName(test.target))
 		})
 	}
 }
 
-func TestParseComponent(t *testing.T) {
+func TestComponentMatchesType(t *testing.T) {
 	testCases := []struct {
-		name          string
-		component     string
-		expected      Component
-		expectedError error
+		name   string
+		given  Component
+		target string
+		result bool
 	}{
 		{
-			name:          "empty",
-			component:     "",
-			expected:      Component{},
-			expectedError: errors.New("unable to parse empty component"),
-		},
-		{
-			name:          "too-many-parts",
-			component:     "bidder.bidderA.bidderB",
-			expected:      Component{},
-			expectedError: errors.New("unable to parse component: bidder.bidderA.bidderB"),
-		},
-		{
-			name:          "type-bidder",
-			component:     "bidder.bidderA",
-			expected:      Component{Type: "bidder", Name: "bidderA"},
-			expectedError: nil,
-		},
-		{
-			name:          "type-analytics",
-			component:     "analytics.bidderA",
-			expected:      Component{Type: "analytics", Name: "bidderA"},
-			expectedError: nil,
-		},
-		{
-			name:          "type-default",
-			component:     "bidderA",
-			expected:      Component{Type: "bidder", Name: "bidderA"},
-			expectedError: nil,
+			name:   "match",
+			given:  Component{Type: "a", Name: "b"},
+			target: "a",
+			result: true,
 		},
 		{
-			name:          "type-rtd",
-			component:     "rtd.test",
-			expected:      Component{Type: "rtd", Name: "test"},
-			expectedError: nil,
+			name:   "wrong-field",
+			given:  Component{Type: "a", Name: "b"},
+			target: "b",
+			result: false,
 		},
 		{
-			name:          "type-general",
-			component:     "general.test",
-			expected:      Component{Type: "general", Name: "test"},
-			expectedError: nil,
+			name:   "different-value",
+			given:  Component{Type: "a", Name: "b"},
+			target: "foo",
+			result: false,
 		},
 		{
-			name:          "type-invalid",
-			component:     "invalid.test",
-			expected:      Component{},
-			expectedError: errors.New("unable to parse component (invalid type): invalid.test"),
+			name:   "different-case",
+			given:  Component{Type: "a", Name: "b"},
+			target: "A",
+			result: true,
 		},
 	}
 
 	for _, test := range testCases {
 		t.Run(test.name, func(t *testing.T) {
-			actualSN, actualErr := ParseComponent(test.component)
-			if test.expectedError == nil {
-				assert.Equal(t, test.expected, actualSN)
-				assert.NoError(t, actualErr)
-			} else {
-				assert.EqualError(t, actualErr, test.expectedError.Error())
-			}
+			assert.Equal(t, test.result, test.given.MatchesType(test.target))
 		})
 	}
 }
diff --git a/privacy/rules_test.go b/privacy/rule_condition_test.go
similarity index 69%
rename from privacy/rules_test.go
rename to privacy/rule_condition_test.go
index 7e80c9ce..3a09b365 100644
--- a/privacy/rules_test.go
+++ b/privacy/rule_condition_test.go
@@ -9,17 +9,15 @@ import (
 func TestComponentEnforcementRuleEvaluate(t *testing.T) {
 	testCases := []struct {
 		name           string
-		componentRule  ComponentEnforcementRule
+		componentRule  ConditionRule
 		target         Component
 		activityResult ActivityResult
 	}{
 		{
 			name: "activity_is_allowed",
-			componentRule: ComponentEnforcementRule{
-				result: ActivityAllow,
-				componentName: []Component{
-					{Type: "bidder", Name: "bidderA"},
-				},
+			componentRule: ConditionRule{
+				result:        ActivityAllow,
+				componentName: []string{"bidderA"},
 				componentType: []string{"bidder"},
 			},
 			target:         Component{Type: "bidder", Name: "bidderA"},
@@ -27,11 +25,9 @@ func TestComponentEnforcementRuleEvaluate(t *testing.T) {
 		},
 		{
 			name: "activity_is_not_allowed",
-			componentRule: ComponentEnforcementRule{
-				result: ActivityDeny,
-				componentName: []Component{
-					{Type: "bidder", Name: "bidderA"},
-				},
+			componentRule: ConditionRule{
+				result:        ActivityDeny,
+				componentName: []string{"bidderA"},
 				componentType: []string{"bidder"},
 			},
 			target:         Component{Type: "bidder", Name: "bidderA"},
@@ -39,11 +35,9 @@ func TestComponentEnforcementRuleEvaluate(t *testing.T) {
 		},
 		{
 			name: "abstain_both_clauses_do_not_match",
-			componentRule: ComponentEnforcementRule{
-				result: ActivityAllow,
-				componentName: []Component{
-					{Type: "bidder", Name: "bidderA"},
-				},
+			componentRule: ConditionRule{
+				result:        ActivityAllow,
+				componentName: []string{"bidderA"},
 				componentType: []string{"bidder"},
 			},
 			target:         Component{Type: "bidder", Name: "bidderB"},
@@ -51,18 +45,16 @@ func TestComponentEnforcementRuleEvaluate(t *testing.T) {
 		},
 		{
 			name: "activity_is_not_allowed_componentName_only",
-			componentRule: ComponentEnforcementRule{
-				result: ActivityAllow,
-				componentName: []Component{
-					{Type: "bidder", Name: "bidderA"},
-				},
+			componentRule: ConditionRule{
+				result:        ActivityAllow,
+				componentName: []string{"bidderA"},
 			},
 			target:         Component{Type: "bidder", Name: "bidderA"},
 			activityResult: ActivityAllow,
 		},
 		{
 			name: "activity_is_allowed_componentType_only",
-			componentRule: ComponentEnforcementRule{
+			componentRule: ConditionRule{
 				result:        ActivityAllow,
 				componentType: []string{"bidder"},
 			},
@@ -71,7 +63,7 @@ func TestComponentEnforcementRuleEvaluate(t *testing.T) {
 		},
 		{
 			name: "no-conditions-allow",
-			componentRule: ComponentEnforcementRule{
+			componentRule: ConditionRule{
 				result: ActivityAllow,
 			},
 			target:         Component{Type: "bidder", Name: "bidderA"},
@@ -79,7 +71,7 @@ func TestComponentEnforcementRuleEvaluate(t *testing.T) {
 		},
 		{
 			name: "no-conditions-deny",
-			componentRule: ComponentEnforcementRule{
+			componentRule: ConditionRule{
 				result: ActivityDeny,
 			},
 			target:         Component{Type: "bidder", Name: "bidderA"},