github.com/prebid/prebid-server@v0.275.0/account/account.go (about)

     1  package account
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"github.com/buger/jsonparser"
     8  	"github.com/prebid/go-gdpr/consentconstants"
     9  	"github.com/prebid/prebid-server/config"
    10  	"github.com/prebid/prebid-server/errortypes"
    11  	"github.com/prebid/prebid-server/metrics"
    12  	"github.com/prebid/prebid-server/openrtb_ext"
    13  	"github.com/prebid/prebid-server/stored_requests"
    14  	"github.com/prebid/prebid-server/util/iputil"
    15  	jsonpatch "gopkg.in/evanphx/json-patch.v4"
    16  )
    17  
    18  // GetAccount looks up the config.Account object referenced by the given accountID, with access rules applied
    19  func GetAccount(ctx context.Context, cfg *config.Configuration, fetcher stored_requests.AccountFetcher, accountID string, me metrics.MetricsEngine) (account *config.Account, errs []error) {
    20  	// Check BlacklistedAcctMap until we have deprecated it
    21  	if _, found := cfg.BlacklistedAcctMap[accountID]; found {
    22  		return nil, []error{&errortypes.BlacklistedAcct{
    23  			Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID),
    24  		}}
    25  	}
    26  	if cfg.AccountRequired && accountID == metrics.PublisherUnknown {
    27  		return nil, []error{&errortypes.AcctRequired{
    28  			Message: fmt.Sprintf("Prebid-server has been configured to discard requests without a valid Account ID. Please reach out to the prebid server host."),
    29  		}}
    30  	}
    31  	if accountJSON, accErrs := fetcher.FetchAccount(ctx, cfg.AccountDefaultsJSON(), accountID); len(accErrs) > 0 || accountJSON == nil {
    32  		// accountID does not reference a valid account
    33  		for _, e := range accErrs {
    34  			if _, ok := e.(stored_requests.NotFoundError); !ok {
    35  				errs = append(errs, e)
    36  			}
    37  		}
    38  		if cfg.AccountRequired && cfg.AccountDefaults.Disabled {
    39  			errs = append(errs, &errortypes.AcctRequired{
    40  				Message: fmt.Sprintf("Prebid-server could not verify the Account ID. Please reach out to the prebid server host."),
    41  			})
    42  			return nil, errs
    43  		}
    44  		// Make a copy of AccountDefaults instead of taking a reference,
    45  		// to preserve original accountID in case is needed to check NonStandardPublisherMap
    46  		pubAccount := cfg.AccountDefaults
    47  		pubAccount.ID = accountID
    48  		account = &pubAccount
    49  	} else {
    50  		// accountID resolved to a valid account, merge with AccountDefaults for a complete config
    51  		account = &config.Account{}
    52  		err := json.Unmarshal(accountJSON, account)
    53  
    54  		// this logic exists for backwards compatibility. If the initial unmarshal fails above, we attempt to
    55  		// resolve it by converting the GDPR enforce purpose fields and then attempting an unmarshal again before
    56  		// declaring a malformed account error.
    57  		// unmarshal fetched account to determine if it is well-formed
    58  		var deprecatedPurposeFields []string
    59  		if _, ok := err.(*json.UnmarshalTypeError); ok {
    60  			// attempt to convert deprecated GDPR enforce purpose fields to resolve issue
    61  			accountJSON, err, deprecatedPurposeFields = ConvertGDPREnforcePurposeFields(accountJSON)
    62  			// unmarshal again to check if unmarshal error still exists after GDPR field conversion
    63  			err = json.Unmarshal(accountJSON, account)
    64  
    65  			if _, ok := err.(*json.UnmarshalTypeError); ok {
    66  				return nil, []error{&errortypes.MalformedAcct{
    67  					Message: fmt.Sprintf("The prebid-server account config for account id \"%s\" is malformed. Please reach out to the prebid server host.", accountID),
    68  				}}
    69  			}
    70  		}
    71  		usingGDPRChannelEnabled := useGDPRChannelEnabled(account)
    72  		usingCCPAChannelEnabled := useCCPAChannelEnabled(account)
    73  
    74  		if usingGDPRChannelEnabled {
    75  			me.RecordAccountGDPRChannelEnabledWarning(accountID)
    76  		}
    77  		if usingCCPAChannelEnabled {
    78  			me.RecordAccountCCPAChannelEnabledWarning(accountID)
    79  		}
    80  		for _, purposeName := range deprecatedPurposeFields {
    81  			me.RecordAccountGDPRPurposeWarning(accountID, purposeName)
    82  		}
    83  		if len(deprecatedPurposeFields) > 0 || usingGDPRChannelEnabled || usingCCPAChannelEnabled {
    84  			me.RecordAccountUpgradeStatus(accountID)
    85  		}
    86  
    87  		if err != nil {
    88  			errs = append(errs, err)
    89  			return nil, errs
    90  		}
    91  		// Fill in ID if needed, so it can be left out of account definition
    92  		if len(account.ID) == 0 {
    93  			account.ID = accountID
    94  		}
    95  
    96  		// Set derived fields
    97  		setDerivedConfig(account)
    98  	}
    99  	if account.Disabled {
   100  		errs = append(errs, &errortypes.BlacklistedAcct{
   101  			Message: fmt.Sprintf("Prebid-server has disabled Account ID: %s, please reach out to the prebid server host.", accountID),
   102  		})
   103  		return nil, errs
   104  	}
   105  
   106  	if ipV6Err := account.Privacy.IPv6Config.Validate(nil); len(ipV6Err) > 0 {
   107  		account.Privacy.IPv6Config.AnonKeepBits = iputil.IPv6DefaultMaskingBitSize
   108  	}
   109  
   110  	if ipV4Err := account.Privacy.IPv4Config.Validate(nil); len(ipV4Err) > 0 {
   111  		account.Privacy.IPv4Config.AnonKeepBits = iputil.IPv4DefaultMaskingBitSize
   112  	}
   113  
   114  	// set the value of events.enabled field based on deprecated events_enabled field and ensure backward compatibility
   115  	deprecateEventsEnabledField(account)
   116  
   117  	return account, nil
   118  }
   119  
   120  // TCF2Enforcements maps enforcement algo string values to their integer representation and is
   121  // used to limit string compares
   122  var TCF2Enforcements = map[string]config.TCF2EnforcementAlgo{
   123  	config.TCF2EnforceAlgoBasic: config.TCF2BasicEnforcement,
   124  	config.TCF2EnforceAlgoFull:  config.TCF2FullEnforcement,
   125  }
   126  
   127  // setDerivedConfig modifies an account object by setting fields derived from other fields set in the account configuration
   128  func setDerivedConfig(account *config.Account) {
   129  	account.GDPR.PurposeConfigs = map[consentconstants.Purpose]*config.AccountGDPRPurpose{
   130  		1:  &account.GDPR.Purpose1,
   131  		2:  &account.GDPR.Purpose2,
   132  		3:  &account.GDPR.Purpose3,
   133  		4:  &account.GDPR.Purpose4,
   134  		5:  &account.GDPR.Purpose5,
   135  		6:  &account.GDPR.Purpose6,
   136  		7:  &account.GDPR.Purpose7,
   137  		8:  &account.GDPR.Purpose8,
   138  		9:  &account.GDPR.Purpose9,
   139  		10: &account.GDPR.Purpose10,
   140  	}
   141  
   142  	for _, pc := range account.GDPR.PurposeConfigs {
   143  		// To minimize the number of string compares per request, we set the integer representation
   144  		// of the enforcement algorithm on each purpose config
   145  		pc.EnforceAlgoID = config.TCF2UndefinedEnforcement
   146  		if algo, exists := TCF2Enforcements[pc.EnforceAlgo]; exists {
   147  			pc.EnforceAlgoID = algo
   148  		}
   149  
   150  		// To look for a purpose's vendor exceptions in O(1) time, for each purpose we fill this hash table with bidders
   151  		// located in the VendorExceptions field of the GDPR.PurposeX struct
   152  		if pc.VendorExceptions == nil {
   153  			continue
   154  		}
   155  		pc.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
   156  		for _, v := range pc.VendorExceptions {
   157  			pc.VendorExceptionMap[v] = struct{}{}
   158  		}
   159  	}
   160  
   161  	// To look for special feature 1's vendor exceptions in O(1) time, we fill this hash table with bidders
   162  	// located in the VendorExceptions field
   163  	if account.GDPR.SpecialFeature1.VendorExceptions != nil {
   164  		account.GDPR.SpecialFeature1.VendorExceptionMap = make(map[openrtb_ext.BidderName]struct{})
   165  
   166  		for _, v := range account.GDPR.SpecialFeature1.VendorExceptions {
   167  			account.GDPR.SpecialFeature1.VendorExceptionMap[v] = struct{}{}
   168  		}
   169  	}
   170  
   171  	// To look for basic enforcement vendors in O(1) time, we fill this hash table with bidders
   172  	// located in the BasicEnforcementVendors field
   173  	if account.GDPR.BasicEnforcementVendors != nil {
   174  		account.GDPR.BasicEnforcementVendorsMap = make(map[string]struct{})
   175  
   176  		for _, v := range account.GDPR.BasicEnforcementVendors {
   177  			account.GDPR.BasicEnforcementVendorsMap[v] = struct{}{}
   178  		}
   179  	}
   180  }
   181  
   182  // PatchAccount represents the GDPR portion of a publisher account configuration that can be mutated
   183  // for backwards compatibility reasons
   184  type PatchAccount struct {
   185  	GDPR map[string]*PatchAccountGDPRPurpose `json:"gdpr"`
   186  }
   187  
   188  // PatchAccountGDPRPurpose represents account-specific GDPR purpose configuration data that can be mutated
   189  // for backwards compatibility reasons
   190  type PatchAccountGDPRPurpose struct {
   191  	EnforceAlgo    string `json:"enforce_algo,omitempty"`
   192  	EnforcePurpose *bool  `json:"enforce_purpose,omitempty"`
   193  }
   194  
   195  // ConvertGDPREnforcePurposeFields is responsible for ensuring account GDPR config backwards compatibility
   196  // given the recent type change of gdpr.purpose{1-10}.enforce_purpose from a string to a bool. This function
   197  // iterates over each GDPR purpose config and sets enforce_purpose and enforce_algo to the appropriate
   198  // bool and string values respectively if enforce_purpose is a string and enforce_algo is not set
   199  func ConvertGDPREnforcePurposeFields(config []byte) (newConfig []byte, err error, deprecatedPurposeFields []string) {
   200  	gdprJSON, _, _, err := jsonparser.Get(config, "gdpr")
   201  	if err != nil && err == jsonparser.KeyPathNotFoundError {
   202  		return config, nil, deprecatedPurposeFields
   203  	}
   204  	if err != nil {
   205  		return nil, err, deprecatedPurposeFields
   206  	}
   207  
   208  	newAccount := PatchAccount{
   209  		GDPR: map[string]*PatchAccountGDPRPurpose{},
   210  	}
   211  
   212  	for i := 1; i <= 10; i++ {
   213  		purposeName := fmt.Sprintf("purpose%d", i)
   214  
   215  		enforcePurpose, purposeDataType, _, err := jsonparser.Get(gdprJSON, purposeName, "enforce_purpose")
   216  		if err != nil && err == jsonparser.KeyPathNotFoundError {
   217  			continue
   218  		}
   219  		if err != nil {
   220  			return nil, err, deprecatedPurposeFields
   221  		}
   222  		if purposeDataType != jsonparser.String {
   223  			continue
   224  		} else {
   225  			deprecatedPurposeFields = append(deprecatedPurposeFields, purposeName)
   226  		}
   227  
   228  		_, _, _, err = jsonparser.Get(gdprJSON, purposeName, "enforce_algo")
   229  		if err != nil && err != jsonparser.KeyPathNotFoundError {
   230  			return nil, err, deprecatedPurposeFields
   231  		}
   232  		if err == nil {
   233  			continue
   234  		}
   235  
   236  		newEnforcePurpose := false
   237  		if string(enforcePurpose) == "full" {
   238  			newEnforcePurpose = true
   239  		}
   240  
   241  		newAccount.GDPR[purposeName] = &PatchAccountGDPRPurpose{
   242  			EnforceAlgo:    "full",
   243  			EnforcePurpose: &newEnforcePurpose,
   244  		}
   245  	}
   246  
   247  	patchConfig, err := json.Marshal(newAccount)
   248  	if err != nil {
   249  		return nil, err, deprecatedPurposeFields
   250  	}
   251  
   252  	newConfig, err = jsonpatch.MergePatch(config, patchConfig)
   253  	if err != nil {
   254  		return nil, err, deprecatedPurposeFields
   255  	}
   256  
   257  	return newConfig, nil, deprecatedPurposeFields
   258  }
   259  
   260  func useGDPRChannelEnabled(account *config.Account) bool {
   261  	return account.GDPR.ChannelEnabled.IsSet() && !account.GDPR.IntegrationEnabled.IsSet()
   262  }
   263  
   264  func useCCPAChannelEnabled(account *config.Account) bool {
   265  	return account.CCPA.ChannelEnabled.IsSet() && !account.CCPA.IntegrationEnabled.IsSet()
   266  }
   267  
   268  // deprecateEventsEnabledField is responsible for ensuring backwards compatibility of "events_enabled" field.
   269  // This function favors "events.enabled" field over deprecated "events_enabled" field, if values for both are set.
   270  // If only deprecated "events_enabled" field is set then it sets the same value to "events.enabled" field.
   271  func deprecateEventsEnabledField(account *config.Account) {
   272  	if account != nil {
   273  		if account.Events.Enabled == nil {
   274  			account.Events.Enabled = account.EventsEnabled
   275  		}
   276  		// assign the old value to the new value so old and new are always the same even though the new value is what is used in the application code.
   277  		account.EventsEnabled = account.Events.Enabled
   278  	}
   279  }