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 }