Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UserSync activity #2897

Merged
merged 14 commits into from
Jul 27, 2023
Merged

UserSync activity #2897

merged 14 commits into from
Jul 27, 2023

Conversation

VeronikaSolovei9
Copy link
Contributor

@VeronikaSolovei9 VeronikaSolovei9 commented Jul 4, 2023

User sync activities for bidder.

In this PR I removed a statement that disables activities. If activities are not specified in config then nil plans will be returned. Default result is Abstain and it will not modify existing behavior.
This is needed because more and more unit tests start to depend on NewActivityControl function as this is a part of auction logic.

@bsardo bsardo self-assigned this Jul 5, 2023
Comment on lines 109 to 110
w.WriteHeader(http.StatusBadRequest)
return
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably record a metric and update the analytics object so here as well.
Should the param passed to metricsEngine.RecordSetUid be one of the account options (metrics.SetUidAccountConfigMalformed or SetUidAccountInvalid) or something more generic?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good point. We discussed this offline and decided to add metrics in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec calls for us to only write to metrics / logs. In the case of error, we should continue with activities set to nil.

Comment on lines +117 to +118
w.WriteHeader(http.StatusUnavailableForLegalReasons)
return
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably record a metric and update the analytics object so here as well.
For GDPR, the metric we use when the error is http.StatusUnavailableForLegalReasons is metrics.SetUidGDPRHostCookieBlocked. Maybe we need to add a more generic privacy related blocking metric like metrics.SetUidPrivacyHostCookieBlocked?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this offline and decided to add metrics in a separate PR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a generic metric is a good idea, especially as the number of privacy policies is expected to increase. IMHO the existign GDPR and CCPA metrics could use the new generalized privacy blocking metric.

@VeronikaSolovei9 VeronikaSolovei9 changed the base branch from activities to master July 6, 2023 22:15
@VeronikaSolovei9 VeronikaSolovei9 marked this pull request as ready for review July 6, 2023 22:36
@SyntaxNode SyntaxNode assigned SyntaxNode and AlexBVolcy and unassigned bsardo Jul 10, 2023
Comment on lines 152 to 153
if errortypes.ContainsFatalError([]error{activitiesErr}) {
return usersync.Request{}, privacy.Policies{}, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests to cover these lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Comment on lines 528 to 531
func (p usersyncPrivacy) ActivityAllowsUserSync(bidder string) privacy.ActivityResult {
activityResult := p.activityControl.Allow(privacy.ActivitySyncUser,
privacy.ScopedName{Scope: privacy.ScopeTypeBidder, Name: bidder})
return activityResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add tests to cover these lines?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added!

@@ -1870,6 +1902,42 @@ func TestUsersyncPrivacyCCPAAllowsBidderSync(t *testing.T) {
}
}

func TestActivityDefaultToDefaultResult(t *testing.T) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Could you remove this blank line?

gdprAllowsHostCookie bool
gdprAllowsBidderSync bool
ccpaAllowsBidderSync bool
activityAllowIUserSync privacy.ActivityResult
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a typo here with activityAllowIUserSync? Should it be activityAllowsUserSync?

Copy link
Contributor Author

@VeronikaSolovei9 VeronikaSolovei9 Jul 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a typo. Fixed

@@ -972,6 +973,36 @@ func TestCookieSyncParseRequest(t *testing.T) {
expectedError: errCookieSyncAccountBlocked.Error(),
givenAccountRequired: true,
},

{
description: "Account Defaults - Invalid activities Activities",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Looks like you have activities twice in the test description.

activityControl, activitiesErr := privacy.NewActivityControl(account.Privacy)
if activitiesErr != nil {
if errortypes.ContainsFatalError([]error{activitiesErr}) {
return usersync.Request{}, privacy.Policies{}, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should ignore account config errors for activity control. Per the spec:

If a rule is invalid for any reason, skip it and emit an error at N% sampling.

I recommend proceeding with a nil activities control object if there's an error and emit metrics and logs so the host can know about the problem. I understand that metrics / logs are scoped for a follow-up PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -1870,6 +1902,41 @@ func TestUsersyncPrivacyCCPAAllowsBidderSync(t *testing.T) {
}
}

func TestActivityDefaultToDefaultResult(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you're too specific with this test name. I suspect this is testing the activity control integration where the default value is not important. Consider renaming to something like TestCookieSyncActivityControlIntegration.

@@ -33,9 +32,6 @@ func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, er

if privacyConf == nil {
return ac, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Consider returning nil for an error here to make it clear this a valid state.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this turned back from nil to err?

Comment on lines 109 to 110
w.WriteHeader(http.StatusBadRequest)
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spec calls for us to only write to metrics / logs. In the case of error, we should continue with activities set to nil.

Comment on lines +117 to +118
w.WriteHeader(http.StatusUnavailableForLegalReasons)
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a generic metric is a good idea, especially as the number of privacy policies is expected to increase. IMHO the existign GDPR and CCPA metrics could use the new generalized privacy blocking metric.

func getSyncer(query url.Values, syncersByKey map[string]usersync.Syncer) (usersync.Syncer, error) {
key := query.Get("bidder")
func getSyncer(query url.Values, syncersByKey map[string]usersync.Syncer) (usersync.Syncer, string, error) {
bidderName := query.Get("bidder")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this change is valid. The "bidder" parameter is actually the syncer key. There is often a 1:1 relationship between them, but it's possible to have a 1:many.

Let's discuss at our next meeting how to address this, since we need the proper bidder name now for activity rule matching. Since we only ever expect the setuid endpoint to be called as a redirect from a cookie sync, we should have the freedom to make this change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch Scott, yeah this function gets passed a query from a cookie sync redirect, where "bidder" would actually grab the syncer key. Maybe that syncersByBidder map that gets passed into NewSetUIDEndpoint() can be used to get the proper bidder name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good though. The problem is there is a 1:many relationship from the syncer key to the bidder name, so we can't be sure which bidder it is. We'll likely be fine if at least one of the linked bidders is allowed, but that's complicated to check. I think the easier approach would be to actually set bidder to the bidder name. SetUID would still need the syncersByBidder map to get the syncer key.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow! Let's discuss it in a team meeting. I'll follow your instructions here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that you closed your bidderNameBySyncer PR. Will your changes be reflected in this PR or will you open a new one for this?

@@ -1,5 +1,9 @@
package usersync

import (
privacyActivity "github.com/prebid/prebid-server/privacy"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent of the privacy interface in this package is to avoid adding a dependency / import. This is just needed for the ActivityResult, so please use a boolean there instead. When evaluating the result, both an Abstain and Allow result should map to true.

@VeronikaSolovei9
Copy link
Contributor Author

All comments are addressed except of one. We need to find the right way to get a bidder name.

Copy link
Contributor

@AlexBVolcy AlexBVolcy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left one comment! Also could you merge with master to fix conflicts!

func getSyncer(query url.Values, syncersByKey map[string]usersync.Syncer) (usersync.Syncer, error) {
key := query.Get("bidder")
func getSyncer(query url.Values, syncersByKey map[string]usersync.Syncer) (usersync.Syncer, string, error) {
bidderName := query.Get("bidder")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw that you closed your bidderNameBySyncer PR. Will your changes be reflected in this PR or will you open a new one for this?

@VeronikaSolovei9
Copy link
Contributor Author

Alex, good point!
I believe this PR will stay the same. It will not be merged until we find a way to pass a bidder name properly.
bidderName here will be actual bidder name, not a syncer key.

)

// Privacy determines which privacy policies will be enforced for a user sync request.
type Privacy interface {
GDPRAllowsHostCookie() bool
GDPRAllowsBidderSync(bidder string) bool
CCPAAllowsBidderSync(bidder string) bool
ActivityAllowsUserSync(bidder string) bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we rename to ActivityAllowsBidderSync to match GDPRAllowsBidderSync and CCPAAllowsBidderSync?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gus, activity name we execute here is syncUser :

case ActivitySyncUser:

and this is how it is in configs:
SyncUser Activity `mapstructure:"syncUser" json:"syncUser"`

I'm open to rename it if you think ActivityAllowsBidderSync is better than ActivityAllowsUserSync.

if len(strSID) > 0 {
gppSID, err := stringutil.StrToInt8Slice(strSID)
if err != nil {
return gdpr.SignalAmbiguous, fmt.Errorf("Error parsing gpp_sid %s", err.Error())
Copy link
Contributor

@AlexBVolcy AlexBVolcy Jul 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For my own understanding is it okay to use fmt when returning an error? I typically use something like errors.New()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I use fmt.Error when I want to pass a parameter to error message. For instance when you want to build dynamic error messages and use %s, %d to format values. fmt.Error returns usual error, it just helps to format the message.

// first and the 'gdpr' and 'gdpr_consent' query params second. If found in both, throws a
// warning. Can also throw a parsing or validation error
func extractGDPRInfo(query url.Values) (reqInfo gdpr.RequestInfo, err error) {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Could you remove this blank line?

Copy link
Contributor

@AlexBVolcy AlexBVolcy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very close to approval, just one last comment

@@ -33,9 +32,6 @@ func NewActivityControl(privacyConf *config.AccountPrivacy) (ActivityControl, er

if privacyConf == nil {
return ac, err
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this turned back from nil to err?

Copy link
Contributor

@AlexBVolcy AlexBVolcy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@guscarreon guscarreon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@VeronikaSolovei9 VeronikaSolovei9 merged commit 555c0a1 into master Jul 27, 2023
3 checks passed
@SyntaxNode SyntaxNode deleted the activity_userSync branch October 2, 2023 17:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants