Repository: prebid/prebid-server
Pull request #2942 (2023-07-18 07:34:17)

introduce aliasOf in bidderInfo struct
We already have [a piece of code that reads all bidder-info/*.yaml files. ](https://github.com/prebid/prebid-server/blob/543f288dc854361724d4a658dce31b81bb864d33/config/bidderinfo.go#L233-L247). This code already un-marshals bidder yaml config into BidderInfo struct. Presently, this function returns map[String]BidderInfos. But we would need a way to also identify aliases so that we can run the validations checks etc.
- This PR introduces `aliasOf` of type string in BidderInfo struct. 
- Also adds a simple validation check that if adapter is an alias then make sure that it's parent is an actual adapter and not an alias else raise a validation error.
- Also throw a validation check if `aliasOf` is defined in BidderInfo struct saying that do not use this as this feature is currently under development.


Patch:
diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml
index a3ac7c54..565bb9b8 100644
--- a/.github/workflows/semgrep.yml
+++ b/.github/workflows/semgrep.yml
@@ -13,6 +13,7 @@ jobs:
         with:
           fetch-depth: 0
           ref: ${{github.event.pull_request.head.ref}}
+          repository: ${{github.event.pull_request.head.repo.full_name}}
 
       - name: Calculate diff
         id: calculate_diff
diff --git a/adapters/mobilefuse/mobilefuse.go b/adapters/mobilefuse/mobilefuse.go
index f46fc591..07ecd61b 100644
--- a/adapters/mobilefuse/mobilefuse.go
+++ b/adapters/mobilefuse/mobilefuse.go
@@ -27,6 +27,10 @@ type BidExt struct {
 	Mf ExtMf `json:"mf"`
 }
 
+type ExtSkadn struct {
+	Skadn json.RawMessage `json:"skadn"`
+}
+
 // Builder builds a new instance of the MobileFuse adapter for the given bidder with the given config.
 func Builder(bidderName openrtb_ext.BidderName, config config.Adapter, server config.Server) (adapters.Bidder, error) {
 	template, err := template.New("endpointTemplate").Parse(config.Endpoint)
@@ -79,7 +83,7 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest,
 
 	for _, seatbid := range incomingBidResponse.SeatBid {
 		for i := range seatbid.Bid {
-			bidType := adapter.getBidType(seatbid.Bid[i])
+			bidType := getBidType(seatbid.Bid[i])
 			seatbid.Bid[i].Ext = nil
 
 			outgoingBidResponse.Bids = append(outgoingBidResponse.Bids, &adapters.TypedBid{
@@ -95,7 +99,7 @@ func (adapter *MobileFuseAdapter) MakeBids(incomingRequest *openrtb2.BidRequest,
 func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (*adapters.RequestData, []error) {
 	var errs []error
 
-	mobileFuseExtension, errs := adapter.getFirstMobileFuseExtension(bidRequest)
+	mobileFuseExtension, errs := getFirstMobileFuseExtension(bidRequest)
 
 	if errs != nil {
 		return nil, errs
@@ -107,10 +111,9 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (
 		return nil, append(errs, err)
 	}
 
-	validImps := adapter.getValidImps(bidRequest, mobileFuseExtension)
+	validImps, err := getValidImps(bidRequest, mobileFuseExtension)
 
-	if len(validImps) == 0 {
-		err := fmt.Errorf("No valid imps")
+	if err != nil {
 		errs = append(errs, err)
 		return nil, errs
 	}
@@ -135,7 +138,7 @@ func (adapter *MobileFuseAdapter) makeRequest(bidRequest *openrtb2.BidRequest) (
 	}, errs
 }
 
-func (adapter *MobileFuseAdapter) getFirstMobileFuseExtension(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) {
+func getFirstMobileFuseExtension(request *openrtb2.BidRequest) (*openrtb_ext.ExtImpMobileFuse, []error) {
 	var mobileFuseImpExtension openrtb_ext.ExtImpMobileFuse
 	var errs []error
 
@@ -178,23 +181,42 @@ func (adapter *MobileFuseAdapter) getEndpoint(ext *openrtb_ext.ExtImpMobileFuse)
 	return url, nil
 }
 
-func (adapter *MobileFuseAdapter) getValidImps(bidRequest *openrtb2.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) []openrtb2.Imp {
+func getValidImps(bidRequest *openrtb2.BidRequest, ext *openrtb_ext.ExtImpMobileFuse) ([]openrtb2.Imp, error) {
 	var validImps []openrtb2.Imp
 
 	for _, imp := range bidRequest.Imp {
 		if imp.Banner != nil || imp.Video != nil || imp.Native != nil {
 			imp.TagID = strconv.Itoa(ext.PlacementId)
-			imp.Ext = nil
+
+			var extSkadn ExtSkadn
+			err := json.Unmarshal(imp.Ext, &extSkadn)
+			if err != nil {
+				return nil, err
+			}
+
+			if extSkadn.Skadn != nil {
+				imp.Ext, err = json.Marshal(map[string]json.RawMessage{"skadn": extSkadn.Skadn})
+				if err != nil {
+					return nil, err
+				}
+			} else {
+				imp.Ext = nil
+			}
+
 			validImps = append(validImps, imp)
 
 			break
 		}
 	}
 
-	return validImps
+	if len(validImps) == 0 {
+		return nil, fmt.Errorf("No valid imps")
+	}
+
+	return validImps, nil
 }
 
-func (adapter *MobileFuseAdapter) getBidType(bid openrtb2.Bid) openrtb_ext.BidType {
+func getBidType(bid openrtb2.Bid) openrtb_ext.BidType {
 	if bid.Ext != nil {
 		var bidExt BidExt
 		err := json.Unmarshal(bid.Ext, &bidExt)
diff --git a/config/bidderinfo.go b/config/bidderinfo.go
index 6f62488c..b4d79001 100644
--- a/config/bidderinfo.go
+++ b/config/bidderinfo.go
@@ -23,6 +23,7 @@ type BidderInfos map[string]BidderInfo
 
 // BidderInfo specifies all configuration for a bidder except for enabled status, endpoint, and extra information.
 type BidderInfo struct {
+	AliasOf          string `yaml:"aliasOf" mapstructure:"aliasOf"`
 	Disabled         bool   `yaml:"disabled" mapstructure:"disabled"`
 	Endpoint         string `yaml:"endpoint" mapstructure:"endpoint"`
 	ExtraAdapterInfo string `yaml:"extra_info" mapstructure:"extra_info"`
@@ -266,20 +267,35 @@ func (infos BidderInfos) validate(errs []error) []error {
 		if bidder.IsEnabled() {
 			errs = validateAdapterEndpoint(bidder.Endpoint, bidderName, errs)
 
-			validateInfoErr := validateInfo(bidder, bidderName)
-			if validateInfoErr != nil {
-				errs = append(errs, validateInfoErr)
+			if err := validateInfo(bidder, bidderName); err != nil {
+				errs = append(errs, err)
 			}
 
-			validateSyncerErr := validateSyncer(bidder)
-			if validateSyncerErr != nil {
-				errs = append(errs, validateSyncerErr)
+			if err := validateSyncer(bidder); err != nil {
+				errs = append(errs, err)
+			}
+
+			if err := validateAliases(bidder, infos, bidderName); err != nil {
+				errs = append(errs, err)
 			}
 		}
 	}
 	return errs
 }
 
+func validateAliases(bidder BidderInfo, infos BidderInfos, bidderName string) error {
+	if len(bidder.AliasOf) > 0 {
+		if parentBidder, ok := infos[bidder.AliasOf]; ok {
+			if len(parentBidder.AliasOf) > 0 {
+				return fmt.Errorf("bidder: %s cannot be an alias of an alias: %s", bidder.AliasOf, bidderName)
+			}
+		} else {
+			return fmt.Errorf("bidder: %s not found for an alias: %s", bidder.AliasOf, bidderName)
+		}
+	}
+	return nil
+}
+
 var testEndpointTemplateParams = macros.EndpointTemplateParams{
 	Host:        "anyHost",
 	PublisherID: "anyPublisherID",
diff --git a/exchange/adapter_util.go b/exchange/adapter_util.go
index ad1866a6..9551ab3e 100644
--- a/exchange/adapter_util.go
+++ b/exchange/adapter_util.go
@@ -33,6 +33,10 @@ func buildBidders(infos config.BidderInfos, builders map[openrtb_ext.BidderName]
 	var errs []error
 
 	for bidder, info := range infos {
+		if len(info.AliasOf) > 0 {
+			errs = append(errs, fmt.Errorf("This feature is currently under development"))
+			continue
+		}
 		bidderName, bidderNameFound := openrtb_ext.NormalizeBidderName(bidder)
 		if !bidderNameFound {
 			errs = append(errs, fmt.Errorf("%v: unknown bidder", bidder))
diff --git a/gdpr/consent_parser.go b/gdpr/consent_parser.go
new file mode 100644
index 00000000..3a354886
--- /dev/null
+++ b/gdpr/consent_parser.go
@@ -0,0 +1,67 @@
+package gdpr
+
+import (
+	"errors"
+	"fmt"
+
+	"github.com/prebid/go-gdpr/api"
+	"github.com/prebid/go-gdpr/vendorconsent"
+	tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2"
+)
+
+// parsedConsent represents a parsed consent string containing notable version information and a convenient
+// metadata object that allows easy examination of encoded purpose and vendor information
+type parsedConsent struct {
+	encodingVersion uint8
+	listVersion     uint16
+	specVersion     uint16
+	consentMeta     tcf2.ConsentMetadata
+}
+
+// parseConsent parses and validates the specified consent string returning an instance of parsedConsent
+func parseConsent(consent string) (*parsedConsent, error) {
+	pc, err := vendorconsent.ParseString(consent)
+	if err != nil {
+		return nil, &ErrorMalformedConsent{
+			Consent: consent,
+			Cause:   err,
+		}
+	}
+	if err = validateVersions(pc); err != nil {
+		return nil, &ErrorMalformedConsent{
+			Consent: consent,
+			Cause:   err,
+		}
+	}
+	cm, ok := pc.(tcf2.ConsentMetadata)
+	if !ok {
+		err = errors.New("Unable to access TCF2 parsed consent")
+		return nil, err
+	}
+	return &parsedConsent{
+		encodingVersion: pc.Version(),
+		listVersion:     pc.VendorListVersion(),
+		specVersion:     getSpecVersion(pc.TCFPolicyVersion()),
+		consentMeta:     cm,
+	}, nil
+}
+
+// validateVersions ensures that certain version fields in the consent string contain valid values.
+// An error is returned if at least one of them is invalid
+func validateVersions(pc api.VendorConsents) (err error) {
+	version := pc.Version()
+	if version != 2 {
+		return fmt.Errorf("invalid encoding format version: %d", version)
+	}
+	return
+}
+
+// getSpecVersion looks at the TCF policy version and determines the corresponding GVL specification
+// version that should be used to calculate legal basis. A zero value is returned if the policy version
+// is invalid
+func getSpecVersion(policyVersion uint8) uint16 {
+	if policyVersion >= 4 {
+		return 3
+	}
+	return 2
+}
diff --git a/gdpr/full_enforcement.go b/gdpr/full_enforcement.go
index b86f7a3f..eefa28d5 100644
--- a/gdpr/full_enforcement.go
+++ b/gdpr/full_enforcement.go
@@ -67,6 +67,9 @@ func (fe *FullEnforcement) applyEnforceOverrides(overrides Overrides) (enforcePu
 // must declare the purpose as either consent or flex and the user must consent in accordance with
 // the purpose configs.
 func (fe *FullEnforcement) consentEstablished(consent tcf2.ConsentMetadata, vi VendorInfo, enforcePurpose bool, enforceVendors bool) bool {
+	if vi.vendor == nil {
+		return false
+	}
 	if !vi.vendor.Purpose(fe.cfg.PurposeID) {
 		return false
 	}
@@ -84,6 +87,9 @@ func (fe *FullEnforcement) consentEstablished(consent tcf2.ConsentMetadata, vi V
 // established, the vendor must declare the purpose as either legit interest or flex and the user
 // must have been provided notice for the legit interest basis in accordance with the purpose configs.
 func (fe *FullEnforcement) legitInterestEstablished(consent tcf2.ConsentMetadata, vi VendorInfo, enforcePurpose bool, enforceVendors bool) bool {
+	if vi.vendor == nil {
+		return false
+	}
 	if !vi.vendor.LegitimateInterest(fe.cfg.PurposeID) {
 		return false
 	}
diff --git a/gdpr/impl.go b/gdpr/impl.go
index 4869a84d..cc88a1fd 100644
--- a/gdpr/impl.go
+++ b/gdpr/impl.go
@@ -2,12 +2,9 @@ package gdpr
 
 import (
 	"context"
-	"errors"
-	"fmt"
 
 	"github.com/prebid/go-gdpr/api"
 	"github.com/prebid/go-gdpr/consentconstants"
-	"github.com/prebid/go-gdpr/vendorconsent"
 	tcf2 "github.com/prebid/go-gdpr/vendorconsent/tcf2"
 	"github.com/prebid/prebid-server/openrtb_ext"
 )
@@ -63,53 +60,27 @@ func (p *permissionsImpl) AuctionActivitiesAllowed(ctx context.Context, bidderCo
 	if _, ok := p.nonStandardPublishers[p.publisherID]; ok {
 		return AllowAll, nil
 	}
-
 	if p.gdprSignal != SignalYes {
 		return AllowAll, nil
 	}
-
 	if p.consent == "" {
 		return p.defaultPermissions(), nil
 	}
-
-	// note the bidder here is guaranteed to be enabled
-	vendorID, vendorFound := p.resolveVendorID(bidderCoreName, bidder)
-	basicEnforcementVendors := p.cfg.BasicEnforcementVendors()
-	_, weakVendorEnforcement := basicEnforcementVendors[string(bidder)]
-
-	if !vendorFound && !weakVendorEnforcement {
-		return DenyAll, nil
-	}
-
-	parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, p.consent)
+	pc, err := parseConsent(p.consent)
 	if err != nil {
 		return p.defaultPermissions(), err
 	}
-
-	// vendor will be nil if not a valid TCF2 consent string
-	if vendor == nil {
-		if weakVendorEnforcement && parsedConsent.Version() == 2 {
-			vendor = vendorTrue{}
-		} else {
-			return p.defaultPermissions(), nil
-		}
-	}
-
-	if !p.cfg.IsEnabled() {
-		return AllowBidRequestOnly, nil
-	}
-
-	consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
-	if !ok {
-		err = fmt.Errorf("Unable to access TCF2 parsed consent")
+	vendorID, _ := p.resolveVendorID(bidderCoreName, bidder)
+	vendor, err := p.getVendor(ctx, vendorID, *pc)
+	if err != nil {
 		return p.defaultPermissions(), err
 	}
-
 	vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
+
 	permissions = AuctionPermissions{}
-	permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, consentMeta, vendorInfo)
-	permissions.PassGeo = p.allowGeo(bidderCoreName, consentMeta, vendor)
-	permissions.PassID = p.allowID(bidderCoreName, consentMeta, vendorInfo)
+	permissions.AllowBidRequest = p.allowBidRequest(bidderCoreName, pc.consentMeta, vendorInfo)
+	permissions.PassGeo = p.allowGeo(bidderCoreName, pc.consentMeta, vendor)
+	permissions.PassID = p.allowID(bidderCoreName, pc.consentMeta, vendorInfo)
 
 	return permissions, nil
 }
@@ -148,34 +119,28 @@ func (p *permissionsImpl) allowSync(ctx context.Context, vendorID uint16, bidder
 	if p.consent == "" {
 		return false, nil
 	}
-
-	parsedConsent, vendor, err := p.parseVendor(ctx, vendorID, p.consent)
+	pc, err := parseConsent(p.consent)
 	if err != nil {
 		return false, err
 	}
-
-	if vendor == nil {
+	vendor, err := p.getVendor(ctx, vendorID, *pc)
+	if err != nil {
 		return false, nil
 	}
+	vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
 
 	if !p.cfg.PurposeEnforced(consentconstants.Purpose(1)) {
 		return true, nil
 	}
-	consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
-	if !ok {
-		err := errors.New("Unable to access TCF2 parsed consent")
-		return false, err
-	}
 
-	if p.cfg.PurposeOneTreatmentEnabled() && consentMeta.PurposeOneTreatment() {
+	if p.cfg.PurposeOneTreatmentEnabled() && pc.consentMeta.PurposeOneTreatment() {
 		return p.cfg.PurposeOneTreatmentAccessAllowed(), nil
 	}
 
 	purpose := consentconstants.Purpose(1)
 	enforcer := p.purposeEnforcerBuilder(purpose, bidder)
 
-	vendorInfo := VendorInfo{vendorID: vendorID, vendor: vendor}
-	if enforcer.LegalBasis(vendorInfo, bidder, consentMeta, Overrides{blockVendorExceptions: !vendorException}) {
+	if enforcer.LegalBasis(vendorInfo, bidder, pc.consentMeta, Overrides{blockVendorExceptions: !vendorException}) {
 		return true, nil
 	}
 	return false, nil
@@ -205,7 +170,7 @@ func (p *permissionsImpl) allowGeo(bidder openrtb_ext.BidderName, consentMeta tc
 
 	basicEnforcementVendors := p.cfg.BasicEnforcementVendors()
 	_, weakVendorEnforcement := basicEnforcementVendors[string(bidder)]
-	return consentMeta.SpecialFeatureOptIn(1) && (vendor.SpecialFeature(1) || weakVendorEnforcement)
+	return consentMeta.SpecialFeatureOptIn(1) && ((vendor != nil && vendor.SpecialFeature(1)) || weakVendorEnforcement)
 }
 
 // allowID computes the pass user ID activity legal basis for a given bidder using the enforcement algorithms
@@ -229,53 +194,13 @@ func (p *permissionsImpl) allowID(bidder openrtb_ext.BidderName, consentMeta tcf
 	return false
 }
 
-// parseVendor parses the consent string and fetches the specified vendor's information from the GVL
-func (p *permissionsImpl) parseVendor(ctx context.Context, vendorID uint16, consent string) (parsedConsent api.VendorConsents, vendor api.Vendor, err error) {
-	parsedConsent, err = vendorconsent.ParseString(consent)
+// getVendor retrieves the GVL vendor information for a particular bidder
+func (p *permissionsImpl) getVendor(ctx context.Context, vendorID uint16, pc parsedConsent) (api.Vendor, error) {
+	vendorList, err := p.fetchVendorList(ctx, pc.specVersion, pc.listVersion)
 	if err != nil {
-		err = &ErrorMalformedConsent{
-			Consent: consent,
-			Cause:   err,
-		}
-		return
-	}
-
-	version := parsedConsent.Version()
-	if version != 2 {
-		return
-	}
-
-	policyVersion := parsedConsent.TCFPolicyVersion()
-	specVersion, err := getSpecVersion(policyVersion)
-	if err != nil {
-		err = &ErrorMalformedConsent{
-			Consent: consent,
-			Cause:   err,
-		}
-		return
-	}
-
-	vendorList, err := p.fetchVendorList(ctx, uint16(specVersion), parsedConsent.VendorListVersion())
-	if err != nil {
-		return
-	}
-
-	vendor = vendorList.Vendor(vendorID)
-	return
-}
-
-// getSpecVersion looks at the TCF policy version and determines the corresponding GVL specification
-// version that should be used to calculate legal basis
-func getSpecVersion(policyVersion uint8) (uint16, error) {
-	var specVersion uint16 = 3
-
-	if policyVersion > 4 {
-		return 0, fmt.Errorf("invalid TCF policy version %d", policyVersion)
+		return nil, err
 	}
-	if policyVersion < 4 {
-		specVersion = 2
-	}
-	return specVersion, nil
+	return vendorList.Vendor(vendorID), nil
 }
 
 // AllowHostCookies represents a GDPR permissions policy with host cookie syncing always allowed
@@ -300,25 +225,3 @@ func (a AlwaysAllow) BidderSyncAllowed(ctx context.Context, bidder openrtb_ext.B
 func (a AlwaysAllow) AuctionActivitiesAllowed(ctx context.Context, bidderCoreName openrtb_ext.BidderName, bidder openrtb_ext.BidderName) (permissions AuctionPermissions, err error) {
 	return AllowAll, nil
 }
-
-// vendorTrue claims everything.
-type vendorTrue struct{}
-
-func (v vendorTrue) Purpose(purposeID consentconstants.Purpose) bool {
-	return true
-}
-func (v vendorTrue) PurposeStrict(purposeID consentconstants.Purpose) bool {
-	return true
-}
-func (v vendorTrue) LegitimateInterest(purposeID consentconstants.Purpose) bool {
-	return true
-}
-func (v vendorTrue) LegitimateInterestStrict(purposeID consentconstants.Purpose) bool {
-	return true
-}
-func (v vendorTrue) SpecialFeature(featureID consentconstants.SpecialFeature) (hasSpecialFeature bool) {
-	return true
-}
-func (v vendorTrue) SpecialPurpose(purposeID consentconstants.Purpose) (hasSpecialPurpose bool) {
-	return true
-}


Test patch:
diff --git a/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json b/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json
new file mode 100644
index 00000000..e8fd66b1
--- /dev/null
+++ b/adapters/mobilefuse/mobilefusetest/exemplary/skadn.json
@@ -0,0 +1,78 @@
+{
+    "mockBidRequest": {
+        "id": "test-request-id",
+        "imp": [
+            {
+                "id": "1",
+                "banner": {
+                    "format": [
+                        {
+                            "w": 300,
+                            "h": 250
+                        }
+                    ]
+                },
+                "ext": {
+                    "skadn": {
+                        "versions": [
+                            "2.0",
+                            "2.1"
+                        ],
+                        "sourceapp": "11111",
+                        "skadnetids": [
+                            "424m5254lk.skadnetwork",
+                            "4fzdc2evr5.skadnetwork"
+                        ]
+                    },
+                    "bidder": {
+                        "placement_id": 999999,
+                        "pub_id": 1111,
+                        "tagid_src": "ext"
+                    }
+                }
+            }
+        ]
+    },
+    "httpCalls": [
+        {
+            "expectedRequest": {
+                "uri": "http://mfx.mobilefuse.com/openrtb?pub_id=1111&tagid_src=ext",
+                "body": {
+                    "id": "test-request-id",
+                    "imp": [
+                        {
+                            "id": "1",
+                            "banner": {
+                                "format": [
+                                    {
+                                        "w": 300,
+                                        "h": 250
+                                    }
+                                ]
+                            },
+                            "tagid": "999999",
+                            "ext": {
+                                "skadn": {
+                                    "versions": [
+                                        "2.0",
+                                        "2.1"
+                                    ],
+                                    "sourceapp": "11111",
+                                    "skadnetids": [
+                                        "424m5254lk.skadnetwork",
+                                        "4fzdc2evr5.skadnetwork"
+                                    ]
+                                }
+                            }
+                        }
+                    ]
+                }
+            },
+            "mockResponse": {
+                "status": 204,
+                "body": {}
+            }
+        }
+    ],
+    "expectedBidResponses": []
+}
\ No newline at end of file
diff --git a/config/bidderinfo_test.go b/config/bidderinfo_test.go
index 6005178c..f83d942c 100644
--- a/config/bidderinfo_test.go
+++ b/config/bidderinfo_test.go
@@ -244,6 +244,22 @@ func TestBidderInfoValidationPositive(t *testing.T) {
 				},
 			},
 		},
+		"bidderC": BidderInfo{
+			Endpoint: "http://bidderB.com/openrtb2",
+			Maintainer: &MaintainerInfo{
+				Email: "maintainer@bidderA.com",
+			},
+			Capabilities: &CapabilitiesInfo{
+				Site: &PlatformInfo{
+					MediaTypes: []openrtb_ext.BidType{
+						openrtb_ext.BidTypeVideo,
+						openrtb_ext.BidTypeNative,
+						openrtb_ext.BidTypeBanner,
+					},
+				},
+			},
+			AliasOf: "bidderB",
+		},
 	}
 	errs := bidderInfos.validate(make([]error, 0))
 	assert.Len(t, errs, 0, "All bidder infos should be correct")
@@ -467,6 +483,43 @@ func TestBidderInfoValidationNegative(t *testing.T) {
 				errors.New("syncer could not be created, invalid supported endpoint: incorrect"),
 			},
 		},
+		{
+			"Invalid aliases",
+			BidderInfos{
+				"bidderA": BidderInfo{
+					Endpoint: "http://bidderA.com/openrtb2",
+					Maintainer: &MaintainerInfo{
+						Email: "maintainer@bidderA.com",
+					},
+					Capabilities: &CapabilitiesInfo{
+						Site: &PlatformInfo{
+							MediaTypes: []openrtb_ext.BidType{
+								openrtb_ext.BidTypeVideo,
+							},
+						},
+					},
+					AliasOf: "bidderB",
+				},
+				"bidderB": BidderInfo{
+					Endpoint: "http://bidderA.com/openrtb2",
+					Maintainer: &MaintainerInfo{
+						Email: "maintainer@bidderA.com",
+					},
+					Capabilities: &CapabilitiesInfo{
+						Site: &PlatformInfo{
+							MediaTypes: []openrtb_ext.BidType{
+								openrtb_ext.BidTypeVideo,
+							},
+						},
+					},
+					AliasOf: "bidderC",
+				},
+			},
+			[]error{
+				errors.New("bidder: bidderB cannot be an alias of an alias: bidderA"),
+				errors.New("bidder: bidderC not found for an alias: bidderB"),
+			},
+		},
 		{
 			"Two bidders, one with incorrect url",
 			BidderInfos{
diff --git a/exchange/adapter_util_test.go b/exchange/adapter_util_test.go
index 2dd0fd04..3b818bdd 100644
--- a/exchange/adapter_util_test.go
+++ b/exchange/adapter_util_test.go
@@ -67,6 +67,13 @@ func TestBuildAdapters(t *testing.T) {
 				errors.New("unknown: unknown bidder"),
 			},
 		},
+		{
+			description: "Alias feature disabled",
+			bidderInfos: map[string]config.BidderInfo{"appNexus": {AliasOf: "rubicon"}},
+			expectedErrors: []error{
+				errors.New("This feature is currently under development"),
+			},
+		},
 	}
 
 	cfg := &config.Configuration{}
diff --git a/gdpr/consent_parser_test.go b/gdpr/consent_parser_test.go
new file mode 100644
index 00000000..4708f689
--- /dev/null
+++ b/gdpr/consent_parser_test.go
@@ -0,0 +1,160 @@
+package gdpr
+
+import (
+	"errors"
+	"testing"
+	"time"
+
+	"github.com/prebid/go-gdpr/consentconstants"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestParseConsent(t *testing.T) {
+	validTCF1Consent := "BONV8oqONXwgmADACHENAO7pqzAAppY"
+	validTCF2Consent := "CPuKGCPPuKGCPNEAAAENCZCAAAAAAAAAAAAAAAAAAAAA"
+
+	tests := []struct {
+		name                    string
+		consent                 string
+		expectedEncodingVersion uint8
+		expectedListVersion     uint16
+		expectedSpecVersion     uint16
+		expectedError           error
+	}{
+
+		{
+			name:                    "valid_consent_with_encoding_version_2",
+			consent:                 validTCF2Consent,
+			expectedEncodingVersion: 2,
+			expectedListVersion:     153,
+			expectedSpecVersion:     2,
+		},
+		{
+			name:    "invalid_consent_parsing_error",
+			consent: "",
+			expectedError: &ErrorMalformedConsent{
+				Consent: "",
+				Cause:   consentconstants.ErrEmptyDecodedConsent,
+			},
+		},
+		{
+			name:    "invalid_consent_version_validation_error",
+			consent: validTCF1Consent,
+			expectedError: &ErrorMalformedConsent{
+				Consent: validTCF1Consent,
+				Cause:   errors.New("invalid encoding format version: 1"),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			parsedConsent, err := parseConsent(tt.consent)
+
+			if tt.expectedError != nil {
+				assert.Equal(t, tt.expectedError, err)
+				assert.Nil(t, parsedConsent)
+			} else {
+				assert.NoError(t, err)
+				assert.NotNil(t, parsedConsent)
+				assert.Equal(t, uint8(2), parsedConsent.encodingVersion)
+				assert.Equal(t, uint16(153), parsedConsent.listVersion)
+				assert.Equal(t, uint16(2), parsedConsent.specVersion)
+				assert.Equal(t, tt.expectedEncodingVersion, parsedConsent.consentMeta.Version())
+				assert.Equal(t, tt.expectedListVersion, parsedConsent.consentMeta.VendorListVersion())
+			}
+		})
+	}
+}
+
+func TestValidateVersions(t *testing.T) {
+	tests := []struct {
+		name          string
+		version       uint8
+		expectedError error
+	}{
+		{
+			name:    "valid_consent_version=2",
+			version: 2,
+		},
+		{
+			name:          "invalid_consent_version<2",
+			version:       1,
+			expectedError: errors.New("invalid encoding format version: 1"),
+		},
+		{
+			name:          "invalid_consent_version>2",
+			version:       3,
+			expectedError: errors.New("invalid encoding format version: 3"),
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			mcs := mockConsentString{
+				version: tt.version,
+			}
+			err := validateVersions(&mcs)
+			if tt.expectedError != nil {
+				assert.Equal(t, tt.expectedError, err)
+			} else {
+				assert.Nil(t, err)
+			}
+		})
+	}
+}
+
+func TestGetSpecVersion(t *testing.T) {
+	tests := []struct {
+		name                string
+		policyVersion       uint8
+		expectedSpecVersion uint16
+	}{
+		{
+			name:                "policy_version_0_gives_spec_version_2",
+			policyVersion:       0,
+			expectedSpecVersion: 2,
+		},
+		{
+			name:                "policy_version_3_gives_spec_version_2",
+			policyVersion:       3,
+			expectedSpecVersion: 2,
+		},
+		{
+			name:                "policy_version_4_gives_spec_version_3",
+			policyVersion:       4,
+			expectedSpecVersion: 3,
+		},
+		{
+			name:                "policy_version_5_gives_spec_version_3",
+			policyVersion:       5,
+			expectedSpecVersion: 3,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			specVersion := getSpecVersion(tt.policyVersion)
+			assert.Equal(t, tt.expectedSpecVersion, specVersion)
+		})
+	}
+}
+
+type mockConsentString struct {
+	version       uint8
+	policyVersion uint8
+}
+
+func (mcs *mockConsentString) Version() uint8                                  { return mcs.version }
+func (mcs *mockConsentString) Created() time.Time                              { return time.Time{} }
+func (mcs *mockConsentString) LastUpdated() time.Time                          { return time.Time{} }
+func (mcs *mockConsentString) CmpID() uint16                                   { return 0 }
+func (mcs *mockConsentString) CmpVersion() uint16                              { return 0 }
+func (mcs *mockConsentString) ConsentScreen() uint8                            { return 0 }
+func (mcs *mockConsentString) ConsentLanguage() string                         { return "" }
+func (mcs *mockConsentString) VendorListVersion() uint16                       { return 0 }
+func (mcs *mockConsentString) TCFPolicyVersion() uint8                         { return mcs.policyVersion }
+func (mcs *mockConsentString) MaxVendorID() uint16                             { return 0 }
+func (mcs *mockConsentString) PurposeAllowed(id consentconstants.Purpose) bool { return false }
+func (mcs *mockConsentString) VendorConsent(id uint16) bool                    { return false }
diff --git a/gdpr/full_enforcement_test.go b/gdpr/full_enforcement_test.go
index e8ccd1e4..61f6e8b8 100644
--- a/gdpr/full_enforcement_test.go
+++ b/gdpr/full_enforcement_test.go
@@ -898,6 +898,66 @@ func TestLegalBasisWithPubRestrictionRequireLI(t *testing.T) {
 	}
 }
 
+func TestLegalBasisWithoutVendor(t *testing.T) {
+	P1P2P3PurposeConsent := "CPfCRQAPfCRQAAAAAAENCgCAAOAAAAAAAAAAAAAAAAAA"
+	tests := []struct {
+		name       string
+		config     purposeConfig
+		wantResult bool
+	}{
+		{
+			name: "enforce_purpose_&_vendors_off",
+			config: purposeConfig{
+				EnforcePurpose: false,
+				EnforceVendors: false,
+			},
+			wantResult: true,
+		},
+		{
+			name: "enforce_purpose_on,_bidder_is_a_vendor_exception",
+			config: purposeConfig{
+				EnforcePurpose:     true,
+				EnforceVendors:     false,
+				VendorExceptionMap: map[openrtb_ext.BidderName]struct{}{openrtb_ext.BidderAppnexus: {}},
+			},
+			wantResult: true,
+		},
+		{
+			name: "enforce_purpose_on",
+			config: purposeConfig{
+				EnforcePurpose: true,
+				EnforceVendors: false,
+			},
+			wantResult: false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			// convert consent string to TCF2 object
+			parsedConsent, err := vendorconsent.ParseString(P1P2P3PurposeConsent)
+			if err != nil {
+				t.Fatalf("Failed to parse consent %s\n", P1P2P3PurposeConsent)
+			}
+			consentMeta, ok := parsedConsent.(tcf2.ConsentMetadata)
+			if !ok {
+				t.Fatalf("Failed to convert consent %s\n", P1P2P3PurposeConsent)
+			}
+
+			vendorInfo := VendorInfo{
+				vendorID: 32,
+				vendor:   nil,
+			}
+
+			enforcer := FullEnforcement{cfg: tt.config}
+			enforcer.cfg.PurposeID = consentconstants.Purpose(3)
+
+			result := enforcer.LegalBasis(vendorInfo, openrtb_ext.BidderAppnexus, consentMeta, Overrides{})
+			assert.Equal(t, tt.wantResult, result)
+		})
+	}
+}
+
 func getVendorList(t *testing.T) vendorlist.VendorList {
 	GVL := makeVendorList()
 
diff --git a/gdpr/impl_test.go b/gdpr/impl_test.go
index 4a65dbaf..835a580f 100644
--- a/gdpr/impl_test.go
+++ b/gdpr/impl_test.go
@@ -342,6 +342,109 @@ func TestAllowActivities(t *testing.T) {
 	}
 }
 
+func TestAllowActivitiesBidderWithoutGVLID(t *testing.T) {
+	bidderWithoutGVLID := openrtb_ext.BidderPangle
+	purpose2Consent := "CPuDXznPuDXznMOAAAENCZCAAEAAAAAAAAAAAAAAAAAA"
+	noPurposeConsent := "CPuDXznPuDXznMOAAAENCZCAAAAAAAAAAAAAAAAAAAAA"
+
+	tests := []struct {
+		name                    string
+		enforceAlgoID           config.TCF2EnforcementAlgo
+		vendorExceptions        map[openrtb_ext.BidderName]struct{}
+		basicEnforcementVendors map[string]struct{}
+		consent                 string
+		allowBidRequest         bool
+		passID                  bool
+	}{
+		{
+			name:          "full_enforcement_no_exceptions_user_consents_to_purpose_2",
+			enforceAlgoID: config.TCF2FullEnforcement,
+			consent:       purpose2Consent,
+		},
+		{
+			name:             "full_enforcement_vendor_exception_user_consents_to_purpose_2",
+			enforceAlgoID:    config.TCF2FullEnforcement,
+			vendorExceptions: map[openrtb_ext.BidderName]struct{}{bidderWithoutGVLID: {}},
+			consent:          purpose2Consent,
+			allowBidRequest:  true,
+			passID:           true,
+		},
+		{
+			name:    "basic_enforcement_no_exceptions_user_consents_to_purpose_2",
+			consent: purpose2Consent,
+		},
+		{
+			name:             "basic_enforcement_vendor_exception_user_consents_to_purpose_2",
+			vendorExceptions: map[openrtb_ext.BidderName]struct{}{bidderWithoutGVLID: {}},
+			consent:          purpose2Consent,
+			allowBidRequest:  true,
+			passID:           true,
+		},
+		{
+			name:                    "full_enforcement_soft_vendor_exception_user_consents_to_purpose_2", // allow bid request and pass ID
+			enforceAlgoID:           config.TCF2FullEnforcement,
+			basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}},
+			consent:                 purpose2Consent,
+			allowBidRequest:         true,
+			passID:                  true,
+		},
+		{
+			name:                    "basic_enforcement_soft_vendor_exception_user_consents_to_purpose_2", // allow bid request and pass ID
+			enforceAlgoID:           config.TCF2BasicEnforcement,
+			basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}},
+			consent:                 purpose2Consent,
+			allowBidRequest:         true,
+			passID:                  true,
+		},
+		{
+			name:                    "full_enforcement_soft_vendor_exception_user_consents_to_purpose_4",
+			enforceAlgoID:           config.TCF2FullEnforcement,
+			basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}},
+			consent:                 noPurposeConsent,
+			allowBidRequest:         false,
+			passID:                  false,
+		},
+		{
+			name:                    "basic_enforcement_soft_vendor_exception_user_consents_to_purpose_4",
+			enforceAlgoID:           config.TCF2BasicEnforcement,
+			basicEnforcementVendors: map[string]struct{}{string(bidderWithoutGVLID): {}},
+			consent:                 noPurposeConsent,
+			allowBidRequest:         false,
+			passID:                  false,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tcf2AggConfig := allPurposesEnabledTCF2Config()
+			tcf2AggConfig.AccountConfig.BasicEnforcementVendorsMap = tt.basicEnforcementVendors
+			tcf2AggConfig.HostConfig.Purpose2.VendorExceptionMap = tt.vendorExceptions
+			tcf2AggConfig.HostConfig.Purpose2.EnforceAlgoID = tt.enforceAlgoID
+			tcf2AggConfig.HostConfig.PurposeConfigs[consentconstants.Purpose(2)] = &tcf2AggConfig.HostConfig.Purpose2
+
+			perms := permissionsImpl{
+				cfg:                   &tcf2AggConfig,
+				consent:               tt.consent,
+				gdprSignal:            SignalYes,
+				hostVendorID:          2,
+				nonStandardPublishers: map[string]struct{}{},
+				vendorIDs:             map[openrtb_ext.BidderName]uint16{},
+				fetchVendorList: listFetcher(map[uint16]map[uint16]vendorlist.VendorList{
+					2: {
+						153: parseVendorListDataV2(t, MarshalVendorList(vendorList{GVLSpecificationVersion: 2, VendorListVersion: 153, Vendors: map[string]*vendor{}})),
+					},
+				}),
+				purposeEnforcerBuilder: NewPurposeEnforcerBuilder(&tcf2AggConfig),
+			}
+
+			permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderWithoutGVLID, bidderWithoutGVLID)
+			assert.NoError(t, err)
+			assert.Equal(t, tt.allowBidRequest, permissions.AllowBidRequest)
+			assert.Equal(t, tt.passID, permissions.PassID)
+		})
+	}
+}
+
 func buildVendorList34() vendorList {
 	return vendorList{
 		VendorListVersion: 2,
@@ -1006,28 +1109,6 @@ func TestAllowActivitiesBidRequests(t *testing.T) {
 	}
 }
 
-func TestTCF1Consent(t *testing.T) {
-	bidderAllowedByConsent := openrtb_ext.BidderAppnexus
-	tcf1Consent := "BOS2bx5OS2bx5ABABBAAABoAAAABBwAA"
-
-	perms := permissionsImpl{
-		cfg: &tcf2Config{},
-		vendorIDs: map[openrtb_ext.BidderName]uint16{
-			openrtb_ext.BidderAppnexus: 2,
-		},
-		aliasGVLIDs: map[string]uint16{},
-		consent:     tcf1Consent,
-		gdprSignal:  SignalYes,
-	}
-
-	permissions, err := perms.AuctionActivitiesAllowed(context.Background(), bidderAllowedByConsent, bidderAllowedByConsent)
-
-	assert.Nil(t, err, "TCF1 consent - no error returned")
-	assert.Equal(t, true, permissions.AllowBidRequest, "TCF1 consent - bid request not allowed")
-	assert.Equal(t, true, permissions.PassGeo, "TCF1 consent - passing geo not allowed")
-	assert.Equal(t, false, permissions.PassID, "TCF1 consent - passing id not allowed")
-}
-
 func TestAllowActivitiesVendorException(t *testing.T) {
 	noPurposeOrVendorConsentAndPubRestrictsP2 := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAACEAAgAgAA"
 	noPurposeOrVendorConsentAndPubRestrictsNone := "CPF_61ePF_61eFxAAAENAiCAAAAAAAAAAAAAACEAAAAA"
@@ -1244,53 +1325,9 @@ func TestDefaultPermissions(t *testing.T) {
 	}
 }
 
-func TestGetSpecVersion(t *testing.T) {
-	tests := []struct {
-		name                string
-		policyVersion       uint8
-		expectedSpecVersion uint16
-		expectedError       bool
-	}{
-		{
-			name:                "policy_version_0_gives_spec_version_2",
-			policyVersion:       0,
-			expectedSpecVersion: 2,
-		},
-		{
-			name:                "policy_version_3_gives_spec_version_2",
-			policyVersion:       3,
-			expectedSpecVersion: 2,
-		},
-		{
-			name:                "policy_version_4_spec_version_3",
-			policyVersion:       4,
-			expectedSpecVersion: 3,
-		},
-		{
-			name:                "policy_version_5_error",
-			policyVersion:       5,
-			expectedSpecVersion: 0,
-			expectedError:       true,
-		},
-	}
-
-	for _, tt := range tests {
-		t.Run(tt.name, func(t *testing.T) {
-			specVersion, err := getSpecVersion(tt.policyVersion)
-			assert.Equal(t, tt.expectedSpecVersion, specVersion)
-			if tt.expectedError {
-				assert.Error(t, err)
-			} else {
-				assert.Nil(t, err)
-			}
-		})
-	}
-}
-
 func TestVendorListSelection(t *testing.T) {
 	policyVersion3WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABDAAIAAAAAAAAAAACEAAAAA"
 	policyVersion4WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABEAAIAAAAAAAAAAACEAAAAA"
-	policyVersion5WithVendor2AndPurpose1Consent := "CPGWbY_PGWbY_GYAAAENABFAAIAAAAAAAAAAACEAAAAA"
 
 	specVersion2vendorListData := MarshalVendorList(vendorList{
 		GVLSpecificationVersion: 2,
@@ -1359,11 +1396,6 @@ func TestVendorListSelection(t *testing.T) {
 			consent:           policyVersion4WithVendor2AndPurpose1Consent,
 			expectedAllowSync: true,
 		},
-		{
-			name:        "consent_tcf_policy_version_5_causes_error",
-			consent:     policyVersion5WithVendor2AndPurpose1Consent,
-			expectedErr: true,
-		},
 	}
 
 	for _, tt := range tests {