Repository: prebid/prebid-server
Pull request #2213 (2022-04-04 12:11:50)

Yieldlab: Fix GDPR handling for AMP
We do want to send the consent string even though we are not getting a gdpr flag (We will default to 1 internally and respect the gdpr consent). This problems currently exists when requested via AMP.


Patch:
diff --git a/account/account.go b/account/account.go
index fc38b943..45506e93 100644
--- a/account/account.go
+++ b/account/account.go
@@ -5,12 +5,13 @@ import (
 	"encoding/json"
 	"fmt"
 
-	jsonpatch "gopkg.in/evanphx/json-patch.v4"
-
+	"github.com/prebid/go-gdpr/consentconstants"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/metrics"
+	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/stored_requests"
+	jsonpatch "gopkg.in/evanphx/json-patch.v4"
 )
 
 // GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied
@@ -59,6 +60,9 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r
 		if len(account.ID) == 0 {
 			account.ID = accountID
 		}
+
+		// Set derived fields
+		setDerivedConfig(account)
 	}
 	if account.Disabled {
 		errs = append(errs, &errortypes.BlacklistedAcct{
@@ -68,3 +72,51 @@ func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_r
 	}
 	return account, nil
 }
+
+// setDerivedConfig modifies an account object by setting fields derived from other fields set in the account configuration
+func setDerivedConfig(account *config.Account) {
+	account.GDPR.PurposeConfigs = map[consentconstants.Purpose]*config.AccountGDPRPurpose{
+		1:  &account.GDPR.Purpose1,
+		2:  &account.GDPR.Purpose2,
+		3:  &account.GDPR.Purpose3,
+		4:  &account.GDPR.Purpose4,
+		5:  &account.GDPR.Purpose5,
+		6:  &account.GDPR.Purpose6,
+		7:  &account.GDPR.Purpose7,
+		8:  &account.GDPR.Purpose8,
+		9:  &account.GDPR.Purpose9,
+		10: &account.GDPR.Purpose10,
+	}
+
+	// To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders
+	// located in the VendorExceptions field of the GDPR.PurposeX struct
+	for _, pc := range account.GDPR.PurposeConfigs {
+		if pc.VendorExceptions == nil {
+			continue
+		}
+		pc.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
+		for _, v := range pc.VendorExceptions {
+			pc.VendorExceptionMap[v] = struct{}{}
+		}
+	}
+
+	// To look for special feature 1's vendor exceptions in O(1) time, we fill this hash table with bidders
+	// located in the VendorExceptions field
+	if account.GDPR.SpecialFeature1.VendorExceptions != nil {
+		account.GDPR.SpecialFeature1.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
+
+		for _, v := range account.GDPR.SpecialFeature1.VendorExceptions {
+			account.GDPR.SpecialFeature1.VendorExceptionMap[v] = struct{}{}
+		}
+	}
+
+	// To look for basic enforcement vendors in O(1) time, we fill this hash table with bidders
+	// located in the BasicEnforcementVendors field
+	if account.GDPR.BasicEnforcementVendors != nil {
+		account.GDPR.BasicEnforcementVendorsMap = make(map[string]struct{})
+
+		for _, v := range account.GDPR.BasicEnforcementVendors {
+			account.GDPR.BasicEnforcementVendorsMap[v] = struct{}{}
+		}
+	}
+}
diff --git a/adapters/pubmatic/pubmatic.go b/adapters/pubmatic/pubmatic.go
index 79f07200..00c9c906 100644
--- a/adapters/pubmatic/pubmatic.go
+++ b/adapters/pubmatic/pubmatic.go
@@ -66,7 +66,7 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad
 	extractWrapperExtFromImp := true
 	extractPubIDFromImp := true
 
-	wrapperExt, err := extractPubmaticWrapperExtFromRequest(request)
+	wrapperExt, acat, err := extractPubmaticExtFromRequest(request)
 	if err != nil {
 		return nil, []error{err}
 	}
@@ -114,9 +114,14 @@ func (a *PubmaticAdapter) MakeRequests(request *openrtb2.BidRequest, reqInfo *ad
 		return nil, errs
 	}
 
+	reqExt := make(map[string]interface{})
+	if len(acat) > 0 {
+		reqExt["acat"] = acat
+	}
 	if wrapperExt != nil {
-		reqExt := make(map[string]interface{})
 		reqExt["wrapper"] = wrapperExt
+	}
+	if len(reqExt) > 0 {
 		rawExt, err := json.Marshal(reqExt)
 		if err != nil {
 			return nil, []error{err}
@@ -310,23 +315,32 @@ func parseImpressionObject(imp *openrtb2.Imp, extractWrapperExtFromImp, extractP
 	return wrapExt, pubID, nil
 }
 
-// extractPubmaticWrapperExtFromRequest parse the imp to get it ready to send to pubmatic
-func extractPubmaticWrapperExtFromRequest(request *openrtb2.BidRequest) (*pubmaticWrapperExt, error) {
-	var wrpExt pubmaticWrapperExt
+// extractPubmaticExtFromRequest parse the req.ext to fetch wrapper and acat params
+func extractPubmaticExtFromRequest(request *openrtb2.BidRequest) (*pubmaticWrapperExt, []string, error) {
+	var acat []string
+	var wrpExt *pubmaticWrapperExt
 	reqExtBidderParams, err := adapters.ExtractReqExtBidderParamsMap(request)
 	if err != nil {
-		return nil, err
+		return nil, acat, err
 	}
 
 	//get request ext bidder params
 	if wrapperObj, present := reqExtBidderParams["wrapper"]; present && len(wrapperObj) != 0 {
-		err = json.Unmarshal(wrapperObj, &wrpExt)
+		wrpExt = &pubmaticWrapperExt{}
+		err = json.Unmarshal(wrapperObj, wrpExt)
 		if err != nil {
-			return nil, err
+			return nil, acat, err
+		}
+	}
+
+	if acatBytes, ok := reqExtBidderParams["acat"]; ok {
+		err = json.Unmarshal(acatBytes, &acat)
+		for i := 0; i < len(acat); i++ {
+			acat[i] = strings.TrimSpace(acat[i])
 		}
-		return &wrpExt, nil
 	}
-	return nil, nil
+
+	return wrpExt, acat, err
 }
 
 func addKeywordsToExt(keywords []*openrtb_ext.ExtImpPubmaticKeyVal, extMap map[string]interface{}) {
diff --git a/adapters/yieldlab/yieldlab.go b/adapters/yieldlab/yieldlab.go
index 2c9892aa..1447aeaf 100644
--- a/adapters/yieldlab/yieldlab.go
+++ b/adapters/yieldlab/yieldlab.go
@@ -76,8 +76,10 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *open
 	if err != nil {
 		return "", err
 	}
-	if gdpr != "" && consent != "" {
+	if gdpr != "" {
 		q.Set("gdpr", gdpr)
+	}
+	if consent != "" {
 		q.Set("consent", consent)
 	}
 
@@ -87,17 +89,6 @@ func (a *YieldlabAdapter) makeEndpointURL(req *openrtb2.BidRequest, params *open
 }
 
 func (a *YieldlabAdapter) getGDPR(request *openrtb2.BidRequest) (string, string, error) {
-	gdpr := ""
-	var extRegs openrtb_ext.ExtRegs
-	if request.Regs != nil {
-		if err := json.Unmarshal(request.Regs.Ext, &extRegs); err != nil {
-			return "", "", fmt.Errorf("failed to parse ExtRegs in Yieldlab GDPR check: %v", err)
-		}
-		if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) {
-			gdpr = strconv.Itoa(int(*extRegs.GDPR))
-		}
-	}
-
 	consent := ""
 	if request.User != nil && request.User.Ext != nil {
 		var extUser openrtb_ext.ExtUser
@@ -107,6 +98,16 @@ func (a *YieldlabAdapter) getGDPR(request *openrtb2.BidRequest) (string, string,
 		consent = extUser.Consent
 	}
 
+	gdpr := ""
+	var extRegs openrtb_ext.ExtRegs
+	if request.Regs != nil {
+		if err := json.Unmarshal(request.Regs.Ext, &extRegs); err == nil {
+			if extRegs.GDPR != nil && (*extRegs.GDPR == 0 || *extRegs.GDPR == 1) {
+				gdpr = strconv.Itoa(int(*extRegs.GDPR))
+			}
+		}
+	}
+
 	return gdpr, consent, nil
 }
 
diff --git a/config/accounts.go b/config/accounts.go
index ae9d656e..e475da8a 100644
--- a/config/accounts.go
+++ b/config/accounts.go
@@ -1,5 +1,10 @@
 package config
 
+import (
+	"github.com/prebid/go-gdpr/consentconstants"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
 // IntegrationType enumerates the values of integrations Prebid Server can configure for an account
 type IntegrationType string
 
@@ -50,21 +55,148 @@ func (a *AccountCCPA) EnabledForIntegrationType(integrationType IntegrationType)
 
 // AccountGDPR represents account-specific GDPR configuration
 type AccountGDPR struct {
-	Enabled                 *bool              `mapstructure:"enabled" json:"enabled,omitempty"`
-	IntegrationEnabled      AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"`
-	BasicEnforcementVendors []string           `mapstructure:"basic_enforcement_vendors" json:"basic_enforcement_vendors"`
+	Enabled            *bool              `mapstructure:"enabled" json:"enabled,omitempty"`
+	IntegrationEnabled AccountIntegration `mapstructure:"integration_enabled" json:"integration_enabled"`
+	// Array of basic enforcement vendors that is used to create the hash table so vendor names can be instantly accessed
+	BasicEnforcementVendors    []string `mapstructure:"basic_enforcement_vendors" json:"basic_enforcement_vendors"`
+	BasicEnforcementVendorsMap map[string]struct{}
+	Purpose1                   AccountGDPRPurpose `mapstructure:"purpose1" json:"purpose1"`
+	Purpose2                   AccountGDPRPurpose `mapstructure:"purpose2" json:"purpose2"`
+	Purpose3                   AccountGDPRPurpose `mapstructure:"purpose3" json:"purpose3"`
+	Purpose4                   AccountGDPRPurpose `mapstructure:"purpose4" json:"purpose4"`
+	Purpose5                   AccountGDPRPurpose `mapstructure:"purpose5" json:"purpose5"`
+	Purpose6                   AccountGDPRPurpose `mapstructure:"purpose6" json:"purpose6"`
+	Purpose7                   AccountGDPRPurpose `mapstructure:"purpose7" json:"purpose7"`
+	Purpose8                   AccountGDPRPurpose `mapstructure:"purpose8" json:"purpose8"`
+	Purpose9                   AccountGDPRPurpose `mapstructure:"purpose9" json:"purpose9"`
+	Purpose10                  AccountGDPRPurpose `mapstructure:"purpose10" json:"purpose10"`
+	// Hash table of purpose configs for convenient purpose config lookup
+	PurposeConfigs      map[consentconstants.Purpose]*AccountGDPRPurpose
+	PurposeOneTreatment AccountGDPRPurposeOneTreatment `mapstructure:"purpose_one_treatment" json:"purpose_one_treatment"`
+	SpecialFeature1     AccountGDPRSpecialFeature      `mapstructure:"special_feature1" json:"special_feature1"`
+}
+
+// BasicEnforcementVendor checks if the given bidder is considered a basic enforcement vendor which indicates whether
+// weak vendor enforcement applies to that bidder.
+func (a *AccountGDPR) BasicEnforcementVendor(bidder openrtb_ext.BidderName) (value, exists bool) {
+	if a.BasicEnforcementVendorsMap == nil {
+		return false, false
+	}
+	_, found := a.BasicEnforcementVendorsMap[string(bidder)]
+
+	return found, true
 }
 
 // EnabledForIntegrationType indicates whether GDPR is turned on at the account level for the specified integration type
-// by using the integration type setting if defined or the general GDPR setting if defined; otherwise it returns nil
+// by using the integration type setting if defined or the general GDPR setting if defined; otherwise it returns nil.
 func (a *AccountGDPR) EnabledForIntegrationType(integrationType IntegrationType) *bool {
-
 	if integrationEnabled := a.IntegrationEnabled.GetByIntegrationType(integrationType); integrationEnabled != nil {
 		return integrationEnabled
 	}
 	return a.Enabled
 }
 
+// FeatureOneEnforced gets the account level feature one enforced setting returning the value and whether or not it
+// was set. If not set, a default value of true is returned matching host default behavior.
+func (a *AccountGDPR) FeatureOneEnforced() (value, exists bool) {
+	if a.SpecialFeature1.Enforce == nil {
+		return true, false
+	}
+	return *a.SpecialFeature1.Enforce, true
+}
+
+// FeatureOneVendorException checks if the given bidder is a vendor exception.
+func (a *AccountGDPR) FeatureOneVendorException(bidder openrtb_ext.BidderName) (value, exists bool) {
+	if a.SpecialFeature1.VendorExceptionMap == nil {
+		return false, false
+	}
+	_, found := a.SpecialFeature1.VendorExceptionMap[bidder]
+
+	return found, true
+}
+
+// PurposeEnforced checks if full enforcement is turned on for a given purpose at the account level. It returns the
+// enforcement strategy type and whether or not it is set on the account. If not set, a default value of true is
+// returned matching host default behavior.
+func (a *AccountGDPR) PurposeEnforced(purpose consentconstants.Purpose) (value, exists bool) {
+	if a.PurposeConfigs[purpose] == nil {
+		return true, false
+	}
+	if a.PurposeConfigs[purpose].EnforcePurpose == TCF2FullEnforcement {
+		return true, true
+	}
+	if a.PurposeConfigs[purpose].EnforcePurpose == TCF2NoEnforcement {
+		return false, true
+	}
+	return true, false
+}
+
+// PurposeEnforcingVendors gets the account level enforce vendors setting for a given purpose returning the value and
+// whether or not it is set. If not set, a default value of true is returned matching host default behavior.
+func (a *AccountGDPR) PurposeEnforcingVendors(purpose consentconstants.Purpose) (value, exists bool) {
+	if a.PurposeConfigs[purpose] == nil {
+		return true, false
+	}
+	if a.PurposeConfigs[purpose].EnforceVendors == nil {
+		return true, false
+	}
+	return *a.PurposeConfigs[purpose].EnforceVendors, true
+}
+
+// PurposeVendorException checks if the given bidder is a vendor exception for a given purpose.
+func (a *AccountGDPR) PurposeVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (value, exists bool) {
+	if a.PurposeConfigs[purpose] == nil {
+		return false, false
+	}
+	if a.PurposeConfigs[purpose].VendorExceptionMap == nil {
+		return false, false
+	}
+	_, found := a.PurposeConfigs[purpose].VendorExceptionMap[bidder]
+
+	return found, true
+}
+
+// PurposeOneTreatmentEnabled gets the account level purpose one treatment enabled setting returning the value and
+// whether or not it is set. If not set, a default value of true is returned matching host default behavior.
+func (a *AccountGDPR) PurposeOneTreatmentEnabled() (value, exists bool) {
+	if a.PurposeOneTreatment.Enabled == nil {
+		return true, false
+	}
+	return *a.PurposeOneTreatment.Enabled, true
+}
+
+// PurposeOneTreatmentAccessAllowed gets the account level purpose one treatment access allowed setting returning the
+// value and whether or not it is set. If not set, a default value of true is returned matching host default behavior.
+func (a *AccountGDPR) PurposeOneTreatmentAccessAllowed() (value, exists bool) {
+	if a.PurposeOneTreatment.AccessAllowed == nil {
+		return true, false
+	}
+	return *a.PurposeOneTreatment.AccessAllowed, true
+}
+
+// AccountGDPRPurpose represents account-specific GDPR purpose configuration
+type AccountGDPRPurpose struct {
+	EnforcePurpose string `mapstructure:"enforce_purpose" json:"enforce_purpose,omitempty"`
+	EnforceVendors *bool  `mapstructure:"enforce_vendors" json:"enforce_vendors,omitempty"`
+	// Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed
+	VendorExceptions   []openrtb_ext.BidderName `mapstructure:"vendor_exceptions" json:"vendor_exceptions"`
+	VendorExceptionMap map[openrtb_ext.BidderName]struct{}
+}
+
+// AccountGDPRSpecialFeature represents account-specific GDPR special feature configuration
+type AccountGDPRSpecialFeature struct {
+	Enforce *bool `mapstructure:"enforce" json:"enforce"`
+	// Array of vendor exceptions that is used to create the hash table VendorExceptionMap so vendor names can be instantly accessed
+	VendorExceptions   []openrtb_ext.BidderName `mapstructure:"vendor_exceptions" json:"vendor_exceptions"`
+	VendorExceptionMap map[openrtb_ext.BidderName]struct{}
+}
+
+// AccountGDPRPurposeOneTreatment represents account-specific GDPR purpose one treatment configuration
+type AccountGDPRPurposeOneTreatment struct {
+	Enabled       *bool `mapstructure:"enabled"`
+	AccessAllowed *bool `mapstructure:"access_allowed"`
+}
+
 // AccountIntegration indicates whether a particular privacy policy (GDPR, CCPA) is enabled for each integration type
 type AccountIntegration struct {
 	AMP   *bool `mapstructure:"amp" json:"amp,omitempty"`
diff --git a/config/config.go b/config/config.go
index 0b3bed04..ef200630 100644
--- a/config/config.go
+++ b/config/config.go
@@ -10,6 +10,7 @@ import (
 	"time"
 
 	"github.com/golang/glog"
+	"github.com/prebid/go-gdpr/consentconstants"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/spf13/viper"
@@ -280,21 +281,102 @@ const (
 
 // TCF2 defines the TCF2 specific configurations for GDPR
 type TCF2 struct {
-	Enabled             bool                    `mapstructure:"enabled"`
-	Purpose1            TCF2Purpose             `mapstructure:"purpose1"`
-	Purpose2            TCF2Purpose             `mapstructure:"purpose2"`
-	Purpose3            TCF2Purpose             `mapstructure:"purpose3"`
-	Purpose4            TCF2Purpose             `mapstructure:"purpose4"`
-	Purpose5            TCF2Purpose             `mapstructure:"purpose5"`
-	Purpose6            TCF2Purpose             `mapstructure:"purpose6"`
-	Purpose7            TCF2Purpose             `mapstructure:"purpose7"`
-	Purpose8            TCF2Purpose             `mapstructure:"purpose8"`
-	Purpose9            TCF2Purpose             `mapstructure:"purpose9"`
-	Purpose10           TCF2Purpose             `mapstructure:"purpose10"`
+	Enabled   bool        `mapstructure:"enabled"`
+	Purpose1  TCF2Purpose `mapstructure:"purpose1"`
+	Purpose2  TCF2Purpose `mapstructure:"purpose2"`
+	Purpose3  TCF2Purpose `mapstructure:"purpose3"`
+	Purpose4  TCF2Purpose `mapstructure:"purpose4"`
+	Purpose5  TCF2Purpose `mapstructure:"purpose5"`
+	Purpose6  TCF2Purpose `mapstructure:"purpose6"`
+	Purpose7  TCF2Purpose `mapstructure:"purpose7"`
+	Purpose8  TCF2Purpose `mapstructure:"purpose8"`
+	Purpose9  TCF2Purpose `mapstructure:"purpose9"`
+	Purpose10 TCF2Purpose `mapstructure:"purpose10"`
+	// Map of purpose configs for easy purpose lookup
+	PurposeConfigs      map[consentconstants.Purpose]*TCF2Purpose
 	SpecialFeature1     TCF2SpecialFeature      `mapstructure:"special_feature1"`
 	PurposeOneTreatment TCF2PurposeOneTreatment `mapstructure:"purpose_one_treatment"`
 }
 
+// BasicEnforcementVendor checks if the given bidder is considered a basic enforcement vendor which indicates whether
+// weak vendor enforcement applies to that bidder. If set, the legal basis calculation for the bidder only considers
+// consent to the purpose, not the vendor. The idea is that the publisher trusts this vendor to enforce the
+// appropriate rules on their own. This only comes into play when enforceVendors is true as it lists those vendors that
+// are exempt for vendor enforcement.
+func (t *TCF2) BasicEnforcementVendor(openrtb_ext.BidderName) bool {
+	return false
+}
+
+// IntegrationEnabled checks if a given integration type is enabled. All integration types are considered either
+// enabled or disabled based on the Enabled flag.
+func (t *TCF2) IntegrationEnabled(integrationType IntegrationType) bool {
+	return t.Enabled
+}
+
+// IsEnabled indicates if TCF2 is enabled
+func (t *TCF2) IsEnabled() bool {
+	return t.Enabled
+}
+
+// PurposeEnforced checks if full enforcement is turned on for a given purpose. With full enforcement enabled, the
+// GDPR full enforcement algorithm will execute for that purpose determining legal basis; otherwise it's skipped.
+func (t *TCF2) PurposeEnforced(purpose consentconstants.Purpose) (value bool) {
+	if t.PurposeConfigs[purpose] == nil {
+		return false
+	}
+	if t.PurposeConfigs[purpose].EnforcePurpose == TCF2FullEnforcement {
+		return true
+	}
+	return false
+}
+
+// PurposeEnforcingVendors checks if enforcing vendors is turned on for a given purpose. With enforcing vendors
+// enabled, the GDPR full enforcement algorithm considers the GVL when determining legal basis; otherwise it's skipped.
+func (t *TCF2) PurposeEnforcingVendors(purpose consentconstants.Purpose) (value bool) {
+	if t.PurposeConfigs[purpose] == nil {
+		return false
+	}
+	return t.PurposeConfigs[purpose].EnforceVendors
+}
+
+// PurposeVendorException checks if the specified bidder is considered a vendor exception for a given purpose. If a
+// bidder is a vendor exception, the GDPR full enforcement algorithm will bypass the legal basis calculation assuming
+// the request is valid and there isn't a "deny all" publisher restriction
+func (t *TCF2) PurposeVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (value bool) {
+	if t.PurposeConfigs[purpose] == nil {
+		return false
+	}
+	if _, ok := t.PurposeConfigs[purpose].VendorExceptionMap[bidder]; ok {
+		return true
+	}
+	return false
+}
+
+// FeatureOneEnforced checks if special feature one is enforced. If it is enforced, PBS will determine whether geo
+// information may be passed through in the bid request.
+func (t *TCF2) FeatureOneEnforced() (value bool) {
+	return t.SpecialFeature1.Enforce
+}
+
+// FeatureOneVendorException checks if the specified bidder is considered a vendor exception for special feature one.
+// If a bidder is a vendor exception, PBS will bypass the pass geo calculation passing the geo information in the bid request.
+func (t *TCF2) FeatureOneVendorException(bidder openrtb_ext.BidderName) (value bool) {
+	if _, ok := t.SpecialFeature1.VendorExceptionMap[bidder]; ok {
+		return true
+	}
+	return false
+}
+
+// PurposeOneTreatmentEnabled checks if purpose one treatment is enabled.
+func (t *TCF2) PurposeOneTreatmentEnabled() (value bool) {
+	return t.PurposeOneTreatment.Enabled
+}
+
+// PurposeOneTreatmentAccessAllowed checks if purpose one treatment access is allowed.
+func (t *TCF2) PurposeOneTreatmentAccessAllowed() (value bool) {
+	return t.PurposeOneTreatment.AccessAllowed
+}
+
 // Making a purpose struct so purpose specific details can be added later.
 type TCF2Purpose struct {
 	Enabled        bool   `mapstructure:"enabled"` // Deprecated: Use enforce_purpose instead
@@ -556,26 +638,27 @@ func New(v *viper.Viper) (*Configuration, error) {
 		c.GDPR.EEACountriesMap[v] = s
 	}
 
-	// To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table located in the
-	// VendorExceptions field of the GDPR.TCF2.PurposeX struct defined in this file
-	purposeConfigs := []*TCF2Purpose{
-		&c.GDPR.TCF2.Purpose1,
-		&c.GDPR.TCF2.Purpose2,
-		&c.GDPR.TCF2.Purpose3,
-		&c.GDPR.TCF2.Purpose4,
-		&c.GDPR.TCF2.Purpose5,
-		&c.GDPR.TCF2.Purpose6,
-		&c.GDPR.TCF2.Purpose7,
-		&c.GDPR.TCF2.Purpose8,
-		&c.GDPR.TCF2.Purpose9,
-		&c.GDPR.TCF2.Purpose10,
+	// for each purpose we capture a reference to the purpose config in a map for easy purpose config lookup
+	c.GDPR.TCF2.PurposeConfigs = map[consentconstants.Purpose]*TCF2Purpose{
+		1:  &c.GDPR.TCF2.Purpose1,
+		2:  &c.GDPR.TCF2.Purpose2,
+		3:  &c.GDPR.TCF2.Purpose3,
+		4:  &c.GDPR.TCF2.Purpose4,
+		5:  &c.GDPR.TCF2.Purpose5,
+		6:  &c.GDPR.TCF2.Purpose6,
+		7:  &c.GDPR.TCF2.Purpose7,
+		8:  &c.GDPR.TCF2.Purpose8,
+		9:  &c.GDPR.TCF2.Purpose9,
+		10: &c.GDPR.TCF2.Purpose10,
 	}
-	for c := 0; c < len(purposeConfigs); c++ {
-		purposeConfigs[c].VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
 
-		for v := 0; v < len(purposeConfigs[c].VendorExceptions); v++ {
-			bidderName := purposeConfigs[c].VendorExceptions[v]
-			purposeConfigs[c].VendorExceptionMap[bidderName] = struct{}{}
+	// To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders
+	// located in the VendorExceptions field of the GDPR.TCF2.PurposeX struct defined in this file
+	for _, pc := range c.GDPR.TCF2.PurposeConfigs {
+		pc.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
+		for v := 0; v < len(pc.VendorExceptions); v++ {
+			bidderName := pc.VendorExceptions[v]
+			pc.VendorExceptionMap[bidderName] = struct{}{}
 		}
 	}
 
diff --git a/endpoints/cookie_sync.go b/endpoints/cookie_sync.go
index 12adc8ad..7cb1d2d7 100644
--- a/endpoints/cookie_sync.go
+++ b/endpoints/cookie_sync.go
@@ -127,7 +127,7 @@ func (c *cookieSyncEndpoint) parseRequest(r *http.Request) (usersync.Request, pr
 			return usersync.Request{}, privacy.Policies{}, errCookieSyncGDPRConsentMissing
 		}
 
-		if gdprSignal == gdpr.SignalAmbiguous && gdpr.SignalNormalize(gdprSignal, c.privacyConfig.gdprConfig) == gdpr.SignalYes {
+		if gdprSignal == gdpr.SignalAmbiguous && gdpr.SignalNormalize(gdprSignal, c.privacyConfig.gdprConfig.DefaultValue) == gdpr.SignalYes {
 			return usersync.Request{}, privacy.Policies{}, errCookieSyncGDPRConsentMissingSignalAmbiguous
 		}
 	}
diff --git a/endpoints/openrtb2/auction.go b/endpoints/openrtb2/auction.go
index 42caa3a7..3b6a677b 100644
--- a/endpoints/openrtb2/auction.go
+++ b/endpoints/openrtb2/auction.go
@@ -31,6 +31,7 @@ import (
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/exchange"
 	"github.com/prebid/prebid-server/firstpartydata"
+	"github.com/prebid/prebid-server/gdpr"
 	"github.com/prebid/prebid-server/metrics"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/prebid_cache_client"
@@ -226,6 +227,8 @@ func (deps *endpointDeps) Auction(w http.ResponseWriter, r *http.Request, _ http
 		ImpExtInfoMap:              impExtInfoMap,
 		FirstPartyData:             resolvedFPD,
 		StoredAuctionResponses:     storedAuctionResponses,
+		TCF2ConfigBuilder:          gdpr.NewTCF2Config,
+		GDPRPermissionsBuilder:     gdpr.NewPermissions,
 	}
 
 	response, err := deps.ex.HoldAuction(ctx, auctionRequest, nil)
@@ -1665,7 +1668,7 @@ func (deps *endpointDeps) processStoredRequests(ctx context.Context, requestJson
 		if err != nil {
 			return nil, nil, []error{err}
 		}
-		if isAppRequest && (deps.cfg.GenerateRequestID || bidRequestID == "{{UUID}}") {
+		if (deps.cfg.GenerateRequestID && isAppRequest) || bidRequestID == "{{UUID}}" {
 			uuidPatch, err := generateUuidForBidRequest(deps.uuidGenerator)
 			if err != nil {
 				return nil, nil, []error{err}
@@ -1923,20 +1926,6 @@ func generateUuidForBidRequest(uuidGenerator uuidutil.UUIDGenerator) ([]byte, er
 	return []byte(`{"id":"` + newBidRequestID + `"}`), nil
 }
 
-func checkIfAppRequest(request []byte) (bool, error) {
-	requestApp, dataType, _, err := jsonparser.Get(request, "app")
-	if dataType == jsonparser.NotExist {
-		return false, nil
-	}
-	if err != nil {
-		return false, err
-	}
-	if requestApp != nil {
-		return true, nil
-	}
-	return false, nil
-}
-
 func (deps *endpointDeps) setIntegrationType(req *openrtb_ext.RequestWrapper, account *config.Account) error {
 	reqExt, err := req.GetRequestExt()
 	if err != nil {
@@ -1956,3 +1945,17 @@ func (deps *endpointDeps) setIntegrationType(req *openrtb_ext.RequestWrapper, ac
 	}
 	return nil
 }
+
+func checkIfAppRequest(request []byte) (bool, error) {
+	requestApp, dataType, _, err := jsonparser.Get(request, "app")
+	if dataType == jsonparser.NotExist {
+		return false, nil
+	}
+	if err != nil {
+		return false, err
+	}
+	if requestApp != nil {
+		return true, nil
+	}
+	return false, nil
+}
diff --git a/exchange/exchange.go b/exchange/exchange.go
index 80820ac1..1d41f891 100644
--- a/exchange/exchange.go
+++ b/exchange/exchange.go
@@ -56,13 +56,14 @@ type exchange struct {
 	me                metrics.MetricsEngine
 	cache             prebid_cache_client.Client
 	cacheTime         time.Duration
-	gDPR              gdpr.Permissions
+	vendorListFetcher gdpr.VendorListFetcher
 	currencyConverter *currency.RateConverter
 	externalURL       string
 	gdprDefaultValue  gdpr.Signal
 	privacyConfig     config.Privacy
 	categoriesFetcher stored_requests.CategoryFetcher
 	bidIDGenerator    BidIDGenerator
+	gvlVendorIDs      map[openrtb_ext.BidderName]uint16
 }
 
 // Container to pass out response ext data from the GetAllBids goroutines back into the main thread
@@ -109,7 +110,7 @@ func (randomDeduplicateBidBooleanGenerator) Generate() bool {
 	return rand.Intn(100) < 50
 }
 
-func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, gDPR gdpr.Permissions, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange {
+func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid_cache_client.Client, cfg *config.Configuration, syncersByBidder map[string]usersync.Syncer, metricsEngine metrics.MetricsEngine, infos config.BidderInfos, vendorListFetcher gdpr.VendorListFetcher, currencyConverter *currency.RateConverter, categoriesFetcher stored_requests.CategoryFetcher) Exchange {
 	bidderToSyncerKey := map[string]string{}
 	for bidder, syncer := range syncersByBidder {
 		bidderToSyncerKey[bidder] = syncer.Key()
@@ -129,7 +130,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid
 		categoriesFetcher: categoriesFetcher,
 		currencyConverter: currencyConverter,
 		externalURL:       cfg.ExternalURL,
-		gDPR:              gDPR,
+		vendorListFetcher: vendorListFetcher,
 		me:                metricsEngine,
 		gdprDefaultValue:  gdprDefaultValue,
 		privacyConfig: config.Privacy{
@@ -138,6 +139,7 @@ func NewExchange(adapters map[openrtb_ext.BidderName]adaptedBidder, cache prebid
 			LMT:  cfg.LMT,
 		},
 		bidIDGenerator: &bidIDGenerator{cfg.GenerateBidID},
+		gvlVendorIDs:   infos.ToGVLVendorIDMap(),
 	}
 }
 
@@ -164,6 +166,8 @@ type AuctionRequest struct {
 	FirstPartyData map[openrtb_ext.BidderName]*firstpartydata.ResolvedFirstPartyData
 	// map of imp id to stored response
 	StoredAuctionResponses map[string]json.RawMessage
+	TCF2ConfigBuilder      gdpr.TCF2ConfigBuilder
+	GDPRPermissionsBuilder gdpr.PermissionsBuilder
 }
 
 // BidderRequest holds the bidder specific request and all other
@@ -198,7 +202,9 @@ func (e *exchange) HoldAuction(ctx context.Context, r AuctionRequest, debugLog *
 	gdprDefaultValue := e.parseGDPRDefaultValue(r.BidRequest)
 
 	// Slice of BidRequests, each a copy of the original cleaned to only contain bidder data for the named bidder
-	bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.bidderToSyncerKey, e.gDPR, e.me, gdprDefaultValue, e.privacyConfig, &r.Account)
+	tcf2Cfg := r.TCF2ConfigBuilder(e.privacyConfig.GDPR.TCF2, r.Account.GDPR)
+	gdprPerms := r.GDPRPermissionsBuilder(e.privacyConfig.GDPR, tcf2Cfg, e.gvlVendorIDs, e.vendorListFetcher)
+	bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(ctx, r, requestExt, e.bidderToSyncerKey, e.me, gdprDefaultValue, gdprPerms, e.privacyConfig, tcf2Cfg)
 
 	e.me.RecordRequestPrivacy(privacyLabels)
 
diff --git a/exchange/utils.go b/exchange/utils.go
index 3a4a1eaf..2204e6ac 100644
--- a/exchange/utils.go
+++ b/exchange/utils.go
@@ -40,11 +40,12 @@ func cleanOpenRTBRequests(ctx context.Context,
 	req AuctionRequest,
 	requestExt *openrtb_ext.ExtRequest,
 	bidderToSyncerKey map[string]string,
-	gDPR gdpr.Permissions,
 	metricsEngine metrics.MetricsEngine,
 	gdprDefaultValue gdpr.Signal,
+	gdprPerms gdpr.Permissions,
 	privacyConfig config.Privacy,
-	account *config.Account) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) {
+	tcf2Cfg gdpr.TCF2ConfigReader,
+) (allowedBidderRequests []BidderRequest, privacyLabels metrics.PrivacyLabels, errs []error) {
 
 	impsByBidder, err := splitImps(req.BidRequest.Imp)
 	if err != nil {
@@ -77,7 +78,7 @@ func cleanOpenRTBRequests(ctx context.Context,
 	if err != nil {
 		errs = append(errs, err)
 	}
-	gdprEnforced := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes)
+	gdprApplies := gdprSignal == gdpr.SignalYes || (gdprSignal == gdpr.SignalAmbiguous && gdprDefaultValue == gdpr.SignalYes)
 
 	ccpaEnforcer, err := extractCCPA(req.BidRequest, privacyConfig, &req.Account, aliases, integrationTypeMap[req.LegacyLabels.RType])
 	if err != nil {
@@ -97,7 +98,10 @@ func cleanOpenRTBRequests(ctx context.Context,
 	privacyLabels.COPPAEnforced = privacyEnforcement.COPPA
 	privacyLabels.LMTEnforced = lmtEnforcer.ShouldEnforce(unknownBidder)
 
-	gdprEnforced = gdprEnforced && gdprEnabled(&req.Account, privacyConfig, integrationTypeMap[req.LegacyLabels.RType])
+	var gdprEnforced bool
+	if gdprApplies {
+		gdprEnforced = tcf2Cfg.IntegrationEnabled(integrationTypeMap[req.LegacyLabels.RType])
+	}
 
 	if gdprEnforced {
 		privacyLabels.GDPREnforced = true
@@ -118,17 +122,8 @@ func cleanOpenRTBRequests(ctx context.Context,
 
 		// GDPR
 		if gdprEnforced {
-			weakVendorEnforcement := false
-			if account != nil {
-				for _, vendor := range account.GDPR.BasicEnforcementVendors {
-					if vendor == string(bidderRequest.BidderCoreName) {
-						weakVendorEnforcement = true
-						break
-					}
-				}
-			}
 			var publisherID = req.LegacyLabels.PubID
-			bidReq, geo, id, err := gDPR.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName, publisherID, gdprSignal, consent, weakVendorEnforcement, aliasesGVLIDs)
+			bidReq, geo, id, err := gdprPerms.AuctionActivitiesAllowed(ctx, bidderRequest.BidderCoreName, bidderRequest.BidderName, publisherID, gdprSignal, consent, aliasesGVLIDs)
 			bidRequestAllowed = bidReq
 
 			if err == nil {
@@ -157,13 +152,6 @@ func cleanOpenRTBRequests(ctx context.Context,
 	return
 }
 
-func gdprEnabled(account *config.Account, privacyConfig config.Privacy, integrationType config.IntegrationType) bool {
-	if accountEnabled := account.GDPR.EnabledForIntegrationType(integrationType); accountEnabled != nil {
-		return *accountEnabled
-	}
-	return privacyConfig.GDPR.Enabled
-}
-
 func ccpaEnabled(account *config.Account, privacyConfig config.Privacy, requestType config.IntegrationType) bool {
 	if accountEnabled := account.CCPA.EnabledForIntegrationType(requestType); accountEnabled != nil {
 		return *accountEnabled
diff --git a/gdpr/aggregated_config.go b/gdpr/aggregated_config.go
new file mode 100644
index 00000000..9f0007e0
--- /dev/null
+++ b/gdpr/aggregated_config.go
@@ -0,0 +1,141 @@
+package gdpr
+
+import (
+	"github.com/prebid/go-gdpr/consentconstants"
+
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+)
+
+// TCF2ConfigReader is an interface to access TCF2 configurations
+type TCF2ConfigReader interface {
+	BasicEnforcementVendor(openrtb_ext.BidderName) bool
+	FeatureOneEnforced() bool
+	FeatureOneVendorException(openrtb_ext.BidderName) bool
+	IntegrationEnabled(config.IntegrationType) bool
+	IsEnabled() bool
+	PurposeEnforced(consentconstants.Purpose) bool
+	PurposeEnforcingVendors(consentconstants.Purpose) bool
+	PurposeVendorException(consentconstants.Purpose, openrtb_ext.BidderName) bool
+	PurposeOneTreatmentEnabled() bool
+	PurposeOneTreatmentAccessAllowed() bool
+}
+
+type TCF2ConfigBuilder func(hostConfig config.TCF2, accountConfig config.AccountGDPR) TCF2ConfigReader
+
+type tcf2Config struct {
+	HostConfig    config.TCF2
+	AccountConfig config.AccountGDPR
+}
+
+// NewTCF2Config creates an instance of tcf2Config which implements the TCF2ConfigReader interface
+func NewTCF2Config(hostConfig config.TCF2, accountConfig config.AccountGDPR) TCF2ConfigReader {
+	return &tcf2Config{
+		HostConfig:    hostConfig,
+		AccountConfig: accountConfig,
+	}
+}
+
+// IsEnabled indicates if TCF2 is enabled
+func (tc *tcf2Config) IsEnabled() bool {
+	return tc.HostConfig.Enabled
+}
+
+// IntegrationEnabled checks if a given integration type is enabled at the account level. If it is not set at the
+// account level, the host TCF2 enabled flag is used to determine if the integration type is enabled.
+func (tc *tcf2Config) IntegrationEnabled(integrationType config.IntegrationType) bool {
+	if accountEnabled := tc.AccountConfig.EnabledForIntegrationType(integrationType); accountEnabled != nil {
+		return *accountEnabled
+	}
+	return tc.HostConfig.Enabled
+}
+
+// PurposeEnforced checks if full enforcement is turned on for a given purpose by first looking at the account
+// settings, and if not set there, defaulting to the host configuration. With full enforcement enabled, the
+// GDPR full enforcement algorithm will execute for that purpose determining legal basis; otherwise it's skipped.
+func (tc *tcf2Config) PurposeEnforced(purpose consentconstants.Purpose) bool {
+	if value, exists := tc.AccountConfig.PurposeEnforced(purpose); exists {
+		return value
+	}
+
+	value := tc.HostConfig.PurposeEnforced(purpose)
+	return value
+}
+
+// PurposeEnforcingVendors checks if enforcing vendors is turned on for a given purpose by first looking at the
+// account settings, and if not set there, defaulting to the host configuration. With enforcing vendors enabled,
+// the GDPR full enforcement algorithm considers the GVL when determining legal basis; otherwise it's skipped.
+func (tc *tcf2Config) PurposeEnforcingVendors(purpose consentconstants.Purpose) bool {
+	if value, exists := tc.AccountConfig.PurposeEnforcingVendors(purpose); exists {
+		return value
+	}
+
+	value := tc.HostConfig.PurposeEnforcingVendors(purpose)
+	return value
+}
+
+// PurposeVendorException checks if the specified bidder is considered a vendor exception for a given purpose by first
+// looking at the account settings, and if not set there, defaulting to the host configuration. If a bidder is a vendor
+// exception, the GDPR full enforcement algorithm will bypass the legal basis calculation assuming the request is valid
+// and there isn't a "deny all" publisher restriction
+func (tc *tcf2Config) PurposeVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) bool {
+	if value, exists := tc.AccountConfig.PurposeVendorException(purpose, bidder); exists {
+		return value
+	}
+	value := tc.HostConfig.PurposeVendorException(purpose, bidder)
+	return value
+}
+
+// FeatureOneEnforced checks if special feature one is enforced by first looking at the account settings, and if not
+// set there, defaulting to the host configuration. If it is enforced, PBS will determine whether geo information
+// may be passed through in the bid request.
+func (tc *tcf2Config) FeatureOneEnforced() bool {
+	if value, exists := tc.AccountConfig.FeatureOneEnforced(); exists {
+		return value
+	}
+	value := tc.HostConfig.FeatureOneEnforced()
+	return value
+}
+
+// FeatureOneVendorException checks if the specified bidder is considered a vendor exception for special feature one
+// by first looking at the account settings, and if not set there, defaulting to the host configuration. If a bidder
+// is a vendor exception, PBS will bypass the pass geo calculation passing the geo information in the bid request.
+func (tc *tcf2Config) FeatureOneVendorException(bidder openrtb_ext.BidderName) bool {
+	if value, exists := tc.AccountConfig.FeatureOneVendorException(bidder); exists {
+		return value
+	}
+	value := tc.HostConfig.FeatureOneVendorException(bidder)
+	return value
+}
+
+// PurposeOneTreatmentEnabled checks if purpose one treatment is enabled by first looking at the account settings, and
+// if not set there, defaulting to the host configuration.
+func (tc *tcf2Config) PurposeOneTreatmentEnabled() bool {
+	if value, exists := tc.AccountConfig.PurposeOneTreatmentEnabled(); exists {
+		return value
+	}
+	value := tc.HostConfig.PurposeOneTreatmentEnabled()
+	return value
+}
+
+// PurposeOneTreatmentAccessAllowed checks if purpose one treatment access is allowed by first looking at the account
+// settings, and if not set there, defaulting to the host configuration.
+func (tc *tcf2Config) PurposeOneTreatmentAccessAllowed() bool {
+	if value, exists := tc.AccountConfig.PurposeOneTreatmentAccessAllowed(); exists {
+		return value
+	}
+	value := tc.HostConfig.PurposeOneTreatmentAccessAllowed()
+	return value
+}
+
+// BasicEnforcementVendor checks if the given bidder is considered a basic enforcement vendor by looking at the account
+// settings, and if not set there, defaulting to false. If set, the legal basis calculation for the bidder only considers
+// consent to the purpose, not the vendor. The idea is that the publisher trusts this vendor to enforce the
+// appropriate rules on their own. This only comes into play when enforceVendors is true as it lists those vendors that
+// are exempt for vendor enforcement.
+func (tc *tcf2Config) BasicEnforcementVendor(bidder openrtb_ext.BidderName) bool {
+	if value, exists := tc.AccountConfig.BasicEnforcementVendor(bidder); exists {
+		return value
+	}
+	return false
+}
diff --git a/gdpr/gdpr.go b/gdpr/gdpr.go
index 0e203d88..ecbbd9f4 100644
--- a/gdpr/gdpr.go
+++ b/gdpr/gdpr.go
@@ -2,10 +2,7 @@ package gdpr
 
 import (
 	"context"
-	"net/http"
 
-	"github.com/prebid/go-gdpr/consentconstants"
-	"github.com/prebid/go-gdpr/vendorlist"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
@@ -24,45 +21,24 @@ type Permissions interface {
 	// Determines whether or not to send PI information to a bidder, or mask it out.
 	//
 	// If the consent string was nonsensical, the returned error will be an ErrorMalformedConsent.
-	AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error)
+	AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error)
 }
 
-// Versions of the GDPR TCF technical specification.
-const (
-	tcf2SpecVersion uint8 = 2
-)
+type PermissionsBuilder func(config.GDPR, TCF2ConfigReader, map[openrtb_ext.BidderName]uint16, VendorListFetcher) Permissions
 
 // NewPermissions gets an instance of the Permissions for use elsewhere in the project.
-func NewPermissions(ctx context.Context, cfg config.GDPR, vendorIDs map[openrtb_ext.BidderName]uint16, client *http.Client) Permissions {
+func NewPermissions(cfg config.GDPR, tcf2Config TCF2ConfigReader, vendorIDs map[openrtb_ext.BidderName]uint16, fetcher VendorListFetcher) Permissions {
 	if !cfg.Enabled {
 		return &AlwaysAllow{}
 	}
 
-	gdprDefaultValue := SignalYes
-	if cfg.DefaultValue == "0" {
-		gdprDefaultValue = SignalNo
-	}
-
-	purposeConfigs := map[consentconstants.Purpose]config.TCF2Purpose{
-		1:  cfg.TCF2.Purpose1,
-		2:  cfg.TCF2.Purpose2,
-		3:  cfg.TCF2.Purpose3,
-		4:  cfg.TCF2.Purpose4,
-		5:  cfg.TCF2.Purpose5,
-		6:  cfg.TCF2.Purpose6,
-		7:  cfg.TCF2.Purpose7,
-		8:  cfg.TCF2.Purpose8,
-		9:  cfg.TCF2.Purpose9,
-		10: cfg.TCF2.Purpose10,
-	}
-
 	permissionsImpl := &permissionsImpl{
-		cfg:              cfg,
-		gdprDefaultValue: gdprDefaultValue,
-		purposeConfigs:   purposeConfigs,
-		vendorIDs:        vendorIDs,
-		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf2SpecVersion: newVendorListFetcher(ctx, cfg, client, vendorListURLMaker)},
+		fetchVendorList:       fetcher,
+		gdprDefaultValue:      cfg.DefaultValue,
+		hostVendorID:          cfg.HostVendorID,
+		nonStandardPublishers: cfg.NonStandardPublisherMap,
+		cfg:                   tcf2Config,
+		vendorIDs:             vendorIDs,
 	}
 
 	if cfg.HostVendorID == 0 {
diff --git a/gdpr/impl.go b/gdpr/impl.go
index c580c380..06f9dc11 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -10,31 +10,30 @@ import (
 	tcf2ConsentConstants "github.com/prebid/go-gdpr/consentconstants/tcf2"
 	"github.com/prebid/go-gdpr/vendorconsent"
 	tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2"
-	"github.com/prebid/go-gdpr/vendorlist"
-	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
 
 type permissionsImpl struct {
-	cfg              config.GDPR
-	gdprDefaultValue Signal
-	purposeConfigs   map[consentconstants.Purpose]config.TCF2Purpose
-	vendorIDs        map[openrtb_ext.BidderName]uint16
-	fetchVendorList  map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error)
+	fetchVendorList       VendorListFetcher
+	gdprDefaultValue      string
+	hostVendorID          int
+	nonStandardPublishers map[string]struct{}
+	cfg                   TCF2ConfigReader
+	vendorIDs             map[openrtb_ext.BidderName]uint16
 }
 
 func (p *permissionsImpl) HostCookiesAllowed(ctx context.Context, gdprSignal Signal, consent string) (bool, error) {
-	gdprSignal = SignalNormalize(gdprSignal, p.cfg)
+	gdprSignal = SignalNormalize(gdprSignal, p.gdprDefaultValue)
 
 	if gdprSignal == SignalNo {
 		return true, nil
 	}
 
-	return p.allowSync(ctx, uint16(p.cfg.HostVendorID), consent, false)
+	return p.allowSync(ctx, uint16(p.hostVendorID), consent, false)
 }
 
 func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.BidderName, gdprSignal Signal, consent string) (bool, error) {
-	gdprSignal = SignalNormalize(gdprSignal, p.cfg)
+	gdprSignal = SignalNormalize(gdprSignal, p.gdprDefaultValue)
 
 	if gdprSignal == SignalNo {
 		return true, nil
@@ -42,19 +41,19 @@ func (p *permissionsImpl) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 
 	id, ok := p.vendorIDs[bidder]
 	if ok {
-		vendorException := p.isVendorException(consentconstants.Purpose(1), bidder)
+		vendorException := p.cfg.PurposeVendorException(consentconstants.Purpose(1), bidder)
 		return p.allowSync(ctx, id, consent, vendorException)
 	}
 
 	return false, nil
 }
 
-func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error) {
-	if _, ok := p.cfg.NonStandardPublisherMap[PublisherID]; ok {
+func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error) {
+	if _, ok := p.nonStandardPublishers[PublisherID]; ok {
 		return true, true, true, nil
 	}
 
-	gdprSignal = SignalNormalize(gdprSignal, p.cfg)
+	gdprSignal = SignalNormalize(gdprSignal, p.gdprDefaultValue)
 
 	if gdprSignal == SignalNo {
 		return true, true, true, nil
@@ -64,6 +63,8 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCo
 		return false, false, false, nil
 	}
 
+	weakVendorEnforcement := p.cfg.BasicEnforcementVendor(bidder)
+
 	if id, ok := p.resolveVendorId(bidderCoreName, bidder, aliasGVLIDs); ok {
 		return p.allowActivities(ctx, id, bidderCoreName, consent, weakVendorEnforcement)
 	} else if weakVendorEnforcement {
@@ -101,7 +102,7 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen
 		return false, nil
 	}
 
-	if p.cfg.TCF2.Purpose1.EnforcePurpose == config.TCF2NoEnforcement {
+	if !p.cfg.PurposeEnforced(consentconstants.Purpose(1)) {
 		return true, nil
 	}
 	consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
@@ -109,7 +110,13 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, consen
 		err := errors.New("Unable to access TCF2 parsed consent")
 		return false, err
 	}
-	return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, vendorException, false), nil
+
+	if p.cfg.PurposeOneTreatmentEnabled() && consentMeta.PurposeOneTreatment() {
+		return p.cfg.PurposeOneTreatmentAccessAllowed(), nil
+	}
+
+	enforceVendors := p.cfg.PurposeEnforcingVendors(tcf2ConsentConstants.InfoStorageAccess)
+	return p.checkPurpose(consentMeta, vendor, vendorID, tcf2ConsentConstants.InfoStorageAccess, enforceVendors, vendorException, false), nil
 }
 
 func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16, bidder openrtb_ext.BidderName, consent string, weakVendorEnforcement bool) (allowBidRequest bool, passGeo bool, passID bool, err error) {
@@ -127,7 +134,7 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16,
 		}
 	}
 
-	if !p.cfg.TCF2.Enabled {
+	if !p.cfg.IsEnabled() {
 		return true, false, false, nil
 	}
 
@@ -137,21 +144,23 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16,
 		return
 	}
 
-	if p.cfg.TCF2.SpecialFeature1.Enforce {
-		vendorException := p.isSpecialFeatureVendorException(bidder)
+	if p.cfg.FeatureOneEnforced() {
+		vendorException := p.cfg.FeatureOneVendorException(bidder)
 		passGeo = vendorException || (consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialFeature(1) || weakVendorEnforcement))
 	} else {
 		passGeo = true
 	}
-	if p.cfg.TCF2.Purpose2.EnforcePurpose == config.TCF2FullEnforcement {
-		vendorException := p.isVendorException(consentconstants.Purpose(2), bidder)
-		allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), vendorException, weakVendorEnforcement)
+	if p.cfg.PurposeEnforced(consentconstants.Purpose(2)) {
+		enforceVendors := p.cfg.PurposeEnforcingVendors(consentconstants.Purpose(2))
+		vendorException := p.cfg.PurposeVendorException(consentconstants.Purpose(2), bidder)
+		allowBidRequest = p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(2), enforceVendors, vendorException, weakVendorEnforcement)
 	} else {
 		allowBidRequest = true
 	}
 	for i := 2; i <= 10; i++ {
-		vendorException := p.isVendorException(consentconstants.Purpose(i), bidder)
-		if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), vendorException, weakVendorEnforcement) {
+		enforceVendors := p.cfg.PurposeEnforcingVendors(consentconstants.Purpose(i))
+		vendorException := p.cfg.PurposeVendorException(consentconstants.Purpose(i), bidder)
+		if p.checkPurpose(consentMeta, vendor, vendorID, consentconstants.Purpose(i), enforceVendors, vendorException, weakVendorEnforcement) {
 			passID = true
 			break
 		}
@@ -160,28 +169,11 @@ func (p *permissionsImpl) allowActivities(ctx context.Context, vendorID uint16,
 	return
 }
 
-func (p *permissionsImpl) isVendorException(purpose consentconstants.Purpose, bidder openrtb_ext.BidderName) (vendorException bool) {
-	if _, ok := p.purposeConfigs[purpose].VendorExceptionMap[bidder]; ok {
-		vendorException = true
-	}
-	return
-}
-
-func (p *permissionsImpl) isSpecialFeatureVendorException(bidder openrtb_ext.BidderName) (vendorException bool) {
-	if _, ok := p.cfg.TCF2.SpecialFeature1.VendorExceptionMap[bidder]; ok {
-		vendorException = true
-	}
-	return
-}
-
 const pubRestrictNotAllowed = 0
 const pubRestrictRequireConsent = 1
 const pubRestrictRequireLegitInterest = 2
 
-func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, vendorException, weakVendorEnforcement bool) bool {
-	if purpose == tcf2ConsentConstants.InfoStorageAccess && p.cfg.TCF2.PurposeOneTreatment.Enabled && consent.PurposeOneTreatment() {
-		return p.cfg.TCF2.PurposeOneTreatment.AccessAllowed
-	}
+func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, enforceVendors, vendorException, weakVendorEnforcement bool) bool {
 	if consent.CheckPubRestriction(uint8(purpose), pubRestrictNotAllowed, vendorID) {
 		return false
 	}
@@ -190,8 +182,8 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.
 		return true
 	}
 
-	purposeAllowed := p.consentEstablished(consent, vendor, vendorID, purpose, weakVendorEnforcement)
-	legitInterest := p.legitInterestEstablished(consent, vendor, vendorID, purpose, weakVendorEnforcement)
+	purposeAllowed := p.consentEstablished(consent, vendor, vendorID, purpose, enforceVendors, weakVendorEnforcement)
+	legitInterest := p.legitInterestEstablished(consent, vendor, vendorID, purpose, enforceVendors, weakVendorEnforcement)
 
 	if consent.CheckPubRestriction(uint8(purpose), pubRestrictRequireConsent, vendorID) {
 		return purposeAllowed
@@ -204,14 +196,14 @@ func (p *permissionsImpl) checkPurpose(consent tcf2.ConsentMetadata, vendor api.
 	return purposeAllowed || legitInterest
 }
 
-func (p *permissionsImpl) consentEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool {
+func (p *permissionsImpl) consentEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, enforceVendors, weakVendorEnforcement bool) bool {
 	if !consent.PurposeAllowed(purpose) {
 		return false
 	}
 	if weakVendorEnforcement {
 		return true
 	}
-	if !p.purposeConfigs[purpose].EnforceVendors {
+	if !enforceVendors {
 		return true
 	}
 	if vendor.Purpose(purpose) && consent.VendorConsent(vendorID) {
@@ -220,14 +212,14 @@ func (p *permissionsImpl) consentEstablished(consent tcf2.ConsentMetadata, vendo
 	return false
 }
 
-func (p *permissionsImpl) legitInterestEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, weakVendorEnforcement bool) bool {
+func (p *permissionsImpl) legitInterestEstablished(consent tcf2.ConsentMetadata, vendor api.Vendor, vendorID uint16, purpose consentconstants.Purpose, enforceVendors, weakVendorEnforcement bool) bool {
 	if !consent.PurposeLITransparency(purpose) {
 		return false
 	}
 	if weakVendorEnforcement {
 		return true
 	}
-	if !p.purposeConfigs[purpose].EnforceVendors {
+	if !enforceVendors {
 		return true
 	}
 	if vendor.LegitimateInterest(purpose) && consent.VendorLegitInterest(vendorID) {
@@ -251,7 +243,7 @@ func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, cons
 		return
 	}
 
-	vendorList, err := p.fetchVendorList[version](ctx, parsedConsent.VendorListVersion())
+	vendorList, err := p.fetchVendorList(ctx, parsedConsent.VendorListVersion())
 	if err != nil {
 		return
 	}
@@ -281,7 +273,7 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B
 	return true, nil
 }
 
-func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, weakVendorEnforcement bool, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error) {
+func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal Signal, consent string, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error) {
 	return true, true, true, nil
 }
 
diff --git a/gdpr/signal.go b/gdpr/signal.go
index 9487b48a..8a5317a4 100644
--- a/gdpr/signal.go
+++ b/gdpr/signal.go
@@ -3,7 +3,6 @@ package gdpr
 import (
 	"strconv"
 
-	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
 )
 
@@ -33,12 +32,12 @@ func SignalParse(rawSignal string) (Signal, error) {
 }
 
 // SignalNormalize normalizes a GDPR signal to ensure it's always either SignalYes or SignalNo.
-func SignalNormalize(signal Signal, config config.GDPR) Signal {
+func SignalNormalize(signal Signal, gdprDefaultValue string) Signal {
 	if signal != SignalAmbiguous {
 		return signal
 	}
 
-	if config.DefaultValue == "0" {
+	if gdprDefaultValue == "0" {
 		return SignalNo
 	}
 
diff --git a/gdpr/vendorlist-fetching.go b/gdpr/vendorlist-fetching.go
index 24489e73..51c41d22 100644
--- a/gdpr/vendorlist-fetching.go
+++ b/gdpr/vendorlist-fetching.go
@@ -19,6 +19,7 @@ import (
 )
 
 type saveVendors func(uint16, api.VendorList)
+type VendorListFetcher func(ctx context.Context, id uint16) (vendorlist.VendorList, error)
 
 // This file provides the vendorlist-fetching function for Prebid Server.
 //
@@ -26,7 +27,7 @@ type saveVendors func(uint16, api.VendorList)
 //
 // Nothing in this file is exported. Public APIs can be found in gdpr.go
 
-func newVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
+func NewVendorListFetcher(initCtx context.Context, cfg config.GDPR, client *http.Client, urlMaker func(uint16) string) VendorListFetcher {
 	cacheSave, cacheLoad := newVendorListCache()
 
 	preloadContext, cancel := context.WithTimeout(initCtx, cfg.Timeouts.InitTimeout())
@@ -73,7 +74,7 @@ func preloadCache(ctx context.Context, client *http.Client, urlMaker func(uint16
 
 // Make a URL which can be used to fetch a given version of the Global Vendor List. If the version is 0,
 // this will fetch the latest version.
-func vendorListURLMaker(vendorListVersion uint16) string {
+func VendorListURLMaker(vendorListVersion uint16) string {
 	if vendorListVersion == 0 {
 		return "https://vendor-list.consensu.org/v2/vendor-list.json"
 	}
diff --git a/router/router.go b/router/router.go
index eb887c02..10b231a0 100644
--- a/router/router.go
+++ b/router/router.go
@@ -197,7 +197,8 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R
 	}
 
 	gvlVendorIDs := bidderInfos.ToGVLVendorIDMap()
-	gdprPerms := gdpr.NewPermissions(context.Background(), cfg.GDPR, gvlVendorIDs, generalHttpClient)
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), cfg.GDPR, generalHttpClient, gdpr.VendorListURLMaker)
+	gdprPerms := gdpr.NewPermissions(cfg.GDPR, &cfg.GDPR.TCF2, gvlVendorIDs, vendorListFetcher)
 
 	cacheClient := pbc.NewClient(cacheHttpClient, &cfg.CacheURL, &cfg.ExtCacheURL, r.MetricsEngine)
 
@@ -207,7 +208,7 @@ func New(cfg *config.Configuration, rateConvertor *currency.RateConverter) (r *R
 		return nil, errs
 	}
 
-	theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, bidderInfos, gdprPerms, rateConvertor, categoriesFetcher)
+	theExchange := exchange.NewExchange(adapters, cacheClient, cfg, syncersByBidder, r.MetricsEngine, bidderInfos, vendorListFetcher, rateConvertor, categoriesFetcher)
 	var uuidGenerator uuidutil.UUIDRandomGenerator
 	openrtbEndpoint, err := openrtb2.NewEndpoint(uuidGenerator, theExchange, paramsValidator, fetcher, accounts, cfg, r.MetricsEngine, pbsAnalytics, disabledBidders, defReqJSON, activeBidders, storedRespFetcher)
 	if err != nil {
diff --git a/static/bidder-params/pubmatic.json b/static/bidder-params/pubmatic.json
index a13b5906..2b903608 100644
--- a/static/bidder-params/pubmatic.json
+++ b/static/bidder-params/pubmatic.json
@@ -24,6 +24,13 @@
 			"type": "string",
 			"description": "Deals Custom Targeting, pipe separated key-value pairs e.g key1=V1,V2,V3|key2=v1|key3=v3,v5"
 		},
+		"acat": {
+			"type": "array",
+			"description": "List of allowed categories for a given auction to be sent in request.ext",
+			"items": {
+				"type": "string"
+			}
+		},
 		"wrapper": {
 			"type": "object",
 			"description": "Specifies pubmatic openwrap configuration for a publisher",


Test patch:
diff --git a/account/account_test.go b/account/account_test.go
index 75c48a02..2b7f6a4e 100644
--- a/account/account_test.go
+++ b/account/account_test.go
@@ -9,6 +9,7 @@ import (
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/metrics"
+	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/stored_requests"
 	"github.com/stretchr/testify/assert"
 )
@@ -25,7 +26,7 @@ func (af mockAccountFetcher) FetchAccount(ctx context.Context, accountID string)
 	if account, ok := mockAccountData[accountID]; ok {
 		return account, nil
 	}
-	return nil, []error{stored_requests.NotFoundError{accountID, "Account"}}
+	return nil, []error{stored_requests.NotFoundError{ID: accountID, DataType: "Account"}}
 }
 
 func TestGetAccount(t *testing.T) {
@@ -92,3 +93,84 @@ func TestGetAccount(t *testing.T) {
 		})
 	}
 }
+
+func TestSetDerivedConfig(t *testing.T) {
+	tests := []struct {
+		description              string
+		purpose1VendorExceptions []openrtb_ext.BidderName
+		feature1VendorExceptions []openrtb_ext.BidderName
+		basicEnforcementVendors  []string
+	}{
+		{
+			description:              "Nil purpose 1 vendor exceptions",
+			purpose1VendorExceptions: nil,
+		},
+		{
+			description:              "One purpose 1 vendor exception",
+			purpose1VendorExceptions: []openrtb_ext.BidderName{"appnexus"},
+		},
+		{
+			description:              "Multiple purpose 1 vendor exceptions",
+			purpose1VendorExceptions: []openrtb_ext.BidderName{"appnexus", "rubicon"},
+		},
+		{
+			description:              "Nil feature 1 vendor exceptions",
+			feature1VendorExceptions: nil,
+		},
+		{
+			description:              "One feature 1 vendor exception",
+			feature1VendorExceptions: []openrtb_ext.BidderName{"appnexus"},
+		},
+		{
+			description:              "Multiple feature 1 vendor exceptions",
+			feature1VendorExceptions: []openrtb_ext.BidderName{"appnexus", "rubicon"},
+		},
+		{
+			description:             "Nil basic enforcement vendors",
+			basicEnforcementVendors: nil,
+		},
+		{
+			description:             "One basic enforcement vendor",
+			basicEnforcementVendors: []string{"appnexus"},
+		},
+		{
+			description:             "Multiple basic enforcement vendors",
+			basicEnforcementVendors: []string{"appnexus", "rubicon"},
+		},
+	}
+
+	for _, tt := range tests {
+		account := config.Account{
+			GDPR: config.AccountGDPR{
+				Purpose1: config.AccountGDPRPurpose{
+					VendorExceptions: tt.purpose1VendorExceptions,
+				},
+				SpecialFeature1: config.AccountGDPRSpecialFeature{
+					VendorExceptions: tt.feature1VendorExceptions,
+				},
+				BasicEnforcementVendors: tt.basicEnforcementVendors,
+			},
+		}
+
+		setDerivedConfig(&account)
+
+		purpose1ExceptionMapKeys := make([]openrtb_ext.BidderName, 0)
+		for k := range account.GDPR.Purpose1.VendorExceptionMap {
+			purpose1ExceptionMapKeys = append(purpose1ExceptionMapKeys, k)
+		}
+
+		feature1ExceptionMapKeys := make([]openrtb_ext.BidderName, 0)
+		for k := range account.GDPR.SpecialFeature1.VendorExceptionMap {
+			feature1ExceptionMapKeys = append(feature1ExceptionMapKeys, k)
+		}
+
+		basicEnforcementMapKeys := make([]string, 0)
+		for k := range account.GDPR.BasicEnforcementVendorsMap {
+			basicEnforcementMapKeys = append(basicEnforcementMapKeys, k)
+		}
+
+		assert.ElementsMatch(t, purpose1ExceptionMapKeys, tt.purpose1VendorExceptions, tt.description)
+		assert.ElementsMatch(t, feature1ExceptionMapKeys, tt.feature1VendorExceptions, tt.description)
+		assert.ElementsMatch(t, basicEnforcementMapKeys, tt.basicEnforcementVendors, tt.description)
+	}
+}
diff --git a/adapters/pubmatic/pubmatic_test.go b/adapters/pubmatic/pubmatic_test.go
index da5f632e..4985d052 100644
--- a/adapters/pubmatic/pubmatic_test.go
+++ b/adapters/pubmatic/pubmatic_test.go
@@ -5,6 +5,7 @@ import (
 	"testing"
 
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/prebid-server/adapters"
 	"github.com/prebid/prebid-server/adapters/adapterstest"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
@@ -150,3 +151,116 @@ func TestParseImpressionObject(t *testing.T) {
 		})
 	}
 }
+
+func TestExtractPubmaticExtFromRequest(t *testing.T) {
+	type args struct {
+		request *openrtb2.BidRequest
+	}
+	tests := []struct {
+		name               string
+		args               args
+		expectedWrapperExt *pubmaticWrapperExt
+		expectedAcat       []string
+		wantErr            bool
+	}{
+		{
+			name:    "Empty bidder param",
+			wantErr: true,
+		},
+		{
+			name: "Pubmatic wrapper ext missing/empty",
+			args: args{
+				request: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"bidderparams":{}}}`),
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "Only Pubmatic wrapper ext present",
+			args: args{
+				request: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":{"profile":123,"version":456}}}}`),
+				},
+			},
+			expectedWrapperExt: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456},
+			wantErr:            false,
+		},
+		{
+			name: "Invalid Pubmatic wrapper ext",
+			args: args{
+				request: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":{"profile":"123","version":456}}}}`),
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "Valid Pubmatic acat ext",
+			args: args{
+				request: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"bidderparams":{"acat":[" drg \t","dlu","ssr"],"wrapper":{"profile":123,"version":456}}}}`),
+				},
+			},
+			expectedWrapperExt: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456},
+			expectedAcat:       []string{"drg", "dlu", "ssr"},
+			wantErr:            false,
+		},
+		{
+			name: "Invalid Pubmatic acat ext. We are ok with acat being non nil in this case as we are returning unmarshal error",
+			args: args{
+				request: &openrtb2.BidRequest{
+					Ext: json.RawMessage(`{"prebid":{"bidderparams":{"acat":[1,3,4],"wrapper":{"profile":123,"version":456}}}}`),
+				},
+			},
+			expectedWrapperExt: &pubmaticWrapperExt{ProfileID: 123, VersionID: 456},
+			expectedAcat:       []string{"", "", ""},
+			wantErr:            true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			gotWrapperExt, gotAcat, err := extractPubmaticExtFromRequest(tt.args.request)
+			assert.Equal(t, tt.wantErr, err != nil)
+			assert.Equal(t, tt.expectedWrapperExt, gotWrapperExt)
+			assert.Equal(t, tt.expectedAcat, gotAcat)
+		})
+	}
+}
+
+func TestPubmaticAdapter_MakeRequests(t *testing.T) {
+	type fields struct {
+		URI string
+	}
+	type args struct {
+		request *openrtb2.BidRequest
+		reqInfo *adapters.ExtraRequestInfo
+	}
+	tests := []struct {
+		name            string
+		fields          fields
+		args            args
+		expectedReqData []*adapters.RequestData
+		wantErr         bool
+	}{
+		// Happy paths covered by TestJsonSamples()
+		// Covering only error scenarios here
+		{
+			name: "invalid bidderparams",
+			args: args{
+				request: &openrtb2.BidRequest{Ext: json.RawMessage(`{"prebid":{"bidderparams":{"wrapper":"123"}}}`)},
+			},
+			wantErr: true,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			a := &PubmaticAdapter{
+				URI: tt.fields.URI,
+			}
+			gotReqData, gotErr := a.MakeRequests(tt.args.request, tt.args.reqInfo)
+			assert.Equal(t, tt.wantErr, len(gotErr) != 0)
+			assert.Equal(t, tt.expectedReqData, gotReqData)
+		})
+	}
+}
diff --git a/adapters/pubmatic/pubmatictest/exemplary/banner.json b/adapters/pubmatic/pubmatictest/exemplary/banner.json
index 313099f2..5012192a 100644
--- a/adapters/pubmatic/pubmatictest/exemplary/banner.json
+++ b/adapters/pubmatic/pubmatictest/exemplary/banner.json
@@ -33,6 +33,13 @@
         "device":{
             "ua": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36"
         },
+        "ext": {
+          "prebid": {
+            "bidderparams": {
+              "acat": ["drg","dlu","ssr"]
+            }
+          }
+        },
         "site": {
 		      	"id": "siteID",
 		    	  "publisher": {
@@ -81,7 +88,8 @@
                 "wrapper": {
                     "profile": 5123,
                     "version":1
-                }
+                },
+                "acat": ["drg","dlu","ssr"]
             }
           }
         },
diff --git a/adapters/pubmatic/pubmatictest/exemplary/video.json b/adapters/pubmatic/pubmatictest/exemplary/video.json
index 693ee3eb..cc43fc89 100644
--- a/adapters/pubmatic/pubmatictest/exemplary/video.json
+++ b/adapters/pubmatic/pubmatictest/exemplary/video.json
@@ -44,7 +44,14 @@
 		    	"publisher": {
 			    	"id": "1234"
 		  	}
-	  	}
+	  	},
+      "ext": {
+        "prebid": {
+          "bidderparams": {
+            "acat": ["drg","dlu","ssr"]
+          }
+        }
+      }
     },
   
     "httpCalls": [
@@ -92,7 +99,8 @@
                 "wrapper": {
                     "profile": 5123,
                     "version":1
-                }
+                },
+                "acat": ["drg","dlu","ssr"]
             }
           }
         },
diff --git a/config/accounts_test.go b/config/accounts_test.go
index bfca27d8..e2e0b169 100644
--- a/config/accounts_test.go
+++ b/config/accounts_test.go
@@ -3,6 +3,8 @@ package config
 import (
 	"testing"
 
+	"github.com/prebid/go-gdpr/consentconstants"
+	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -241,3 +243,467 @@ func TestAccountIntegrationGetByIntegrationType(t *testing.T) {
 		}
 	}
 }
+
+func TestPurposeEnforced(t *testing.T) {
+	tests := []struct {
+		description          string
+		givePurposeConfigNil bool
+		givePurpose1Enforced string
+		givePurpose2Enforced string
+		givePurpose          consentconstants.Purpose
+		wantEnforced         bool
+		wantEnforcedSet      bool
+	}{
+		{
+			description:          "Purpose config is nil",
+			givePurposeConfigNil: true,
+			givePurpose:          1,
+			wantEnforced:         true,
+			wantEnforcedSet:      false,
+		},
+		{
+			description:          "Purpose 1 Enforced not set",
+			givePurpose1Enforced: "",
+			givePurpose:          1,
+			wantEnforced:         true,
+			wantEnforcedSet:      false,
+		},
+		{
+			description:          "Purpose 1 Enforced set to full enforcement",
+			givePurpose1Enforced: TCF2FullEnforcement,
+			givePurpose:          1,
+			wantEnforced:         true,
+			wantEnforcedSet:      true,
+		},
+		{
+			description:          "Purpose 1 Enforced set to no enforcement",
+			givePurpose1Enforced: TCF2NoEnforcement,
+			givePurpose:          1,
+			wantEnforced:         false,
+			wantEnforcedSet:      true,
+		},
+		{
+			description:          "Purpose 2 Enforced set to full enforcement",
+			givePurpose2Enforced: TCF2FullEnforcement,
+			givePurpose:          2,
+			wantEnforced:         true,
+			wantEnforcedSet:      true,
+		},
+	}
+
+	for _, tt := range tests {
+		accountGDPR := AccountGDPR{}
+
+		if !tt.givePurposeConfigNil {
+			accountGDPR.PurposeConfigs = map[consentconstants.Purpose]*AccountGDPRPurpose{
+				1: {
+					EnforcePurpose: tt.givePurpose1Enforced,
+				},
+				2: {
+					EnforcePurpose: tt.givePurpose2Enforced,
+				},
+			}
+		}
+
+		value, present := accountGDPR.PurposeEnforced(tt.givePurpose)
+
+		assert.Equal(t, tt.wantEnforced, value, tt.description)
+		assert.Equal(t, tt.wantEnforcedSet, present, tt.description)
+	}
+}
+
+func TestPurposeEnforcingVendors(t *testing.T) {
+	tests := []struct {
+		description           string
+		givePurposeConfigNil  bool
+		givePurpose1Enforcing *bool
+		givePurpose2Enforcing *bool
+		givePurpose           consentconstants.Purpose
+		wantEnforcing         bool
+		wantEnforcingSet      bool
+	}{
+		{
+			description:          "Purpose config is nil",
+			givePurposeConfigNil: true,
+			givePurpose:          1,
+			wantEnforcing:        true,
+			wantEnforcingSet:     false,
+		},
+		{
+			description:           "Purpose 1 Enforcing not set",
+			givePurpose1Enforcing: nil,
+			givePurpose:           1,
+			wantEnforcing:         true,
+			wantEnforcingSet:      false,
+		},
+		{
+			description:           "Purpose 1 Enforcing set to true",
+			givePurpose1Enforcing: &[]bool{true}[0],
+			givePurpose:           1,
+			wantEnforcing:         true,
+			wantEnforcingSet:      true,
+		},
+		{
+			description:           "Purpose 1 Enforcing set to false",
+			givePurpose1Enforcing: &[]bool{false}[0],
+			givePurpose:           1,
+			wantEnforcing:         false,
+			wantEnforcingSet:      true,
+		},
+		{
+			description:           "Purpose 2 Enforcing set to true",
+			givePurpose2Enforcing: &[]bool{true}[0],
+			givePurpose:           2,
+			wantEnforcing:         true,
+			wantEnforcingSet:      true,
+		},
+	}
+
+	for _, tt := range tests {
+		accountGDPR := AccountGDPR{}
+
+		if !tt.givePurposeConfigNil {
+			accountGDPR.PurposeConfigs = map[consentconstants.Purpose]*AccountGDPRPurpose{
+				1: {
+					EnforceVendors: tt.givePurpose1Enforcing,
+				},
+				2: {
+					EnforceVendors: tt.givePurpose2Enforcing,
+				},
+			}
+		}
+
+		value, present := accountGDPR.PurposeEnforcingVendors(tt.givePurpose)
+
+		assert.Equal(t, tt.wantEnforcing, value, tt.description)
+		assert.Equal(t, tt.wantEnforcingSet, present, tt.description)
+	}
+}
+
+func TestPurposeVendorException(t *testing.T) {
+	tests := []struct {
+		description              string
+		givePurposeConfigNil     bool
+		givePurpose1ExceptionMap map[openrtb_ext.BidderName]struct{}
+		givePurpose2ExceptionMap map[openrtb_ext.BidderName]struct{}
+		givePurpose              consentconstants.Purpose
+		giveBidder               openrtb_ext.BidderName
+		wantIsVendorException    bool
+		wantVendorExceptionSet   bool
+	}{
+		{
+			description:            "Purpose config is nil",
+			givePurposeConfigNil:   true,
+			givePurpose:            1,
+			giveBidder:             "appnexus",
+			wantIsVendorException:  false,
+			wantVendorExceptionSet: false,
+		},
+		{
+			description:            "Nil - exception map not defined for purpose",
+			givePurpose:            1,
+			giveBidder:             "appnexus",
+			wantIsVendorException:  false,
+			wantVendorExceptionSet: false,
+		},
+		{
+			description:              "Empty - exception map empty for purpose",
+			givePurpose:              1,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+			giveBidder:               "appnexus",
+			wantIsVendorException:    false,
+			wantVendorExceptionSet:   true,
+		},
+		{
+			description:              "One - bidder found in purpose exception map containing one entry",
+			givePurpose:              1,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}},
+			giveBidder:               "appnexus",
+			wantIsVendorException:    true,
+			wantVendorExceptionSet:   true,
+		},
+		{
+			description:              "Many - bidder found in purpose exception map containing multiple entries",
+			givePurpose:              1,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			giveBidder:               "appnexus",
+			wantIsVendorException:    true,
+			wantVendorExceptionSet:   true,
+		},
+		{
+			description:              "Many - bidder not found in purpose exception map containing multiple entries",
+			givePurpose:              1,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}},
+			giveBidder:               "openx",
+			wantIsVendorException:    false,
+			wantVendorExceptionSet:   true,
+		},
+		{
+			description:              "Many - bidder found in different purpose exception map containing multiple entries",
+			givePurpose:              2,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}},
+			giveBidder:               "openx",
+			wantIsVendorException:    true,
+			wantVendorExceptionSet:   true,
+		},
+	}
+
+	for _, tt := range tests {
+		accountGDPR := AccountGDPR{}
+
+		if !tt.givePurposeConfigNil {
+			accountGDPR.PurposeConfigs = map[consentconstants.Purpose]*AccountGDPRPurpose{
+				1: {
+					VendorExceptionMap: tt.givePurpose1ExceptionMap,
+				},
+				2: {
+					VendorExceptionMap: tt.givePurpose2ExceptionMap,
+				},
+			}
+		}
+
+		value, present := accountGDPR.PurposeVendorException(tt.givePurpose, tt.giveBidder)
+
+		assert.Equal(t, tt.wantIsVendorException, value, tt.description)
+		assert.Equal(t, tt.wantVendorExceptionSet, present, tt.description)
+	}
+}
+
+func TestFeatureOneEnforced(t *testing.T) {
+	tests := []struct {
+		description     string
+		giveEnforce     *bool
+		wantEnforcedSet bool
+		wantEnforced    bool
+	}{
+		{
+			description:     "Special feature 1 enforce not set",
+			giveEnforce:     nil,
+			wantEnforcedSet: false,
+			wantEnforced:    true,
+		},
+		{
+			description:     "Special feature 1 enforce set to true",
+			giveEnforce:     &[]bool{true}[0],
+			wantEnforcedSet: true,
+			wantEnforced:    true,
+		},
+		{
+			description:     "Special feature 1 enforce set to false",
+			giveEnforce:     &[]bool{false}[0],
+			wantEnforcedSet: true,
+			wantEnforced:    false,
+		},
+	}
+
+	for _, tt := range tests {
+		accountGDPR := AccountGDPR{
+			SpecialFeature1: AccountGDPRSpecialFeature{
+				Enforce: tt.giveEnforce,
+			},
+		}
+
+		value, present := accountGDPR.FeatureOneEnforced()
+
+		assert.Equal(t, tt.wantEnforced, value, tt.description)
+		assert.Equal(t, tt.wantEnforcedSet, present, tt.description)
+	}
+}
+
+func TestFeatureOneVendorException(t *testing.T) {
+	tests := []struct {
+		description            string
+		giveExceptionMap       map[openrtb_ext.BidderName]struct{}
+		giveBidder             openrtb_ext.BidderName
+		wantVendorExceptionSet bool
+		wantIsVendorException  bool
+	}{
+		{
+			description:            "Nil - exception map not defined",
+			giveBidder:             "appnexus",
+			wantVendorExceptionSet: false,
+			wantIsVendorException:  false,
+		},
+		{
+			description:            "Empty - exception map empty",
+			giveExceptionMap:       map[openrtb_ext.BidderName]struct{}{},
+			giveBidder:             "appnexus",
+			wantVendorExceptionSet: true,
+			wantIsVendorException:  false,
+		},
+		{
+			description:            "One - bidder found in exception map containing one entry",
+			giveExceptionMap:       map[openrtb_ext.BidderName]struct{}{"appnexus": {}},
+			giveBidder:             "appnexus",
+			wantVendorExceptionSet: true,
+			wantIsVendorException:  true,
+		},
+		{
+			description:            "Many - bidder found in exception map containing multiple entries",
+			giveExceptionMap:       map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			giveBidder:             "appnexus",
+			wantVendorExceptionSet: true,
+			wantIsVendorException:  true,
+		},
+		{
+			description:            "Many - bidder not found in exception map containing multiple entries",
+			giveExceptionMap:       map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			giveBidder:             "openx",
+			wantVendorExceptionSet: true,
+			wantIsVendorException:  false,
+		},
+	}
+
+	for _, tt := range tests {
+		accountGDPR := AccountGDPR{
+			SpecialFeature1: AccountGDPRSpecialFeature{
+				VendorExceptionMap: tt.giveExceptionMap,
+			},
+		}
+
+		value, present := accountGDPR.FeatureOneVendorException(tt.giveBidder)
+
+		assert.Equal(t, tt.wantIsVendorException, value, tt.description)
+		assert.Equal(t, tt.wantVendorExceptionSet, present, tt.description)
+	}
+}
+
+func TestPurposeOneTreatmentEnabled(t *testing.T) {
+	tests := []struct {
+		description    string
+		giveEnabled    *bool
+		wantEnabledSet bool
+		wantEnabled    bool
+	}{
+		{
+			description:    "Purpose one treatment enabled not set",
+			giveEnabled:    nil,
+			wantEnabledSet: false,
+			wantEnabled:    true,
+		},
+		{
+			description:    "Purpose one treatment enabled set to true",
+			giveEnabled:    &[]bool{true}[0],
+			wantEnabledSet: true,
+			wantEnabled:    true,
+		},
+		{
+			description:    "Purpose one treatment enabled set to false",
+			giveEnabled:    &[]bool{false}[0],
+			wantEnabledSet: true,
+			wantEnabled:    false,
+		},
+	}
+
+	for _, tt := range tests {
+		accountGDPR := AccountGDPR{
+			PurposeOneTreatment: AccountGDPRPurposeOneTreatment{
+				Enabled: tt.giveEnabled,
+			},
+		}
+
+		value, present := accountGDPR.PurposeOneTreatmentEnabled()
+
+		assert.Equal(t, tt.wantEnabled, value, tt.description)
+		assert.Equal(t, tt.wantEnabledSet, present, tt.description)
+	}
+}
+
+func TestPurposeOneTreatmentAccessAllowed(t *testing.T) {
+	tests := []struct {
+		description    string
+		giveAllowed    *bool
+		wantAllowedSet bool
+		wantAllowed    bool
+	}{
+		{
+			description:    "Purpose one treatment access allowed not set",
+			giveAllowed:    nil,
+			wantAllowedSet: false,
+			wantAllowed:    true,
+		},
+		{
+			description:    "Purpose one treatment access allowed set to true",
+			giveAllowed:    &[]bool{true}[0],
+			wantAllowedSet: true,
+			wantAllowed:    true,
+		},
+		{
+			description:    "Purpose one treatment access allowed set to false",
+			giveAllowed:    &[]bool{false}[0],
+			wantAllowedSet: true,
+			wantAllowed:    false,
+		},
+	}
+
+	for _, tt := range tests {
+		accountGDPR := AccountGDPR{
+			PurposeOneTreatment: AccountGDPRPurposeOneTreatment{
+				AccessAllowed: tt.giveAllowed,
+			},
+		}
+
+		value, present := accountGDPR.PurposeOneTreatmentAccessAllowed()
+
+		assert.Equal(t, tt.wantAllowed, value, tt.description)
+		assert.Equal(t, tt.wantAllowedSet, present, tt.description)
+	}
+}
+
+func TestBasicEnforcementVendor(t *testing.T) {
+	tests := []struct {
+		description        string
+		giveBasicVendorMap map[string]struct{}
+		giveBidder         openrtb_ext.BidderName
+		wantBasicVendorSet bool
+		wantIsBasicVendor  bool
+	}{
+		{
+			description:        "Nil - basic vendor map not defined",
+			giveBidder:         "appnexus",
+			wantBasicVendorSet: false,
+			wantIsBasicVendor:  false,
+		},
+		{
+			description:        "Empty - basic vendor map empty",
+			giveBasicVendorMap: map[string]struct{}{},
+			giveBidder:         "appnexus",
+			wantBasicVendorSet: true,
+			wantIsBasicVendor:  false,
+		},
+		{
+			description:        "One - bidder found in basic vendor map containing one entry",
+			giveBasicVendorMap: map[string]struct{}{"appnexus": {}},
+			giveBidder:         "appnexus",
+			wantBasicVendorSet: true,
+			wantIsBasicVendor:  true,
+		},
+		{
+			description:        "Many - bidder found in basic vendor map containing multiple entries",
+			giveBasicVendorMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			giveBidder:         "appnexus",
+			wantBasicVendorSet: true,
+			wantIsBasicVendor:  true,
+		},
+		{
+			description:        "Many - bidder not found in basic vendor map containing multiple entries",
+			giveBasicVendorMap: map[string]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			giveBidder:         "openx",
+			wantBasicVendorSet: true,
+			wantIsBasicVendor:  false,
+		},
+	}
+
+	for _, tt := range tests {
+		accountGDPR := AccountGDPR{
+			BasicEnforcementVendorsMap: tt.giveBasicVendorMap,
+		}
+
+		value, present := accountGDPR.BasicEnforcementVendor(tt.giveBidder)
+
+		assert.Equal(t, tt.wantIsBasicVendor, value, tt.description)
+		assert.Equal(t, tt.wantBasicVendorSet, present, tt.description)
+	}
+}
diff --git a/config/config_test.go b/config/config_test.go
index d06a9fd8..4f9bc44c 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -11,6 +11,7 @@ import (
 
 	"encoding/json"
 
+	"github.com/prebid/go-gdpr/consentconstants"
 	"github.com/prebid/prebid-server/errortypes"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/spf13/viper"
@@ -227,6 +228,18 @@ func TestDefaults(t *testing.T) {
 			AccessAllowed: true,
 		},
 	}
+	expectedTCF2.PurposeConfigs = map[consentconstants.Purpose]*TCF2Purpose{
+		1:  &expectedTCF2.Purpose1,
+		2:  &expectedTCF2.Purpose2,
+		3:  &expectedTCF2.Purpose3,
+		4:  &expectedTCF2.Purpose4,
+		5:  &expectedTCF2.Purpose5,
+		6:  &expectedTCF2.Purpose6,
+		7:  &expectedTCF2.Purpose7,
+		8:  &expectedTCF2.Purpose8,
+		9:  &expectedTCF2.Purpose9,
+		10: &expectedTCF2.Purpose10,
+	}
 	assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2")
 
 	// Assert User Sync Override Defaults To Nil
@@ -552,6 +565,18 @@ func TestFullConfig(t *testing.T) {
 			AccessAllowed: true, // true by default
 		},
 	}
+	expectedTCF2.PurposeConfigs = map[consentconstants.Purpose]*TCF2Purpose{
+		1:  &expectedTCF2.Purpose1,
+		2:  &expectedTCF2.Purpose2,
+		3:  &expectedTCF2.Purpose3,
+		4:  &expectedTCF2.Purpose4,
+		5:  &expectedTCF2.Purpose5,
+		6:  &expectedTCF2.Purpose6,
+		7:  &expectedTCF2.Purpose7,
+		8:  &expectedTCF2.Purpose8,
+		9:  &expectedTCF2.Purpose9,
+		10: &expectedTCF2.Purpose10,
+	}
 	assert.Equal(t, expectedTCF2, cfg.GDPR.TCF2, "gdpr.tcf2")
 
 	cmpStrings(t, "currency_converter.fetch_url", cfg.CurrencyConverter.FetchURL, "https://currency.prebid.org")
@@ -1435,3 +1460,252 @@ func TestSpecialFeature1VendorExceptionMap(t *testing.T) {
 		assert.Equal(t, tt.wantVendorExceptionsMap, cfg.GDPR.TCF2.SpecialFeature1.VendorExceptionMap, tt.description)
 	}
 }
+
+func TestTCF2PurposeEnforced(t *testing.T) {
+	tests := []struct {
+		description          string
+		givePurposeConfigNil bool
+		givePurpose1Enforced string
+		givePurpose2Enforced string
+		givePurpose          consentconstants.Purpose
+		wantEnforced         bool
+	}{
+		{
+			description:          "Purpose config is nil",
+			givePurposeConfigNil: true,
+			givePurpose:          1,
+			wantEnforced:         false,
+		},
+		{
+			description:          "Purpose 1 Enforced not set",
+			givePurpose1Enforced: "",
+			givePurpose:          1,
+			wantEnforced:         false,
+		},
+		{
+			description:          "Purpose 1 Enforced set to full enforcement",
+			givePurpose1Enforced: TCF2FullEnforcement,
+			givePurpose:          1,
+			wantEnforced:         true,
+		},
+		{
+			description:          "Purpose 1 Enforced set to no enforcement",
+			givePurpose1Enforced: TCF2NoEnforcement,
+			givePurpose:          1,
+			wantEnforced:         false,
+		},
+		{
+			description:          "Purpose 2 Enforced set to full enforcement",
+			givePurpose2Enforced: TCF2FullEnforcement,
+			givePurpose:          2,
+			wantEnforced:         true,
+		},
+	}
+
+	for _, tt := range tests {
+		tcf2 := TCF2{}
+
+		if !tt.givePurposeConfigNil {
+			tcf2.PurposeConfigs = map[consentconstants.Purpose]*TCF2Purpose{
+				1: {
+					EnforcePurpose: tt.givePurpose1Enforced,
+				},
+				2: {
+					EnforcePurpose: tt.givePurpose2Enforced,
+				},
+			}
+		}
+
+		value := tcf2.PurposeEnforced(tt.givePurpose)
+
+		assert.Equal(t, tt.wantEnforced, value, tt.description)
+	}
+}
+
+func TestTCF2PurposeEnforcingVendors(t *testing.T) {
+	tests := []struct {
+		description           string
+		givePurposeConfigNil  bool
+		givePurpose1Enforcing bool
+		givePurpose2Enforcing bool
+		givePurpose           consentconstants.Purpose
+		wantEnforcing         bool
+	}{
+		{
+			description:          "Purpose config is nil",
+			givePurposeConfigNil: true,
+			givePurpose:          1,
+			wantEnforcing:        false,
+		},
+		{
+			description:           "Purpose 1 Enforcing set to true",
+			givePurpose1Enforcing: true,
+			givePurpose:           1,
+			wantEnforcing:         true,
+		},
+		{
+			description:           "Purpose 1 Enforcing set to false",
+			givePurpose1Enforcing: false,
+			givePurpose:           1,
+			wantEnforcing:         false,
+		},
+		{
+			description:           "Purpose 2 Enforcing set to true",
+			givePurpose2Enforcing: true,
+			givePurpose:           2,
+			wantEnforcing:         true,
+		},
+	}
+
+	for _, tt := range tests {
+		tcf2 := TCF2{}
+
+		if !tt.givePurposeConfigNil {
+			tcf2.PurposeConfigs = map[consentconstants.Purpose]*TCF2Purpose{
+				1: {
+					EnforceVendors: tt.givePurpose1Enforcing,
+				},
+				2: {
+					EnforceVendors: tt.givePurpose2Enforcing,
+				},
+			}
+		}
+
+		value := tcf2.PurposeEnforcingVendors(tt.givePurpose)
+
+		assert.Equal(t, tt.wantEnforcing, value, tt.description)
+	}
+}
+
+func TestTCF2PurposeVendorException(t *testing.T) {
+	tests := []struct {
+		description              string
+		givePurposeConfigNil     bool
+		givePurpose1ExceptionMap map[openrtb_ext.BidderName]struct{}
+		givePurpose2ExceptionMap map[openrtb_ext.BidderName]struct{}
+		givePurpose              consentconstants.Purpose
+		giveBidder               openrtb_ext.BidderName
+		wantIsVendorException    bool
+	}{
+		{
+			description:           "Purpose config is nil",
+			givePurposeConfigNil:  true,
+			givePurpose:           1,
+			giveBidder:            "appnexus",
+			wantIsVendorException: false,
+		},
+		{
+			description:           "Nil - exception map not defined for purpose",
+			givePurpose:           1,
+			giveBidder:            "appnexus",
+			wantIsVendorException: false,
+		},
+		{
+			description:              "Empty - exception map empty for purpose",
+			givePurpose:              1,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{},
+			giveBidder:               "appnexus",
+			wantIsVendorException:    false,
+		},
+		{
+			description:              "One - bidder found in purpose exception map containing one entry",
+			givePurpose:              1,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}},
+			giveBidder:               "appnexus",
+			wantIsVendorException:    true,
+		},
+		{
+			description:              "Many - bidder found in purpose exception map containing multiple entries",
+			givePurpose:              1,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			giveBidder:               "appnexus",
+			wantIsVendorException:    true,
+		},
+		{
+			description:              "Many - bidder not found in purpose exception map containing multiple entries",
+			givePurpose:              1,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}},
+			giveBidder:               "openx",
+			wantIsVendorException:    false,
+		},
+		{
+			description:              "Many - bidder found in different purpose exception map containing multiple entries",
+			givePurpose:              2,
+			givePurpose1ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			givePurpose2ExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "openx": {}},
+			giveBidder:               "openx",
+			wantIsVendorException:    true,
+		},
+	}
+
+	for _, tt := range tests {
+		tcf2 := TCF2{}
+
+		if !tt.givePurposeConfigNil {
+			tcf2.PurposeConfigs = map[consentconstants.Purpose]*TCF2Purpose{
+				1: {
+					VendorExceptionMap: tt.givePurpose1ExceptionMap,
+				},
+				2: {
+					VendorExceptionMap: tt.givePurpose2ExceptionMap,
+				},
+			}
+		}
+
+		value := tcf2.PurposeVendorException(tt.givePurpose, tt.giveBidder)
+
+		assert.Equal(t, tt.wantIsVendorException, value, tt.description)
+	}
+}
+
+func TestTCF2FeatureOneVendorException(t *testing.T) {
+	tests := []struct {
+		description           string
+		giveExceptionMap      map[openrtb_ext.BidderName]struct{}
+		giveBidder            openrtb_ext.BidderName
+		wantIsVendorException bool
+	}{
+		{
+			description:           "Nil - exception map not defined",
+			giveBidder:            "appnexus",
+			wantIsVendorException: false,
+		},
+		{
+			description:           "Empty - exception map empty",
+			giveExceptionMap:      map[openrtb_ext.BidderName]struct{}{},
+			giveBidder:            "appnexus",
+			wantIsVendorException: false,
+		},
+		{
+			description:           "One - bidder found in exception map containing one entry",
+			giveExceptionMap:      map[openrtb_ext.BidderName]struct{}{"appnexus": {}},
+			giveBidder:            "appnexus",
+			wantIsVendorException: true,
+		},
+		{
+			description:           "Many - bidder found in exception map containing multiple entries",
+			giveExceptionMap:      map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			giveBidder:            "appnexus",
+			wantIsVendorException: true,
+		},
+		{
+			description:           "Many - bidder not found in exception map containing multiple entries",
+			giveExceptionMap:      map[openrtb_ext.BidderName]struct{}{"rubicon": {}, "appnexus": {}, "index": {}},
+			giveBidder:            "openx",
+			wantIsVendorException: false,
+		},
+	}
+
+	for _, tt := range tests {
+		tcf2 := TCF2{
+			SpecialFeature1: TCF2SpecialFeature{
+				VendorExceptionMap: tt.giveExceptionMap,
+			},
+		}
+
+		value := tcf2.FeatureOneVendorException(tt.giveBidder)
+
+		assert.Equal(t, tt.wantIsVendorException, value, tt.description)
+	}
+}
diff --git a/endpoints/cookie_sync_test.go b/endpoints/cookie_sync_test.go
index 263f8b1f..53f52a3e 100644
--- a/endpoints/cookie_sync_test.go
+++ b/endpoints/cookie_sync_test.go
@@ -1652,8 +1652,8 @@ func (m *MockGDPRPerms) BidderSyncAllowed(ctx context.Context, bidder openrtb_ex
 	return args.Bool(0), args.Error(1)
 }
 
-func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error) {
-	args := m.Called(ctx, bidderCoreName, bidder, PublisherID, gdprSignal, consent, weakVendorEnforcement, aliasGVLIDs)
+func (m *MockGDPRPerms) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error) {
+	args := m.Called(ctx, bidderCoreName, bidder, PublisherID, gdprSignal, consent, aliasGVLIDs)
 	return args.Bool(0), args.Bool(1), args.Bool(2), args.Error(3)
 }
 
diff --git a/endpoints/openrtb2/auction_benchmark_test.go b/endpoints/openrtb2/auction_benchmark_test.go
index bd61c21a..5cae8e83 100644
--- a/endpoints/openrtb2/auction_benchmark_test.go
+++ b/endpoints/openrtb2/auction_benchmark_test.go
@@ -1,17 +1,18 @@
 package openrtb2
 
 import (
+	"context"
 	"net/http"
 	"net/http/httptest"
 	"strings"
 	"testing"
 	"time"
 
+	"github.com/prebid/go-gdpr/vendorlist"
 	analyticsConf "github.com/prebid/prebid-server/analytics/config"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/currency"
 	"github.com/prebid/prebid-server/exchange"
-	"github.com/prebid/prebid-server/gdpr"
 	metricsConfig "github.com/prebid/prebid-server/metrics/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/prebid/prebid-server/stored_requests/backends/empty_fetcher"
@@ -81,7 +82,7 @@ func BenchmarkOpenrtbEndpoint(b *testing.B) {
 		map[string]usersync.Syncer{},
 		nilMetrics,
 		infos,
-		gdpr.AlwaysAllow{},
+		func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { return nil, nil },
 		currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		empty_fetcher.EmptyFetcher{},
 	)
diff --git a/endpoints/openrtb2/auction_test.go b/endpoints/openrtb2/auction_test.go
index 190405db..0f56036d 100644
--- a/endpoints/openrtb2/auction_test.go
+++ b/endpoints/openrtb2/auction_test.go
@@ -1855,13 +1855,13 @@ func TestStoredRequestGenerateUuid(t *testing.T) {
 			expectedID:             uuid,
 		},
 		{
-			description:            "GenerateRequestID is true, rawData is an app request, has stored bid, and stored bidrequestID is not the macro {{UUID}}, we should generate uuid",
+			description:            "GenerateRequestID is true, rawData is a site request, has stored bid, and stored bidrequestID is not the macro {{UUID}}, we should not generate uuid",
 			givenRawData:           testBidRequests[3],
 			givenGenerateRequestID: true,
-			expectedID:             uuid,
+			expectedID:             "ThisID",
 		},
 		{
-			description:            "GenerateRequestID is false, rawData is an app request and has stored bid, but stored bidrequestID is the macro {{UUID}}, so we should generate uuid",
+			description:            "GenerateRequestID is false, rawData is an app request and has stored bid, and stored bidrequestID is the macro {{UUID}}, so we should generate uuid",
 			givenRawData:           testBidRequests[4],
 			givenGenerateRequestID: false,
 			expectedID:             uuid,
@@ -1879,10 +1879,10 @@ func TestStoredRequestGenerateUuid(t *testing.T) {
 			expectedID:             "ThisID",
 		},
 		{
-			description:            "GenerateRequestID is true, but rawData is a site request, we should not generate uuid",
+			description:            "GenerateRequestID is false, and rawData is a site request, and macro {{UUID}} is present, we should generate uuid",
 			givenRawData:           testBidRequests[1],
-			givenGenerateRequestID: true,
-			expectedID:             "ThisID",
+			givenGenerateRequestID: false,
+			expectedID:             uuid,
 		},
 		{
 			description:            "Macro ID {{UUID}} case sensitivity check meaning a macro that is lowercase {{uuid}} shouldn't generate a uuid",
@@ -1891,19 +1891,12 @@ func TestStoredRequestGenerateUuid(t *testing.T) {
 			expectedID:             "ThisID",
 		},
 		{
-			description:            "Test to check that stored requests are being merged when Macro ID is present with a site rquest",
+			description:            "Test to check that stored requests are being merged properly when UUID isn't being generated",
 			givenRawData:           testBidRequests[5],
 			givenGenerateRequestID: false,
 			expectedID:             "ThisID",
 			expectedCur:            "USD",
 		},
-		{
-			description:            "Test to check that stored requests are being merged when Generate Request ID flag with a site rquest",
-			givenRawData:           testBidRequests[5],
-			givenGenerateRequestID: true,
-			expectedID:             "ThisID",
-			expectedCur:            "USD",
-		},
 	}
 
 	for _, test := range testCases {
@@ -3966,7 +3959,7 @@ var testStoredRequestData = map[string]json.RawMessage{
 						}
 				}}
 		}`),
-	"4": json.RawMessage(`{"id": "{{UUID}}", "cur": ["USD"]}`),
+	"4": json.RawMessage(`{"id": "ThisID", "cur": ["USD"]}`),
 }
 
 // Stored Imp Requests
@@ -4455,7 +4448,8 @@ var testBidRequests = []string{
 		],
 		"ext": {
 			"prebid": {
-				"targeting": {
+				"storedrequest": {
+					"id": "1"
 				}
 			}
 		}
@@ -4489,8 +4483,8 @@ var testBidRequests = []string{
 	}`,
 	`{
 		"id": "ThisID",
-		"app": {
-			"id": "123"
+		"site": {
+			"page": "prebid.org"
 		},
 		"imp": [
 			{
diff --git a/endpoints/setuid_test.go b/endpoints/setuid_test.go
index bfefeb3b..afe6d612 100644
--- a/endpoints/setuid_test.go
+++ b/endpoints/setuid_test.go
@@ -573,7 +573,7 @@ func (g *mockPermsSetUID) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return false, nil
 }
 
-func (g *mockPermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error) {
+func (g *mockPermsSetUID) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, aliasGVLIDs map[string]uint16) (allowBidReq bool, passGeo bool, passID bool, err error) {
 	return g.personalInfoAllowed, g.personalInfoAllowed, g.personalInfoAllowed, nil
 }
 
diff --git a/exchange/exchange_test.go b/exchange/exchange_test.go
index 474c0284..71393abd 100644
--- a/exchange/exchange_test.go
+++ b/exchange/exchange_test.go
@@ -17,6 +17,7 @@ import (
 
 	"github.com/buger/jsonparser"
 	"github.com/mxmCherry/openrtb/v15/openrtb2"
+	"github.com/prebid/go-gdpr/vendorlist"
 	"github.com/prebid/prebid-server/firstpartydata"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
@@ -66,7 +67,8 @@ func TestNewExchange(t *testing.T) {
 	}
 
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange)
+	vendorListFetcher := func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { return nil, nil }
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange)
 	for _, bidderName := range knownAdapters {
 		if _, ok := e.adapterMap[bidderName]; !ok {
 			t.Errorf("NewExchange produced an Exchange without bidder %s", bidderName)
@@ -114,7 +116,8 @@ func TestCharacterEscape(t *testing.T) {
 	}
 
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange)
+	vendorListFetcher := func(ctx context.Context, id uint16) (vendorlist.VendorList, error) { return nil, nil }
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange)
 
 	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
 	//liveAdapters []openrtb_ext.BidderName,
@@ -309,7 +312,7 @@ func TestDebugBehaviour(t *testing.T) {
 
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
-	e.gDPR = gdpr.AlwaysAllow{}
+	e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.categoriesFetcher = categoriesFetcher
 
@@ -331,10 +334,12 @@ func TestDebugBehaviour(t *testing.T) {
 		}
 
 		auctionRequest := AuctionRequest{
-			BidRequest: bidRequest,
-			Account:    config.Account{DebugAllow: test.debugData.accountLevelDebugAllowed},
-			UserSyncs:  &emptyUsersync{},
-			StartTime:  time.Now(),
+			BidRequest:             bidRequest,
+			Account:                config.Account{DebugAllow: test.debugData.accountLevelDebugAllowed},
+			UserSyncs:              &emptyUsersync{},
+			StartTime:              time.Now(),
+			GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+			TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 		}
 		if test.generateWarnings {
 			var errL []error
@@ -464,7 +469,7 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
 	e := new(exchange)
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
-	e.gDPR = gdpr.AlwaysAllow{}
+	e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.categoriesFetcher = categoriesFetcher
 
@@ -487,10 +492,12 @@ func TestTwoBiddersDebugDisabledAndEnabled(t *testing.T) {
 		bidRequest.Ext = json.RawMessage(`{"prebid":{"debug":true}}`)
 
 		auctionRequest := AuctionRequest{
-			BidRequest: bidRequest,
-			Account:    config.Account{DebugAllow: true},
-			UserSyncs:  &emptyUsersync{},
-			StartTime:  time.Now(),
+			BidRequest:             bidRequest,
+			Account:                config.Account{DebugAllow: true},
+			UserSyncs:              &emptyUsersync{},
+			StartTime:              time.Now(),
+			GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+			TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 		}
 
 		e.adapterMap = map[openrtb_ext.BidderName]adaptedBidder{
@@ -620,7 +627,7 @@ func TestOverrideWithCustomCurrency(t *testing.T) {
 	e := new(exchange)
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
-	e.gDPR = gdpr.AlwaysAllow{}
+	e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
 	e.currencyConverter = mockCurrencyConverter
 	e.categoriesFetcher = categoriesFetcher
 	e.bidIDGenerator = &mockBidIDGenerator{false, false}
@@ -659,9 +666,11 @@ func TestOverrideWithCustomCurrency(t *testing.T) {
 		mockBidRequest.Cur = []string{test.in.bidRequestCurrency}
 
 		auctionRequest := AuctionRequest{
-			BidRequest: mockBidRequest,
-			Account:    config.Account{},
-			UserSyncs:  &emptyUsersync{},
+			BidRequest:             mockBidRequest,
+			Account:                config.Account{},
+			UserSyncs:              &emptyUsersync{},
+			GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+			TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 		}
 
 		// Run test
@@ -710,11 +719,13 @@ func TestAdapterCurrency(t *testing.T) {
 	mockBidder := &mockBidder{}
 	mockBidder.On("MakeRequests", mock.Anything, mock.Anything).Return([]*adapters.RequestData(nil), []error(nil))
 
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
+
 	// Initialize Real Exchange
 	e := exchange{
 		cache:             &wellBehavedCache{},
 		me:                &metricsConf.NilMetricsEngine{},
-		gDPR:              gdpr.AlwaysAllow{},
+		vendorListFetcher: vendorListFetcher,
 		currencyConverter: currencyConverter,
 		categoriesFetcher: nilCategoryFetcher{},
 		bidIDGenerator:    &mockBidIDGenerator{false, false},
@@ -741,9 +752,11 @@ func TestAdapterCurrency(t *testing.T) {
 
 	// Run Auction
 	auctionRequest := AuctionRequest{
-		BidRequest: request,
-		Account:    config.Account{},
-		UserSyncs:  &emptyUsersync{},
+		BidRequest:             request,
+		Account:                config.Account{},
+		UserSyncs:              &emptyUsersync{},
+		GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+		TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 	}
 	response, err := e.HoldAuction(context.Background(), auctionRequest, &DebugLog{})
 	assert.NoError(t, err)
@@ -1104,7 +1117,7 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
-	e.gDPR = gdpr.AlwaysAllow{}
+	e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	e.categoriesFetcher = categoriesFetcher
 	e.bidIDGenerator = &mockBidIDGenerator{false, false}
@@ -1126,9 +1139,11 @@ func TestReturnCreativeEndToEnd(t *testing.T) {
 			mockBidRequest.Ext = test.inExt
 
 			auctionRequest := AuctionRequest{
-				BidRequest: mockBidRequest,
-				Account:    config.Account{},
-				UserSyncs:  &emptyUsersync{},
+				BidRequest:             mockBidRequest,
+				Account:                config.Account{},
+				UserSyncs:              &emptyUsersync{},
+				GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+				TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 			}
 
 			// Run test
@@ -1202,7 +1217,8 @@ func TestGetBidCacheInfoEndToEnd(t *testing.T) {
 	}
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 	pbc := pbc.NewClient(&http.Client{}, &cfg.CacheURL, &cfg.ExtCacheURL, testEngine)
-	e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange)
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
+	e := NewExchange(adapters, pbc, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange)
 	// 	3) Build all the parameters e.buildBidResponse(ctx.Background(), liveA... ) needs
 	liveAdapters := []openrtb_ext.BidderName{bidderName}
 
@@ -1394,7 +1410,7 @@ func TestBidReturnsCreative(t *testing.T) {
 	}
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
-	e.gDPR = gdpr.AlwaysAllow{}
+	e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	//Run tests
@@ -1556,7 +1572,8 @@ func TestBidResponseCurrency(t *testing.T) {
 	}
 
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange)
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange)
 
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
@@ -1695,7 +1712,8 @@ func TestBidResponseImpExtInfo(t *testing.T) {
 	cfg := &config.Configuration{Adapters: make(map[string]config.Adapter, 1)}
 	cfg.Adapters["appnexus"] = config.Adapter{Endpoint: "http://ib.adnxs.com"}
 
-	e := NewExchange(nil, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, gdpr.AlwaysAllow{}, nil, nilCategoryFetcher{}).(*exchange)
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
+	e := NewExchange(nil, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, nil, vendorListFetcher, nil, nilCategoryFetcher{}).(*exchange)
 
 	liveAdapters := make([]openrtb_ext.BidderName, 1)
 	liveAdapters[0] = "appnexus"
@@ -1788,13 +1806,16 @@ func TestRaceIntegration(t *testing.T) {
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
 
 	auctionRequest := AuctionRequest{
-		BidRequest: getTestBuildRequest(t),
-		Account:    config.Account{},
-		UserSyncs:  &emptyUsersync{},
+		BidRequest:             getTestBuildRequest(t),
+		Account:                config.Account{},
+		UserSyncs:              &emptyUsersync{},
+		GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+		TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 	}
 
 	debugLog := DebugLog{}
-	ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, &nilCategoryFetcher{}).(*exchange)
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
+	ex := NewExchange(adapters, &wellBehavedCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, &nilCategoryFetcher{}).(*exchange)
 	_, err = ex.HoldAuction(context.Background(), auctionRequest, &debugLog)
 	if err != nil {
 		t.Errorf("HoldAuction returned unexpected error: %v", err)
@@ -1886,7 +1907,8 @@ func TestPanicRecovery(t *testing.T) {
 	}
 
 	currencyConverter := currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
-	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, nilCategoryFetcher{}).(*exchange)
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
+	e := NewExchange(adapters, nil, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, nilCategoryFetcher{}).(*exchange)
 
 	chBids := make(chan *bidResponseWrapper, 1)
 	panicker := func(bidderRequest BidderRequest, conversions currency.Conversions) {
@@ -1959,7 +1981,8 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 		t.Errorf("Failed to create a category Fetcher: %v", error)
 	}
 
-	e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, gdpr.AlwaysAllow{}, currencyConverter, categoriesFetcher).(*exchange)
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
+	e := NewExchange(adapters, &mockCache{}, cfg, map[string]usersync.Syncer{}, &metricsConf.NilMetricsEngine{}, biddersInfo, vendorListFetcher, currencyConverter, categoriesFetcher).(*exchange)
 
 	e.adapterMap[openrtb_ext.BidderBeachfront] = panicingAdapter{}
 	e.adapterMap[openrtb_ext.BidderAppnexus] = panicingAdapter{}
@@ -1993,9 +2016,11 @@ func TestPanicRecoveryHighLevel(t *testing.T) {
 	}
 
 	auctionRequest := AuctionRequest{
-		BidRequest: request,
-		Account:    config.Account{},
-		UserSyncs:  &emptyUsersync{},
+		BidRequest:             request,
+		Account:                config.Account{},
+		UserSyncs:              &emptyUsersync{},
+		GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+		TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 	}
 	debugLog := DebugLog{}
 	_, err = e.HoldAuction(context.Background(), auctionRequest, &debugLog)
@@ -2083,6 +2108,9 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 			Enabled:         spec.GDPREnabled,
 			DefaultValue:    gdprDefaultValue,
 			EEACountriesMap: eeac,
+			TCF2: config.TCF2{
+				Enabled: spec.GDPREnabled,
+			},
 		},
 	}
 	bidIdGenerator := &mockBidIDGenerator{}
@@ -2104,7 +2132,9 @@ func runSpec(t *testing.T, filename string, spec *exchangeSpec) {
 			EventsEnabled: spec.EventsEnabled,
 			DebugAllow:    true,
 		},
-		UserSyncs: mockIdFetcher(spec.IncomingRequest.Usersyncs),
+		UserSyncs:              mockIdFetcher(spec.IncomingRequest.Usersyncs),
+		GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+		TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 	}
 	if spec.StartTime > 0 {
 		auctionRequest.StartTime = time.Unix(0, spec.StartTime*1e+6)
@@ -2222,6 +2252,8 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 		t.Fatalf("Failed to create a category Fetcher: %v", error)
 	}
 
+	vendorListFetcher := gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
+
 	bidderToSyncerKey := map[string]string{}
 	for _, bidderName := range openrtb_ext.CoreBidderNames() {
 		bidderToSyncerKey[string(bidderName)] = string(bidderName)
@@ -2237,7 +2269,7 @@ func newExchangeForTests(t *testing.T, filename string, expectations map[string]
 		me:                metricsConf.NewMetricsEngine(&config.Configuration{}, openrtb_ext.CoreBidderNames(), nil),
 		cache:             &wellBehavedCache{},
 		cacheTime:         0,
-		gDPR:              &permissionsMock{allowAllBidders: true},
+		vendorListFetcher: vendorListFetcher,
 		currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		gdprDefaultValue:  gdprDefaultValue,
 		privacyConfig:     privacyConfig,
@@ -3646,9 +3678,11 @@ func TestFPD(t *testing.T) {
 	ctx := context.Background()
 
 	auctionRequest := AuctionRequest{
-		BidRequest:     bidRequest,
-		UserSyncs:      &emptyUsersync{},
-		FirstPartyData: fpd,
+		BidRequest:             bidRequest,
+		UserSyncs:              &emptyUsersync{},
+		FirstPartyData:         fpd,
+		GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+		TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 	}
 
 	debugLog := &DebugLog{DebugOverride: true, DebugEnabledOrOverridden: true}
@@ -3676,7 +3710,7 @@ func TestStoredAuctionResponses(t *testing.T) {
 	e := new(exchange)
 	e.cache = &wellBehavedCache{}
 	e.me = &metricsConf.NilMetricsEngine{}
-	e.gDPR = gdpr.AlwaysAllow{}
+	e.vendorListFetcher = gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker)
 	e.categoriesFetcher = categoriesFetcher
 	e.bidIDGenerator = &mockBidIDGenerator{false, false}
 	e.currencyConverter = currency.NewRateConverter(&http.Client{}, "", time.Duration(0))
@@ -3730,6 +3764,8 @@ func TestStoredAuctionResponses(t *testing.T) {
 			Account:                config.Account{},
 			UserSyncs:              &emptyUsersync{},
 			StoredAuctionResponses: test.storedAuctionResp,
+			GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+			TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 		}
 
 		// Run test
@@ -4174,3 +4210,13 @@ func (m *mockBidder) MakeBids(internalRequest *openrtb2.BidRequest, externalRequ
 	args := m.Called(internalRequest, externalRequest, response)
 	return args.Get(0).(*adapters.BidderResponse), args.Get(1).([]error)
 }
+
+func mockGDPRPermissionsBuilder(cfg config.GDPR, tcf2Config gdpr.TCF2ConfigReader, vendorIDs map[openrtb_ext.BidderName]uint16, fetcher gdpr.VendorListFetcher) gdpr.Permissions {
+	return &permissionsMock{allowAllBidders: true}
+}
+
+func mockTCF2ConfigBuilder(hostConfig config.TCF2, accountConfig config.AccountGDPR) gdpr.TCF2ConfigReader {
+	return &config.TCF2{
+		Enabled: hostConfig.Enabled,
+	}
+}
diff --git a/exchange/targeting_test.go b/exchange/targeting_test.go
index d452d191..de92eed3 100644
--- a/exchange/targeting_test.go
+++ b/exchange/targeting_test.go
@@ -88,7 +88,7 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 		me:                &metricsConfig.NilMetricsEngine{},
 		cache:             &wellBehavedCache{},
 		cacheTime:         time.Duration(0),
-		gDPR:              gdpr.AlwaysAllow{},
+		vendorListFetcher: gdpr.NewVendorListFetcher(context.Background(), config.GDPR{}, &http.Client{}, gdpr.VendorListURLMaker),
 		currencyConverter: currency.NewRateConverter(&http.Client{}, "", time.Duration(0)),
 		gdprDefaultValue:  gdpr.SignalYes,
 		categoriesFetcher: categoriesFetcher,
@@ -108,9 +108,11 @@ func runTargetingAuction(t *testing.T, mockBids map[openrtb_ext.BidderName][]*op
 	}
 
 	auctionRequest := AuctionRequest{
-		BidRequest: req,
-		Account:    config.Account{},
-		UserSyncs:  &emptyUsersync{},
+		BidRequest:             req,
+		Account:                config.Account{},
+		UserSyncs:              &emptyUsersync{},
+		GDPRPermissionsBuilder: mockGDPRPermissionsBuilder,
+		TCF2ConfigBuilder:      mockTCF2ConfigBuilder,
 	}
 
 	debugLog := DebugLog{}
diff --git a/exchange/utils_test.go b/exchange/utils_test.go
index 7ba3d4f5..57905895 100644
--- a/exchange/utils_test.go
+++ b/exchange/utils_test.go
@@ -35,7 +35,7 @@ func (p *permissionsMock) BidderSyncAllowed(ctx context.Context, bidder openrtb_
 	return true, nil
 }
 
-func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdprSignal gdpr.Signal, consent string, weakVendorEnforcement bool, aliasGVLIDs map[string]uint16) (allowBidRequest bool, passGeo bool, passID bool, err error) {
+func (p *permissionsMock) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName, PublisherID string, gdpr gdpr.Signal, consent string, aliasGVLIDs map[string]uint16) (allowBidRequest bool, passGeo bool, passID bool, err error) {
 	if p.allowAllBidders {
 		return true, p.passGeo, p.passID, p.activitiesError
 	}
@@ -491,8 +491,7 @@ func TestCleanOpenRTBRequests(t *testing.T) {
 	for _, test := range testCases {
 		metricsMock := metrics.MetricsEngineMock{}
 		bidderToSyncerKey := map[string]string{}
-		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
-		bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &permissions, &metricsMock, gdpr.SignalNo, privacyConfig, nil)
+		bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &metricsMock, gdpr.SignalNo, gdpr.AlwaysAllow{}, privacyConfig, &config.TCF2{})
 		if test.hasError {
 			assert.NotNil(t, err, "Error shouldn't be nil")
 		} else {
@@ -549,8 +548,7 @@ func TestCleanOpenRTBRequestsWithFPD(t *testing.T) {
 	for _, test := range testCases {
 		metricsMock := metrics.MetricsEngineMock{}
 		bidderToSyncerKey := map[string]string{}
-		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
-		bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &permissions, &metricsMock, gdpr.SignalNo, config.Privacy{}, nil)
+		bidderRequests, _, err := cleanOpenRTBRequests(context.Background(), test.req, nil, bidderToSyncerKey, &metricsMock, gdpr.SignalNo, gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{})
 		assert.Empty(t, err, "No errors should be returned")
 		for _, bidderRequest := range bidderRequests {
 			bidderName := bidderRequest.BidderName
@@ -713,11 +711,11 @@ func TestCleanOpenRTBRequestsCCPA(t *testing.T) {
 			auctionReq,
 			nil,
 			bidderToSyncerKey,
-			&permissionsMock{allowAllBidders: true, passGeo: true, passID: true},
 			&metrics.MetricsEngineMock{},
 			gdpr.SignalNo,
+			gdpr.AlwaysAllow{},
 			privacyConfig,
-			nil)
+			&config.TCF2{})
 		result := bidderRequests[0]
 
 		assert.Nil(t, errs)
@@ -776,9 +774,8 @@ func TestCleanOpenRTBRequestsCCPAErrors(t *testing.T) {
 			},
 		}
 		bidderToSyncerKey := map[string]string{}
-		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
 		metrics := metrics.MetricsEngineMock{}
-		_, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil)
+		_, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, &reqExtStruct, bidderToSyncerKey, &metrics, gdpr.SignalNo, gdpr.AlwaysAllow{}, privacyConfig, &config.TCF2{})
 
 		assert.ElementsMatch(t, []error{test.expectError}, errs, test.description)
 	}
@@ -819,9 +816,8 @@ func TestCleanOpenRTBRequestsCOPPA(t *testing.T) {
 		}
 
 		bidderToSyncerKey := map[string]string{}
-		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
 		metrics := metrics.MetricsEngineMock{}
-		bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
+		bidderRequests, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &metrics, gdpr.SignalNo, &gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{})
 		result := bidderRequests[0]
 
 		assert.Nil(t, errs)
@@ -906,9 +902,8 @@ func TestCleanOpenRTBRequestsSChain(t *testing.T) {
 		}
 
 		bidderToSyncerKey := map[string]string{}
-		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
 		metrics := metrics.MetricsEngineMock{}
-		bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
+		bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{})
 		if test.hasError == true {
 			assert.NotNil(t, errs)
 			assert.Len(t, bidderRequests, 0)
@@ -964,9 +959,8 @@ func TestCleanOpenRTBRequestsBidderParams(t *testing.T) {
 		}
 
 		bidderToSyncerKey := map[string]string{}
-		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
 		metrics := metrics.MetricsEngineMock{}
-		bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
+		bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{})
 		if test.hasError == true {
 			assert.NotNil(t, errs)
 			assert.Len(t, bidderRequests, 0)
@@ -1646,9 +1640,8 @@ func TestCleanOpenRTBRequestsLMT(t *testing.T) {
 		}
 
 		bidderToSyncerKey := map[string]string{}
-		permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
 		metrics := metrics.MetricsEngineMock{}
-		results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, privacyConfig, nil)
+		results, privacyLabels, errs := cleanOpenRTBRequests(context.Background(), auctionReq, nil, bidderToSyncerKey, &metrics, gdpr.SignalNo, &gdpr.AlwaysAllow{}, privacyConfig, &config.TCF2{})
 		result := results[0]
 
 		assert.Nil(t, errs)
@@ -1835,10 +1828,9 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 
 		privacyConfig := config.Privacy{
 			GDPR: config.GDPR{
-				Enabled:      test.gdprHostEnabled,
 				DefaultValue: test.gdprDefaultValue,
 				TCF2: config.TCF2{
-					Enabled: true,
+					Enabled: test.gdprHostEnabled,
 				},
 			},
 		}
@@ -1867,11 +1859,11 @@ func TestCleanOpenRTBRequestsGDPR(t *testing.T) {
 			auctionReq,
 			nil,
 			bidderToSyncerKey,
-			&permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError},
 			&metrics.MetricsEngineMock{},
 			gdprDefaultValue,
+			&permissionsMock{allowAllBidders: true, passGeo: !test.gdprScrub, passID: !test.gdprScrub, activitiesError: test.permissionsError},
 			privacyConfig,
-			nil)
+			gdpr.NewTCF2Config(privacyConfig.GDPR.TCF2, accountConfig.GDPR))
 		result := results[0]
 
 		if test.expectError {
@@ -1931,10 +1923,9 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) {
 
 		privacyConfig := config.Privacy{
 			GDPR: config.GDPR{
-				Enabled:      test.gdprEnforced,
 				DefaultValue: "0",
 				TCF2: config.TCF2{
-					Enabled: true,
+					Enabled: test.gdprEnforced,
 				},
 			},
 		}
@@ -1960,11 +1951,11 @@ func TestCleanOpenRTBRequestsGDPRBlockBidRequest(t *testing.T) {
 			auctionReq,
 			nil,
 			bidderToSyncerKey,
-			&permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil},
 			&metricsMock,
 			gdpr.SignalNo,
+			&permissionsMock{allowedBidders: test.gdprAllowedBidders, passGeo: true, passID: true, activitiesError: nil},
 			privacyConfig,
-			nil)
+			&privacyConfig.GDPR.TCF2)
 
 		// extract bidder name from each request in the results
 		bidders := []openrtb_ext.BidderName{}
@@ -2534,9 +2525,8 @@ func TestCleanOpenRTBRequestsSChainMultipleBidders(t *testing.T) {
 		UserSyncs:  &emptyUsersync{},
 	}
 	bidderToSyncerKey := map[string]string{}
-	permissions := permissionsMock{allowAllBidders: true, passGeo: true, passID: true}
 	metrics := metrics.MetricsEngineMock{}
-	bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &permissions, &metrics, gdpr.SignalNo, config.Privacy{}, nil)
+	bidderRequests, _, errs := cleanOpenRTBRequests(context.Background(), auctionReq, extRequest, bidderToSyncerKey, &metrics, gdpr.SignalNo, gdpr.AlwaysAllow{}, config.Privacy{}, &config.TCF2{})
 
 	assert.Nil(t, errs)
 	assert.Len(t, bidderRequests, 2, "Bid request count is not 2")
diff --git a/gdpr/aggregated_config_test.go b/gdpr/aggregated_config_test.go
new file mode 100644
index 00000000..59de5dae
--- /dev/null
+++ b/gdpr/aggregated_config_test.go
@@ -0,0 +1,590 @@
+package gdpr
+
+import (
+	"testing"
+
+	"github.com/prebid/go-gdpr/consentconstants"
+
+	"github.com/prebid/prebid-server/config"
+	"github.com/prebid/prebid-server/openrtb_ext"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func MakeTCF2ConfigPurposeMaps(tcf2Config *tcf2Config) {
+	tcf2Config.AccountConfig.PurposeConfigs = map[consentconstants.Purpose]*config.AccountGDPRPurpose{
+		1:  &tcf2Config.AccountConfig.Purpose1,
+		2:  &tcf2Config.AccountConfig.Purpose2,
+		3:  &tcf2Config.AccountConfig.Purpose3,
+		4:  &tcf2Config.AccountConfig.Purpose4,
+		5:  &tcf2Config.AccountConfig.Purpose5,
+		6:  &tcf2Config.AccountConfig.Purpose6,
+		7:  &tcf2Config.AccountConfig.Purpose7,
+		8:  &tcf2Config.AccountConfig.Purpose8,
+		9:  &tcf2Config.AccountConfig.Purpose9,
+		10: &tcf2Config.AccountConfig.Purpose10,
+	}
+
+	tcf2Config.HostConfig.PurposeConfigs = map[consentconstants.Purpose]*config.TCF2Purpose{
+		1:  &tcf2Config.HostConfig.Purpose1,
+		2:  &tcf2Config.HostConfig.Purpose2,
+		3:  &tcf2Config.HostConfig.Purpose3,
+		4:  &tcf2Config.HostConfig.Purpose4,
+		5:  &tcf2Config.HostConfig.Purpose5,
+		6:  &tcf2Config.HostConfig.Purpose6,
+		7:  &tcf2Config.HostConfig.Purpose7,
+		8:  &tcf2Config.HostConfig.Purpose8,
+		9:  &tcf2Config.HostConfig.Purpose9,
+		10: &tcf2Config.HostConfig.Purpose10,
+	}
+}
+
+func TestIntegrationEnabled(t *testing.T) {
+	tests := []struct {
+		description            string
+		giveHostGDPREnabled    bool
+		giveAccountGDPREnabled *bool
+		wantIntegrationEnabled bool
+	}{
+		{
+			description:            "Set at account level - use account setting false",
+			giveHostGDPREnabled:    true,
+			giveAccountGDPREnabled: &[]bool{false}[0],
+			wantIntegrationEnabled: false,
+		},
+		{
+			description:            "Set at account level - use account setting true",
+			giveHostGDPREnabled:    false,
+			giveAccountGDPREnabled: &[]bool{true}[0],
+			wantIntegrationEnabled: true,
+		},
+		{
+			description:            "Not set at account level - use host setting false",
+			giveHostGDPREnabled:    false,
+			giveAccountGDPREnabled: nil,
+			wantIntegrationEnabled: false,
+		},
+		{
+			description:            "Not set at account level - use host setting true",
+			giveHostGDPREnabled:    true,
+			giveAccountGDPREnabled: nil,
+			wantIntegrationEnabled: true,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				Enabled: tt.giveAccountGDPREnabled,
+			},
+			HostConfig: config.TCF2{
+				Enabled: tt.giveHostGDPREnabled,
+			},
+		}
+
+		result := cfg.IntegrationEnabled(config.IntegrationTypeWeb)
+
+		assert.Equal(t, tt.wantIntegrationEnabled, result, tt.description)
+	}
+}
+
+func TestPurposeEnforced(t *testing.T) {
+	tests := []struct {
+		description                    string
+		givePurpose1HostEnforcement    string
+		givePurpose1AccountEnforcement string
+		givePurpose2HostEnforcement    string
+		givePurpose2AccountEnforcement string
+		givePurpose                    consentconstants.Purpose
+		wantEnforced                   bool
+	}{
+		{
+			description:                    "Purpose 1 set at account level - use account setting false",
+			givePurpose1HostEnforcement:    config.TCF2FullEnforcement,
+			givePurpose1AccountEnforcement: config.TCF2NoEnforcement,
+			givePurpose:                    1,
+			wantEnforced:                   false,
+		},
+		{
+			description:                    "Purpose 1 set at account level - use account setting true",
+			givePurpose1HostEnforcement:    config.TCF2NoEnforcement,
+			givePurpose1AccountEnforcement: config.TCF2FullEnforcement,
+			givePurpose:                    1,
+			wantEnforced:                   true,
+		},
+		{
+			description:                    "Purpose 1 not set at account level - use host setting false",
+			givePurpose1HostEnforcement:    config.TCF2NoEnforcement,
+			givePurpose1AccountEnforcement: "",
+			givePurpose:                    1,
+			wantEnforced:                   false,
+		},
+		{
+			description:                    "Purpose 1 not set at account level - use host setting true",
+			givePurpose1HostEnforcement:    config.TCF2FullEnforcement,
+			givePurpose1AccountEnforcement: "",
+			givePurpose:                    1,
+			wantEnforced:                   true,
+		},
+		{
+			description:                    "Some other purpose set at account level - use account setting true",
+			givePurpose2HostEnforcement:    config.TCF2NoEnforcement,
+			givePurpose2AccountEnforcement: config.TCF2FullEnforcement,
+			givePurpose:                    2,
+			wantEnforced:                   true,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				Purpose1: config.AccountGDPRPurpose{
+					EnforcePurpose: tt.givePurpose1AccountEnforcement,
+				},
+				Purpose2: config.AccountGDPRPurpose{
+					EnforcePurpose: tt.givePurpose2AccountEnforcement,
+				},
+			},
+			HostConfig: config.TCF2{
+				Purpose1: config.TCF2Purpose{
+					EnforcePurpose: tt.givePurpose1HostEnforcement,
+				},
+				Purpose2: config.TCF2Purpose{
+					EnforcePurpose: tt.givePurpose2HostEnforcement,
+				},
+			},
+		}
+		MakeTCF2ConfigPurposeMaps(&cfg)
+
+		result := cfg.PurposeEnforced(consentconstants.Purpose(tt.givePurpose))
+
+		assert.Equal(t, tt.wantEnforced, result, tt.description)
+	}
+}
+
+func TestPurposeEnforcingVendors(t *testing.T) {
+	tests := []struct {
+		description                  string
+		givePurpose1HostEnforcing    bool
+		givePurpose1AccountEnforcing *bool
+		givePurpose2HostEnforcing    bool
+		givePurpose2AccountEnforcing *bool
+		givePurpose                  consentconstants.Purpose
+		wantEnforcing                bool
+	}{
+		{
+			description:                  "Purpose 1 set at account level - use account setting false",
+			givePurpose1HostEnforcing:    true,
+			givePurpose1AccountEnforcing: &[]bool{false}[0],
+			givePurpose:                  1,
+			wantEnforcing:                false,
+		},
+		{
+			description:                  "Purpose 1 set at account level - use account setting true",
+			givePurpose1HostEnforcing:    false,
+			givePurpose1AccountEnforcing: &[]bool{true}[0],
+			givePurpose:                  1,
+			wantEnforcing:                true,
+		},
+		{
+			description:                  "Purpose 1 not set at account level - use host setting false",
+			givePurpose1HostEnforcing:    false,
+			givePurpose1AccountEnforcing: nil,
+			givePurpose:                  1,
+			wantEnforcing:                false,
+		},
+		{
+			description:                  "Purpose 1 not set at account level - use host setting true",
+			givePurpose1HostEnforcing:    true,
+			givePurpose1AccountEnforcing: nil,
+			givePurpose:                  1,
+			wantEnforcing:                true,
+		},
+		{
+			description:                  "Some other purpose set at account level - use account setting true",
+			givePurpose2HostEnforcing:    false,
+			givePurpose2AccountEnforcing: &[]bool{true}[0],
+			givePurpose:                  2,
+			wantEnforcing:                true,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				Purpose1: config.AccountGDPRPurpose{
+					EnforceVendors: tt.givePurpose1AccountEnforcing,
+				},
+				Purpose2: config.AccountGDPRPurpose{
+					EnforceVendors: tt.givePurpose2AccountEnforcing,
+				},
+			},
+			HostConfig: config.TCF2{
+				Purpose1: config.TCF2Purpose{
+					EnforceVendors: tt.givePurpose1HostEnforcing,
+				},
+				Purpose2: config.TCF2Purpose{
+					EnforceVendors: tt.givePurpose2HostEnforcing,
+				},
+			},
+		}
+		MakeTCF2ConfigPurposeMaps(&cfg)
+
+		result := cfg.PurposeEnforcingVendors(consentconstants.Purpose(tt.givePurpose))
+
+		assert.Equal(t, tt.wantEnforcing, result, tt.description)
+	}
+}
+
+func TestPurposeVendorException(t *testing.T) {
+	tests := []struct {
+		description                           string
+		givePurpose1HostVendorExceptionMap    map[openrtb_ext.BidderName]struct{}
+		givePurpose1AccountVendorExceptionMap map[openrtb_ext.BidderName]struct{}
+		givePurpose2HostVendorExceptionMap    map[openrtb_ext.BidderName]struct{}
+		givePurpose2AccountVendorExceptionMap map[openrtb_ext.BidderName]struct{}
+		givePurpose                           consentconstants.Purpose
+		giveBidder                            openrtb_ext.BidderName
+		wantVendorException                   bool
+	}{
+		{
+			description:                           "Purpose 1 exception list set at account level - vendor found",
+			givePurpose1HostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{},
+			givePurpose1AccountVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}},
+			givePurpose:                           1,
+			giveBidder:                            "appnexus",
+			wantVendorException:                   true,
+		},
+		{
+			description:                           "Purpose 1 exception list set at account level - vendor not found",
+			givePurpose1HostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{},
+			givePurpose1AccountVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}},
+			givePurpose:                           1,
+			giveBidder:                            "appnexus",
+			wantVendorException:                   false,
+		},
+		{
+			description:                           "Purpose 1 exception list not set at account level - vendor found in host list",
+			givePurpose1HostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}},
+			givePurpose1AccountVendorExceptionMap: nil,
+			givePurpose:                           1,
+			giveBidder:                            "appnexus",
+			wantVendorException:                   true,
+		},
+		{
+			description:                           "Purpose 1 exception list not set at account level - vendor not found in host list",
+			givePurpose1HostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{"rubicon": {}},
+			givePurpose1AccountVendorExceptionMap: nil,
+			givePurpose:                           1,
+			giveBidder:                            "appnexus",
+			wantVendorException:                   false,
+		},
+		{
+			description:                           "Purpose 1 exception list not set at account level or host level - vendor not found",
+			givePurpose1HostVendorExceptionMap:    nil,
+			givePurpose1AccountVendorExceptionMap: nil,
+			givePurpose:                           1,
+			giveBidder:                            "appnexus",
+			wantVendorException:                   false,
+		},
+		{
+			description:                           "Some other purpose exception list set at account level - vendor found",
+			givePurpose2HostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{},
+			givePurpose2AccountVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}},
+			givePurpose:                           2,
+			giveBidder:                            "appnexus",
+			wantVendorException:                   true,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				Purpose1: config.AccountGDPRPurpose{
+					VendorExceptionMap: tt.givePurpose1AccountVendorExceptionMap,
+				},
+				Purpose2: config.AccountGDPRPurpose{
+					VendorExceptionMap: tt.givePurpose2AccountVendorExceptionMap,
+				},
+			},
+			HostConfig: config.TCF2{
+				Purpose1: config.TCF2Purpose{
+					VendorExceptionMap: tt.givePurpose1HostVendorExceptionMap,
+				},
+				Purpose2: config.TCF2Purpose{
+					VendorExceptionMap: tt.givePurpose2HostVendorExceptionMap,
+				},
+			},
+		}
+		MakeTCF2ConfigPurposeMaps(&cfg)
+
+		result := cfg.PurposeVendorException(consentconstants.Purpose(tt.givePurpose), tt.giveBidder)
+
+		assert.Equal(t, tt.wantVendorException, result, tt.description)
+	}
+}
+
+func TestFeatureOneEnforced(t *testing.T) {
+	tests := []struct {
+		description          string
+		giveHostEnforcing    bool
+		giveAccountEnforcing *bool
+		wantEnforcing        bool
+		wantEnabled          bool
+	}{
+		{
+			description:          "Feature 1 enforced set at account level - use account setting false",
+			giveHostEnforcing:    true,
+			giveAccountEnforcing: &[]bool{false}[0],
+			wantEnforcing:        false,
+		},
+		{
+			description:          "Feature 1 enforced set at account level - use account setting true",
+			giveHostEnforcing:    false,
+			giveAccountEnforcing: &[]bool{true}[0],
+			wantEnforcing:        true,
+		},
+		{
+			description:          "Feature 1 enforced not set at account level - use host setting false",
+			giveHostEnforcing:    false,
+			giveAccountEnforcing: nil,
+			wantEnforcing:        false,
+		},
+		{
+			description:          "Feature 1 enforced not set at account level - use host setting true",
+			giveHostEnforcing:    true,
+			giveAccountEnforcing: nil,
+			wantEnforcing:        true,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				SpecialFeature1: config.AccountGDPRSpecialFeature{
+					Enforce: tt.giveAccountEnforcing,
+				},
+			},
+			HostConfig: config.TCF2{
+				SpecialFeature1: config.TCF2SpecialFeature{
+					Enforce: tt.giveHostEnforcing,
+				},
+			},
+		}
+
+		result := cfg.FeatureOneEnforced()
+
+		assert.Equal(t, tt.wantEnforcing, result, tt.description)
+	}
+}
+
+func TestFeatureOneVendorException(t *testing.T) {
+	tests := []struct {
+		description                   string
+		giveHostVendorExceptionMap    map[openrtb_ext.BidderName]struct{}
+		giveAccountVendorExceptionMap map[openrtb_ext.BidderName]struct{}
+		giveBidder                    openrtb_ext.BidderName
+		wantVendorException           bool
+	}{
+		{
+			description:                   "Feature 1 exception list set at account level - vendor found",
+			giveHostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{},
+			giveAccountVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}},
+			giveBidder:                    "appnexus",
+			wantVendorException:           true,
+		},
+		{
+			description:                   "Feature 1 exception list set at account level - vendor not found",
+			giveHostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{},
+			giveAccountVendorExceptionMap: map[openrtb_ext.BidderName]struct{}{"rubicon": {}},
+			giveBidder:                    "appnexus",
+			wantVendorException:           false,
+		},
+		{
+			description:                   "Feature 1 exception list not set at account level - vendor found in host list",
+			giveHostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{"appnexus": {}, "rubicon": {}},
+			giveAccountVendorExceptionMap: nil,
+			giveBidder:                    "appnexus",
+			wantVendorException:           true,
+		},
+		{
+			description:                   "Feature 1 exception list not set at account level - vendor not found in host list",
+			giveHostVendorExceptionMap:    map[openrtb_ext.BidderName]struct{}{"rubicon": {}},
+			giveAccountVendorExceptionMap: nil,
+			giveBidder:                    "appnexus",
+			wantVendorException:           false,
+		},
+		{
+			description:                   "Feature 1 exception list not set at account level or host level - vendor not found",
+			giveHostVendorExceptionMap:    nil,
+			giveAccountVendorExceptionMap: nil,
+			giveBidder:                    "appnexus",
+			wantVendorException:           false,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				SpecialFeature1: config.AccountGDPRSpecialFeature{
+					VendorExceptionMap: tt.giveAccountVendorExceptionMap,
+				},
+			},
+			HostConfig: config.TCF2{
+				SpecialFeature1: config.TCF2SpecialFeature{
+					VendorExceptionMap: tt.giveHostVendorExceptionMap,
+				},
+			},
+		}
+
+		result := cfg.FeatureOneVendorException(tt.giveBidder)
+
+		assert.Equal(t, tt.wantVendorException, result, tt.description)
+	}
+}
+
+func TestPurposeOneTreatmentEnabled(t *testing.T) {
+	tests := []struct {
+		description        string
+		giveHostEnabled    bool
+		giveAccountEnabled *bool
+		wantEnabled        bool
+	}{
+		{
+			description:        "Purpose 1 treatment enabled set at account level - use account setting false",
+			giveHostEnabled:    true,
+			giveAccountEnabled: &[]bool{false}[0],
+			wantEnabled:        false,
+		},
+		{
+			description:        "Purpose 1 treatment enabled set at account level - use account setting true",
+			giveHostEnabled:    false,
+			giveAccountEnabled: &[]bool{true}[0],
+			wantEnabled:        true,
+		},
+		{
+			description:        "Purpose 1 treatment enabled not set at account level - use host setting false",
+			giveHostEnabled:    false,
+			giveAccountEnabled: nil,
+			wantEnabled:        false,
+		},
+		{
+			description:        "Purpose 1 treatment enabled not set at account level - use host setting true",
+			giveHostEnabled:    true,
+			giveAccountEnabled: nil,
+			wantEnabled:        true,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				PurposeOneTreatment: config.AccountGDPRPurposeOneTreatment{
+					Enabled: tt.giveAccountEnabled,
+				},
+			},
+			HostConfig: config.TCF2{
+				PurposeOneTreatment: config.TCF2PurposeOneTreatment{
+					Enabled: tt.giveHostEnabled,
+				},
+			},
+		}
+
+		result := cfg.PurposeOneTreatmentEnabled()
+
+		assert.Equal(t, tt.wantEnabled, result, tt.description)
+	}
+}
+
+func TestPurposeOneTreatmentAllowed(t *testing.T) {
+	tests := []struct {
+		description              string
+		giveHostAccessAllowed    bool
+		giveAccountAccessAllowed *bool
+		wantAccessAllowed        bool
+	}{
+		{
+			description:              "Purpose 1 treatment access allowed set at account level - use account setting false",
+			giveHostAccessAllowed:    true,
+			giveAccountAccessAllowed: &[]bool{false}[0],
+			wantAccessAllowed:        false,
+		},
+		{
+			description:              "Purpose 1 treatment access allowed set at account level - use account setting true",
+			giveHostAccessAllowed:    false,
+			giveAccountAccessAllowed: &[]bool{true}[0],
+			wantAccessAllowed:        true,
+		},
+		{
+			description:              "Purpose 1 treatment access allowed not set at account level - use host setting false",
+			giveHostAccessAllowed:    false,
+			giveAccountAccessAllowed: nil,
+			wantAccessAllowed:        false,
+		},
+		{
+			description:              "Purpose 1 treatment access allowed not set at account level - use host setting true",
+			giveHostAccessAllowed:    true,
+			giveAccountAccessAllowed: nil,
+			wantAccessAllowed:        true,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				PurposeOneTreatment: config.AccountGDPRPurposeOneTreatment{
+					AccessAllowed: tt.giveAccountAccessAllowed,
+				},
+			},
+			HostConfig: config.TCF2{
+				PurposeOneTreatment: config.TCF2PurposeOneTreatment{
+					AccessAllowed: tt.giveHostAccessAllowed,
+				},
+			},
+		}
+
+		result := cfg.PurposeOneTreatmentAccessAllowed()
+
+		assert.Equal(t, tt.wantAccessAllowed, result, tt.description)
+	}
+}
+
+func TestBasicEnforcementVendor(t *testing.T) {
+	tests := []struct {
+		description                          string
+		giveAccountBasicEnforcementVendorMap map[string]struct{}
+		giveBidder                           openrtb_ext.BidderName
+		wantBasicEnforcement                 bool
+	}{
+		{
+			description:                          "Basic enforcement vendor list set at account level - vendor found",
+			giveAccountBasicEnforcementVendorMap: map[string]struct{}{"appnexus": {}, "rubicon": {}},
+			giveBidder:                           "appnexus",
+			wantBasicEnforcement:                 true,
+		},
+		{
+			description:                          "Basic enforcement vendor list set at account level - vendor not found",
+			giveAccountBasicEnforcementVendorMap: map[string]struct{}{"rubicon": {}},
+			giveBidder:                           "appnexus",
+			wantBasicEnforcement:                 false,
+		},
+		{
+			description:                          "Basic enforcement vendor list not set at account level - vendor not found",
+			giveAccountBasicEnforcementVendorMap: nil,
+			giveBidder:                           "appnexus",
+			wantBasicEnforcement:                 false,
+		},
+	}
+
+	for _, tt := range tests {
+		cfg := tcf2Config{
+			AccountConfig: config.AccountGDPR{
+				BasicEnforcementVendorsMap: tt.giveAccountBasicEnforcementVendorMap,
+			},
+		}
+
+		result := cfg.BasicEnforcementVendor(tt.giveBidder)
+
+		assert.Equal(t, tt.wantBasicEnforcement, result, tt.description)
+	}
+}
diff --git a/gdpr/gdpr_test.go b/gdpr/gdpr_test.go
index 9e4f0940..906a27e2 100644
--- a/gdpr/gdpr_test.go
+++ b/gdpr/gdpr_test.go
@@ -2,9 +2,9 @@ package gdpr
 
 import (
 	"context"
-	"net/http"
 	"testing"
 
+	"github.com/prebid/go-gdpr/vendorlist"
 	"github.com/prebid/prebid-server/config"
 	"github.com/prebid/prebid-server/openrtb_ext"
 	"github.com/stretchr/testify/assert"
@@ -41,8 +41,11 @@ func TestNewPermissions(t *testing.T) {
 			HostVendorID: tt.hostVendorID,
 		}
 		vendorIDs := map[openrtb_ext.BidderName]uint16{}
+		vendorListFetcher := func(ctx context.Context, id uint16) (vendorlist.VendorList, error) {
+			return nil, nil
+		}
 
-		perms := NewPermissions(context.Background(), config, vendorIDs, &http.Client{})
+		perms := NewPermissions(config, &tcf2Config{}, vendorIDs, vendorListFetcher)
 
 		assert.IsType(t, tt.wantType, perms, tt.description)
 	}
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index f5bdbaf0..025a8b76 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -17,16 +17,13 @@ import (
 
 func TestDisallowOnEmptyConsent(t *testing.T) {
 	perms := permissionsImpl{
-		cfg: config.GDPR{
-			HostVendorID: 3,
-			DefaultValue: "0",
-		},
-		gdprDefaultValue: SignalNo,
+		cfg:              &tcf2Config{},
+		fetchVendorList:  failedListFetcher,
+		gdprDefaultValue: "0",
+		hostVendorID:     3,
 		vendorIDs:        nil,
-		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf2SpecVersion: failedListFetcher,
-		},
 	}
+
 	allowSync, err := perms.BidderSyncAllowed(context.Background(), openrtb_ext.BidderAppnexus, SignalYes, "")
 	assertBoolsEqual(t, false, allowSync)
 	assertNilErr(t, err)
@@ -60,27 +57,27 @@ func TestAllowedSyncs(t *testing.T) {
 		},
 	})
 
-	perms := permissionsImpl{
-		cfg: config.GDPR{
-			HostVendorID: 2,
-			TCF2: config.TCF2{
-				Purpose1: config.TCF2Purpose{
-					EnforcePurpose: config.TCF2FullEnforcement,
-					EnforceVendors: true,
-				},
+	tcf2AggConfig := tcf2Config{
+		HostConfig: config.TCF2{
+			Purpose1: config.TCF2Purpose{
+				EnforcePurpose: config.TCF2FullEnforcement,
+				EnforceVendors: true,
 			},
 		},
+	}
+	tcf2AggConfig.HostConfig.PurposeConfigs = map[consentconstants.Purpose]*config.TCF2Purpose{
+		consentconstants.Purpose(1): &tcf2AggConfig.HostConfig.Purpose1,
+	}
+
+	perms := permissionsImpl{
+		cfg:          &tcf2AggConfig,
+		hostVendorID: 2,
 		vendorIDs: map[openrtb_ext.BidderName]uint16{
 			openrtb_ext.BidderAppnexus: 2,
 		},
-		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
-				1: parseVendorListDataV2(t, vendorListData),
-			}),
-		},
-	}
-	perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
-		consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1,
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
+			1: parseVendorListDataV2(t, vendorListData),
+		}),
 	}
 
 	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2AndPurpose1Consent)
@@ -104,23 +101,26 @@ func TestProhibitedPurposes(t *testing.T) {
 		},
 	})
 
-	perms := permissionsImpl{
-		cfg: config.GDPR{
-			HostVendorID: 2,
-			TCF2: config.TCF2{
-				Purpose1: config.TCF2Purpose{
-					EnforcePurpose: config.TCF2FullEnforcement,
-				},
+	tcf2AggConfig := tcf2Config{
+		HostConfig: config.TCF2{
+			Purpose1: config.TCF2Purpose{
+				EnforcePurpose: config.TCF2FullEnforcement,
 			},
 		},
+	}
+	tcf2AggConfig.HostConfig.PurposeConfigs = map[consentconstants.Purpose]*config.TCF2Purpose{
+		consentconstants.Purpose(1): &tcf2AggConfig.HostConfig.Purpose1,
+	}
+
+	perms := permissionsImpl{
+		cfg:          &tcf2AggConfig,
+		hostVendorID: 2,
 		vendorIDs: map[openrtb_ext.BidderName]uint16{
 			openrtb_ext.BidderAppnexus: 2,
 		},
-		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
-				1: parseVendorListDataV2(t, vendorListData),
-			}),
-		},
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
+			1: parseVendorListDataV2(t, vendorListData),
+		}),
 	}
 
 	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, vendor2NoPurpose1Consent)
@@ -143,27 +143,28 @@ func TestProhibitedVendors(t *testing.T) {
 			},
 		},
 	})
-	perms := permissionsImpl{
-		cfg: config.GDPR{
-			HostVendorID: 2,
-			TCF2: config.TCF2{
-				Purpose1: config.TCF2Purpose{
-					EnforcePurpose: config.TCF2FullEnforcement,
-					EnforceVendors: true,
-				},
+
+	tcf2AggConfig := tcf2Config{
+		HostConfig: config.TCF2{
+			Purpose1: config.TCF2Purpose{
+				EnforcePurpose: config.TCF2FullEnforcement,
+				EnforceVendors: true,
 			},
 		},
+	}
+	tcf2AggConfig.HostConfig.PurposeConfigs = map[consentconstants.Purpose]*config.TCF2Purpose{
+		consentconstants.Purpose(1): &tcf2AggConfig.HostConfig.Purpose1,
+	}
+
+	perms := permissionsImpl{
+		cfg:          &tcf2AggConfig,
+		hostVendorID: 2,
 		vendorIDs: map[openrtb_ext.BidderName]uint16{
 			openrtb_ext.BidderAppnexus: 2,
 		},
-		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
-				1: parseVendorListDataV2(t, vendorListData),
-			}),
-		},
-	}
-	perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
-		consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1,
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
+			1: parseVendorListDataV2(t, vendorListData),
+		}),
 	}
 
 	allowSync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, purpose1NoVendorConsent)
@@ -177,12 +178,8 @@ func TestProhibitedVendors(t *testing.T) {
 
 func TestMalformedConsent(t *testing.T) {
 	perms := permissionsImpl{
-		cfg: config.GDPR{
-			HostVendorID: 2,
-		},
-		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf2SpecVersion: listFetcher(nil),
-		},
+		hostVendorID:    2,
+		fetchVendorList: listFetcher(nil),
 	}
 
 	sync, err := perms.HostCookiesAllowed(context.Background(), SignalYes, "BON")
@@ -320,41 +317,24 @@ func TestAllowActivities(t *testing.T) {
 			},
 		},
 	})
+	tcf2AggConfig := allPurposesEnabledTCF2Config()
 
 	perms := permissionsImpl{
-		cfg: config.GDPR{
-			HostVendorID:            2,
-			NonStandardPublisherMap: map[string]struct{}{"appNexusAppID": {}},
-			TCF2: config.TCF2{
-				Enabled: true,
-				Purpose2: config.TCF2Purpose{
-					EnforcePurpose: config.TCF2FullEnforcement,
-					EnforceVendors: true,
-				},
-			},
-		},
+		cfg:                   &tcf2AggConfig,
+		hostVendorID:          2,
+		nonStandardPublishers: map[string]struct{}{"appNexusAppID": {}},
 		vendorIDs: map[openrtb_ext.BidderName]uint16{
 			openrtb_ext.BidderAppnexus: 2,
 		},
-		fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
-				1: parseVendorListDataV2(t, vendorListData),
-			}),
-		},
-	}
-	perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
-		consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2,
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
+			1: parseVendorListDataV2(t, vendorListData),
+		}),
 	}
 
 	for _, tt := range tests {
-		perms.cfg.DefaultValue = tt.gdprDefaultValue
-		if tt.gdprDefaultValue == "0" {
-			perms.gdprDefaultValue = SignalNo
-		} else {
-			perms.gdprDefaultValue = SignalYes
-		}
+		perms.gdprDefaultValue = tt.gdprDefaultValue
 
-		_, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderCoreName, tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.weakVendorEnforcement, tt.aliasGVLIDs)
+		_, _, passID, err := perms.AuctionActivitiesAllowed(context.Background(), tt.bidderCoreName, tt.bidderName, tt.publisherID, tt.gdpr, tt.consent, tt.aliasGVLIDs)
 
 		assert.Nil(t, err, tt.description)
 		assert.Equal(t, tt.passID, passID, tt.description)
@@ -400,37 +380,48 @@ func buildVendorList34() vendorList {
 	}
 }
 
-func allPurposesEnabledPermissions() (perms permissionsImpl) {
-	perms = permissionsImpl{
-		cfg: config.GDPR{
-			HostVendorID: 2,
-			TCF2: config.TCF2{
-				Enabled:         true,
-				Purpose1:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose2:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose3:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose4:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose5:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose6:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose7:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose8:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose9:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				Purpose10:       config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
-				SpecialFeature1: config.TCF2SpecialFeature{Enforce: true},
+func allPurposesEnabledTCF2Config() (TCF2AggConfig tcf2Config) {
+	TCF2AggConfig = tcf2Config{
+		HostConfig: config.TCF2{
+			Enabled:         true,
+			Purpose1:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose2:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose3:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose4:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose5:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose6:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose7:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose8:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose9:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			Purpose10:       config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, EnforceVendors: true},
+			SpecialFeature1: config.TCF2SpecialFeature{Enforce: true},
+		},
+		AccountConfig: config.AccountGDPR{
+			PurposeConfigs: map[consentconstants.Purpose]*config.AccountGDPRPurpose{
+				consentconstants.Purpose(1):  {},
+				consentconstants.Purpose(2):  {},
+				consentconstants.Purpose(3):  {},
+				consentconstants.Purpose(4):  {},
+				consentconstants.Purpose(5):  {},
+				consentconstants.Purpose(6):  {},
+				consentconstants.Purpose(7):  {},
+				consentconstants.Purpose(8):  {},
+				consentconstants.Purpose(9):  {},
+				consentconstants.Purpose(10): {},
 			},
 		},
 	}
-	perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
-		consentconstants.Purpose(1):  perms.cfg.TCF2.Purpose1,
-		consentconstants.Purpose(2):  perms.cfg.TCF2.Purpose2,
-		consentconstants.Purpose(3):  perms.cfg.TCF2.Purpose3,
-		consentconstants.Purpose(4):  perms.cfg.TCF2.Purpose4,
-		consentconstants.Purpose(5):  perms.cfg.TCF2.Purpose5,
-		consentconstants.Purpose(6):  perms.cfg.TCF2.Purpose6,
-		consentconstants.Purpose(7):  perms.cfg.TCF2.Purpose7,
-		consentconstants.Purpose(8):  perms.cfg.TCF2.Purpose8,
-		consentconstants.Purpose(9):  perms.cfg.TCF2.Purpose9,
-		consentconstants.Purpose(10): perms.cfg.TCF2.Purpose10,
+	TCF2AggConfig.HostConfig.PurposeConfigs = map[consentconstants.Purpose]*config.TCF2Purpose{
+		consentconstants.Purpose(1):  &TCF2AggConfig.HostConfig.Purpose1,
+		consentconstants.Purpose(2):  &TCF2AggConfig.HostConfig.Purpose2,
+		consentconstants.Purpose(3):  &TCF2AggConfig.HostConfig.Purpose3,
+		consentconstants.Purpose(4):  &TCF2AggConfig.HostConfig.Purpose4,
+		consentconstants.Purpose(5):  &TCF2AggConfig.HostConfig.Purpose5,
+		consentconstants.Purpose(6):  &TCF2AggConfig.HostConfig.Purpose6,
+		consentconstants.Purpose(7):  &TCF2AggConfig.HostConfig.Purpose7,
+		consentconstants.Purpose(8):  &TCF2AggConfig.HostConfig.Purpose8,
+		consentconstants.Purpose(9):  &TCF2AggConfig.HostConfig.Purpose9,
+		consentconstants.Purpose(10): &TCF2AggConfig.HostConfig.Purpose10,
 	}
 	return
 }
@@ -450,16 +441,17 @@ type testDef struct {
 func TestAllowActivitiesGeoAndID(t *testing.T) {
 	vendorListData := MarshalVendorList(buildVendorList34())
 
-	perms := allPurposesEnabledPermissions()
-	perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
-		openrtb_ext.BidderAppnexus:        2,
-		openrtb_ext.BidderPubmatic:        6,
-		openrtb_ext.BidderRubicon:         8,
-		openrtb_ext.BidderOpenx:           20,
-		openrtb_ext.BidderAudienceNetwork: 55,
-	}
-	perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-		tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+	perms := permissionsImpl{
+		hostVendorID:          2,
+		nonStandardPublishers: map[string]struct{}{"appNexusAppID": {}},
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus:        2,
+			openrtb_ext.BidderPubmatic:        6,
+			openrtb_ext.BidderRubicon:         8,
+			openrtb_ext.BidderOpenx:           20,
+			openrtb_ext.BidderAudienceNetwork: 55,
+		},
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
 			34: parseVendorListDataV2(t, vendorListData),
 			74: parseVendorListDataV2(t, vendorListData),
 		}),
@@ -549,7 +541,14 @@ func TestAllowActivitiesGeoAndID(t *testing.T) {
 	}
 
 	for _, td := range testDefs {
-		allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement, td.aliasGVLIDs)
+
+		tcf2AggConfig := allPurposesEnabledTCF2Config()
+		if td.weakVendorEnforcement {
+			tcf2AggConfig.AccountConfig.BasicEnforcementVendorsMap = map[string]struct{}{string(td.bidder): {}}
+		}
+		perms.cfg = &tcf2AggConfig
+
+		allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.aliasGVLIDs)
 		assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description)
 		assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description)
@@ -559,22 +558,24 @@ func TestAllowActivitiesGeoAndID(t *testing.T) {
 
 func TestAllowActivitiesWhitelist(t *testing.T) {
 	vendorListData := MarshalVendorList(buildVendorList34())
+	tcf2AggConfig := allPurposesEnabledTCF2Config()
 
-	perms := allPurposesEnabledPermissions()
-	perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
-		openrtb_ext.BidderAppnexus: 2,
-		openrtb_ext.BidderPubmatic: 6,
-		openrtb_ext.BidderRubicon:  8,
-	}
-	perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-		tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+	perms := permissionsImpl{
+		cfg:                   &tcf2AggConfig,
+		hostVendorID:          2,
+		nonStandardPublishers: map[string]struct{}{"appNexusAppID": {}},
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
 			34: parseVendorListDataV2(t, vendorListData),
 		}),
 	}
 
 	// Assert that an item that otherwise would not be allowed PI access, gets approved because it is found in the GDPR.NonStandardPublishers array
-	perms.cfg.NonStandardPublisherMap = map[string]struct{}{"appNexusAppID": {}}
-	_, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", false, map[string]uint16{})
+	_, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), openrtb_ext.BidderAppnexus, openrtb_ext.BidderAppnexus, "appNexusAppID", SignalYes, "COzTVhaOzTVhaGvAAAENAiCIAP_AAH_AAAAAAEEUACCKAAA", map[string]uint16{})
 	assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed")
 	assert.EqualValuesf(t, true, passGeo, "PassGeo failure")
 	assert.EqualValuesf(t, true, passID, "PassID failure")
@@ -582,15 +583,17 @@ func TestAllowActivitiesWhitelist(t *testing.T) {
 
 func TestAllowActivitiesPubRestrict(t *testing.T) {
 	vendorListData := MarshalVendorList(buildVendorList34())
+	tcf2AggConfig := allPurposesEnabledTCF2Config()
 
-	perms := allPurposesEnabledPermissions()
-	perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
-		openrtb_ext.BidderAppnexus: 2,
-		openrtb_ext.BidderPubmatic: 32,
-		openrtb_ext.BidderRubicon:  8,
-	}
-	perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-		tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+	perms := permissionsImpl{
+		cfg:          &tcf2AggConfig,
+		hostVendorID: 2,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 32,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
 			15: parseVendorListDataV2(t, vendorListData),
 		}),
 	}
@@ -637,7 +640,7 @@ func TestAllowActivitiesPubRestrict(t *testing.T) {
 	}
 
 	for _, td := range testDefs {
-		_, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement, td.aliasGVLIDs)
+		_, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.aliasGVLIDs)
 		assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description)
 		assert.EqualValuesf(t, td.passID, passID, "PassID failure on %s", td.description)
@@ -646,15 +649,17 @@ func TestAllowActivitiesPubRestrict(t *testing.T) {
 
 func TestAllowSync(t *testing.T) {
 	vendorListData := MarshalVendorList(buildVendorList34())
+	tcf2AggConfig := allPurposesEnabledTCF2Config()
 
-	perms := allPurposesEnabledPermissions()
-	perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
-		openrtb_ext.BidderAppnexus: 2,
-		openrtb_ext.BidderPubmatic: 6,
-		openrtb_ext.BidderRubicon:  8,
-	}
-	perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-		tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+	perms := permissionsImpl{
+		cfg:          &tcf2AggConfig,
+		hostVendorID: 2,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
 			34: parseVendorListDataV2(t, vendorListData),
 		}),
 	}
@@ -674,15 +679,17 @@ func TestProhibitedPurposeSync(t *testing.T) {
 	vendorList34.Vendors["8"].Purposes = []int{7}
 	vendorListData := MarshalVendorList(vendorList34)
 
-	perms := allPurposesEnabledPermissions()
-	perms.cfg.HostVendorID = 8
-	perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
-		openrtb_ext.BidderAppnexus: 2,
-		openrtb_ext.BidderPubmatic: 6,
-		openrtb_ext.BidderRubicon:  8,
-	}
-	perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-		tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+	tcf2AggConfig := allPurposesEnabledTCF2Config()
+
+	perms := permissionsImpl{
+		cfg:          &tcf2AggConfig,
+		hostVendorID: 8,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+		},
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
 			34: parseVendorListDataV2(t, vendorListData),
 		}),
 	}
@@ -699,17 +706,18 @@ func TestProhibitedPurposeSync(t *testing.T) {
 
 func TestProhibitedVendorSync(t *testing.T) {
 	vendorListData := MarshalVendorList(buildVendorList34())
+	tcf2AggConfig := allPurposesEnabledTCF2Config()
 
-	perms := allPurposesEnabledPermissions()
-	perms.cfg.HostVendorID = 10
-	perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
-		openrtb_ext.BidderAppnexus: 2,
-		openrtb_ext.BidderPubmatic: 6,
-		openrtb_ext.BidderRubicon:  8,
-		openrtb_ext.BidderOpenx:    10,
-	}
-	perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-		tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+	perms := permissionsImpl{
+		cfg:          &tcf2AggConfig,
+		hostVendorID: 10,
+		vendorIDs: map[openrtb_ext.BidderName]uint16{
+			openrtb_ext.BidderAppnexus: 2,
+			openrtb_ext.BidderPubmatic: 6,
+			openrtb_ext.BidderRubicon:  8,
+			openrtb_ext.BidderOpenx:    10,
+		},
+		fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
 			34: parseVendorListDataV2(t, vendorListData),
 		}),
 	}
@@ -790,7 +798,6 @@ func TestAllowActivitiesBidRequests(t *testing.T) {
 		allowBid               bool
 		passGeo                bool
 		passID                 bool
-		weakVendorEnforcement  bool
 		aliasGVLIDs            map[string]uint16
 	}{
 		{
@@ -899,23 +906,28 @@ func TestAllowActivitiesBidRequests(t *testing.T) {
 	for _, td := range testDefs {
 		vendorListData := MarshalVendorList(buildVendorList34())
 
-		perms := allPurposesEnabledPermissions()
-		perms.vendorIDs = map[openrtb_ext.BidderName]uint16{
-			openrtb_ext.BidderPubmatic: 6,
-			openrtb_ext.BidderRubicon:  8,
-		}
-		perms.fetchVendorList = map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-			tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
+		perms := permissionsImpl{
+			hostVendorID: 2,
+			vendorIDs: map[openrtb_ext.BidderName]uint16{
+				openrtb_ext.BidderPubmatic: 6,
+				openrtb_ext.BidderRubicon:  8,
+			},
+			fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
 				34: parseVendorListDataV2(t, vendorListData),
 			}),
 		}
-		perms.cfg.TCF2.Purpose2.EnforcePurpose = td.purpose2EnforcePurpose
-		p2Config := perms.purposeConfigs[consentconstants.Purpose(2)]
+
+		tcf2AggConfig := allPurposesEnabledTCF2Config()
+		tcf2AggConfig.HostConfig.Purpose2.EnforcePurpose = td.purpose2EnforcePurpose
+		tcf2AggConfig.HostConfig.Purpose2.EnforceVendors = td.purpose2EnforceVendors
+		p2Config := tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)]
 		p2Config.EnforcePurpose = td.purpose2EnforcePurpose
 		p2Config.EnforceVendors = td.purpose2EnforceVendors
-		perms.purposeConfigs[consentconstants.Purpose(2)] = p2Config
+		tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = p2Config
+		tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = &tcf2AggConfig.HostConfig.Purpose2
+		perms.cfg = &tcf2AggConfig
 
-		allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.weakVendorEnforcement, td.aliasGVLIDs)
+		allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, td.aliasGVLIDs)
 		assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description)
 		assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description)
@@ -928,12 +940,13 @@ func TestTCF1Consent(t *testing.T) {
 	tcf1Consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA"
 
 	perms := permissionsImpl{
+		cfg: &tcf2Config{},
 		vendorIDs: map[openrtb_ext.BidderName]uint16{
 			openrtb_ext.BidderAppnexus: 2,
 		},
 	}
 
-	bidReq, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, bidderAllowedByConsent, "", SignalYes, tcf1Consent, false, map[string]uint16{})
+	bidReq, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, bidderAllowedByConsent, "", SignalYes, tcf1Consent, map[string]uint16{})
 
 	assert.Nil(t, err, "TCF1 consent - no error returned")
 	assert.Equal(t, false, bidReq, "TCF1 consent - bid request not allowed")
@@ -1004,29 +1017,24 @@ func TestAllowActivitiesVendorException(t *testing.T) {
 	for _, td := range testDefs {
 		vendorListData := MarshalVendorList(buildVendorList34())
 		perms := permissionsImpl{
-			cfg: config.GDPR{
-				HostVendorID: 2,
-				TCF2: config.TCF2{
-					Enabled:         true,
-					Purpose2:        config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, VendorExceptionMap: td.p2VendorExceptionMap},
-					SpecialFeature1: config.TCF2SpecialFeature{Enforce: true, VendorExceptionMap: td.sf1VendorExceptionMap},
-				},
-			},
+			hostVendorID: 2,
 			vendorIDs: map[openrtb_ext.BidderName]uint16{
 				openrtb_ext.BidderAppnexus: 32,
 			},
-			fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-				tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
-					34: parseVendorListDataV2(t, vendorListData),
-				}),
-			},
-		}
-		perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
-			consentconstants.Purpose(2): perms.cfg.TCF2.Purpose2,
-			consentconstants.Purpose(3): perms.cfg.TCF2.Purpose3,
+			fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
 		}
 
-		allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, false, map[string]uint16{})
+		tcf2AggConfig := allPurposesEnabledTCF2Config()
+		tcf2AggConfig.HostConfig.Purpose2.VendorExceptionMap = td.p2VendorExceptionMap
+		tcf2AggConfig.HostConfig.SpecialFeature1.Enforce = true
+		tcf2AggConfig.HostConfig.SpecialFeature1.VendorExceptionMap = td.sf1VendorExceptionMap
+		tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = &tcf2AggConfig.HostConfig.Purpose2
+		tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(3)] = &tcf2AggConfig.HostConfig.Purpose3
+		perms.cfg = &tcf2AggConfig
+
+		allowBid, passGeo, passID, err := perms.AuctionActivitiesAllowed(context.Background(), td.bidderCoreName, td.bidder, "", SignalYes, td.consent, map[string]uint16{})
 		assert.NoErrorf(t, err, "Error processing AuctionActivitiesAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.allowBid, allowBid, "AllowBid failure on %s", td.description)
 		assert.EqualValuesf(t, td.passGeo, passGeo, "PassGeo failure on %s", td.description)
@@ -1071,26 +1079,20 @@ func TestBidderSyncAllowedVendorException(t *testing.T) {
 	for _, td := range testDefs {
 		vendorListData := MarshalVendorList(buildVendorList34())
 		perms := permissionsImpl{
-			cfg: config.GDPR{
-				HostVendorID: 2,
-				TCF2: config.TCF2{
-					Enabled:  true,
-					Purpose1: config.TCF2Purpose{EnforcePurpose: config.TCF2FullEnforcement, VendorExceptionMap: td.p1VendorExceptionMap},
-				},
-			},
+			hostVendorID: 2,
 			vendorIDs: map[openrtb_ext.BidderName]uint16{
 				openrtb_ext.BidderAppnexus: 32,
 			},
-			fetchVendorList: map[uint8]func(ctx context.Context, id uint16) (vendorlist.VendorList, error){
-				tcf2SpecVersion: listFetcher(map[uint16]vendorlist.VendorList{
-					34: parseVendorListDataV2(t, vendorListData),
-				}),
-			},
-		}
-		perms.purposeConfigs = map[consentconstants.Purpose]config.TCF2Purpose{
-			consentconstants.Purpose(1): perms.cfg.TCF2.Purpose1,
+			fetchVendorList: listFetcher(map[uint16]vendorlist.VendorList{
+				34: parseVendorListDataV2(t, vendorListData),
+			}),
 		}
 
+		tcf2AggConfig := allPurposesEnabledTCF2Config()
+		tcf2AggConfig.HostConfig.Purpose1.VendorExceptionMap = td.p1VendorExceptionMap
+		tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(1)] = &tcf2AggConfig.HostConfig.Purpose1
+		perms.cfg = &tcf2AggConfig
+
 		allowSync, err := perms.BidderSyncAllowed(context.Background(), td.bidder, SignalYes, td.consent)
 		assert.NoErrorf(t, err, "Error processing BidderSyncAllowed for %s", td.description)
 		assert.EqualValuesf(t, td.allowSync, allowSync, "AllowSync failure on %s", td.description)
diff --git a/gdpr/signal_test.go b/gdpr/signal_test.go
index 1b7e4e81..4c7b0aab 100644
--- a/gdpr/signal_test.go
+++ b/gdpr/signal_test.go
@@ -3,7 +3,6 @@ package gdpr
 import (
 	"testing"
 
-	"github.com/prebid/prebid-server/config"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -105,11 +104,7 @@ func TestSignalNormalize(t *testing.T) {
 	}
 
 	for _, test := range tests {
-		config := config.GDPR{
-			DefaultValue: test.defaultValue,
-		}
-
-		normalizedSignal := SignalNormalize(test.giveSignal, config)
+		normalizedSignal := SignalNormalize(test.giveSignal, test.defaultValue)
 
 		assert.Equal(t, test.wantSignal, normalizedSignal, test.description)
 	}
diff --git a/gdpr/vendorlist-fetching_test.go b/gdpr/vendorlist-fetching_test.go
index a1a926f5..2c5eb429 100644
--- a/gdpr/vendorlist-fetching_test.go
+++ b/gdpr/vendorlist-fetching_test.go
@@ -83,7 +83,7 @@ func TestFetcherThrottling(t *testing.T) {
 	})))
 	defer server.Close()
 
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
+	fetcher := NewVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
 
 	// Dynamically Load List 2 Successfully
 	_, errList1 := fetcher(context.Background(), 2)
@@ -104,7 +104,7 @@ func TestMalformedVendorlist(t *testing.T) {
 	})))
 	defer server.Close()
 
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
+	fetcher := NewVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
 	_, err := fetcher(context.Background(), 1)
 
 	// Fetching should fail since vendor list could not be unmarshalled.
@@ -117,7 +117,7 @@ func TestServerUrlInvalid(t *testing.T) {
 
 	invalidURLGenerator := func(uint16) string { return " http://invalid-url-has-leading-whitespace" }
 
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator)
+	fetcher := NewVendorListFetcher(context.Background(), testConfig(), server.Client(), invalidURLGenerator)
 	_, err := fetcher(context.Background(), 1)
 
 	assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
@@ -127,7 +127,7 @@ func TestServerUnavailable(t *testing.T) {
 	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
 	server.Close()
 
-	fetcher := newVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
+	fetcher := NewVendorListFetcher(context.Background(), testConfig(), server.Client(), testURLMaker(server))
 	_, err := fetcher(context.Background(), 1)
 
 	assert.EqualError(t, err, "gdpr vendor list version 1 does not exist, or has not been loaded yet. Try again in a few minutes")
@@ -152,7 +152,7 @@ func TestVendorListURLMaker(t *testing.T) {
 	}
 
 	for _, test := range testCases {
-		result := vendorListURLMaker(test.vendorListVersion)
+		result := VendorListURLMaker(test.vendorListVersion)
 		assert.Equal(t, test.expectedURL, result)
 	}
 }
@@ -254,7 +254,7 @@ type testExpected struct {
 
 func runTest(t *testing.T, test test, server *httptest.Server) {
 	config := testConfig()
-	fetcher := newVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server))
+	fetcher := NewVendorListFetcher(context.Background(), config, server.Client(), testURLMaker(server))
 	vendorList, err := fetcher(context.Background(), test.setup.vendorListVersion)
 
 	if test.expected.errorMessage != "" {