Skip to content

Commit 2c30f89

Browse files
authored
bug 1567320: add support for funnel_experiment and funnel_variation a… (#95)
* bug 1567320: add support for funnel_experiment and funnel_variation attribution keys * add funnel params to test and fix length test * add original test back * validator: increase accepted code length to 600 * s/funnel_// * set maxUnescapedCodeLen to match AttributionCode.jsm * fix pingdom test * fix tests and allow blank inputs * add ua attribution key * return error if code is empty * tests to account for code is empty
1 parent 9cc6e29 commit 2c30f89

File tree

4 files changed

+61
-23
lines changed

4 files changed

+61
-23
lines changed

attributioncode/validator.go

+35-13
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,33 @@ import (
55
"crypto/sha256"
66
"encoding/base64"
77
"encoding/hex"
8+
"fmt"
89
"net/url"
910
"time"
1011

1112
"github.com/pkg/errors"
1213
"github.com/sirupsen/logrus"
1314
)
1415

16+
// Set to match https://searchfox.org/mozilla-central/rev/a92ed79b0bc746159fc31af1586adbfa9e45e264/browser/components/attribution/AttributionCode.jsm#24
17+
const maxUnescapedCodeLen = 1010
18+
1519
var validAttributionKeys = map[string]bool{
16-
"source": true,
17-
"medium": true,
18-
"campaign": true,
19-
"content": true,
20+
"source": true,
21+
"medium": true,
22+
"campaign": true,
23+
"content": true,
24+
"experiment": true,
25+
"variation": true,
26+
"ua": true,
27+
}
28+
29+
// If any of these are not set in the incoming payload, they will be set to '(not set)'
30+
var requiredAttributionKeys = []string{
31+
"source",
32+
"medium",
33+
"campaign",
34+
"content",
2035
}
2136

2237
var base64Decoder = base64.URLEncoding.WithPadding('.')
@@ -38,6 +53,12 @@ func NewValidator(hmacKey string, timeout time.Duration) *Validator {
3853
// Validate validates and sanitizes attribution code and signature
3954
func (v *Validator) Validate(code, sig string) (string, error) {
4055
logEntry := logrus.WithField("b64code", code)
56+
57+
if code == "" {
58+
logEntry.Error("code is empty")
59+
return "", errors.New("code is empty")
60+
}
61+
4162
if len(code) > 5000 {
4263
logEntry.WithField("code_len", len(code)).Error("code longer than 5000 characters")
4364
return "", errors.New("base64 code longer than 5000 characters")
@@ -50,9 +71,10 @@ func (v *Validator) Validate(code, sig string) (string, error) {
5071
}
5172

5273
logEntry = logrus.WithField("code", unEscapedCode)
53-
if len(unEscapedCode) > 200 {
54-
logEntry.WithField("code_len", len(code)).Error("code longer than 200 characters")
55-
return "", errors.New("code longer than 200 characters")
74+
if len(unEscapedCode) > maxUnescapedCodeLen {
75+
errMsg := fmt.Sprintf("code longer than %d characters", maxUnescapedCodeLen)
76+
logEntry.WithField("code_len", len(code)).Error(errMsg)
77+
return "", errors.New(errMsg)
5678
}
5779

5880
vals, err := url.ParseQuery(string(unEscapedCode))
@@ -78,17 +100,17 @@ func (v *Validator) Validate(code, sig string) (string, error) {
78100
}
79101
}
80102

81-
// all keys are included
82-
if len(vals) != len(validAttributionKeys) {
83-
logrus.Error("code is missing keys")
84-
return "", errors.New("code is missing keys")
85-
}
86-
87103
if source := vals.Get("source"); !isWhitelisted(source) {
88104
logrus.WithField("source", source).Error("source is not in whitelist")
89105
vals.Set("source", "(other)")
90106
}
91107

108+
for _, val := range requiredAttributionKeys {
109+
if vals.Get(val) == "" {
110+
vals.Set(val, "(not set)")
111+
}
112+
}
113+
92114
return url.QueryEscape(vals.Encode()), nil
93115
}
94116

attributioncode/validator_test.go

+14-6
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ func TestValidateAttributionCode(t *testing.T) {
5757
"c291cmNlPXd3dy5nb29nbGUuY29tJm1lZGl1bT1vcmdhbmljJmNhbXBhaWduPShub3Qgc2V0KSZjb250ZW50PShub3Qgc2V0KQ..", // source=www.google.com&medium=organic&campaign=(not set)&content=(not set)
5858
"campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26medium%3Dorganic%26source%3Dwww.google.com",
5959
},
60+
{
61+
"c291cmNlPXd3dy5nb29nbGUuY29tJm1lZGl1bT1vcmdhbmljJmNhbXBhaWduPShub3Qgc2V0KQ..", // source=www.google.com&medium=organic&campaign=(not set)
62+
"campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26medium%3Dorganic%26source%3Dwww.google.com",
63+
},
64+
{
65+
"c291cmNlPXd3dy5nb29nbGUuY29tJm1lZGl1bT1vcmdhbmljJmNhbXBhaWduPShub3Qgc2V0KSZjb250ZW50PShub3Qgc2V0KSZ2YXJpYXRpb249ZjEmZXhwZXJpbWVudD1lMQ..", // source=www.google.com&medium=organic&campaign=(not set)&content=(not set)&variation=f1&experiment=e1
66+
"campaign%3D%2528not%2Bset%2529%26content%3D%2528not%2Bset%2529%26experiment%3De1%26medium%3Dorganic%26source%3Dwww.google.com%26variation%3Df1",
67+
},
6068
}
6169
for _, c := range validCodes {
6270
res, err := v.Validate(c.In, "")
@@ -77,17 +85,17 @@ func TestValidateAttributionCode(t *testing.T) {
7785
"base64 code longer than 5000 characters",
7886
},
7987
{
80-
"c291cmNlPWdvb2dsZS5jb21tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbSZtZWRpdW09b3JnYW5pYyZjYW1wYWlnbj0obm90IHNldCkmY29udGVudD0obm90IHNldCk.", // source=google.commmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm&medium=organic&campaign=(not set)&content=(not set)
81-
"code longer than 200 characters",
82-
},
83-
{
84-
"bWVkaXVtPW9yZ2FuaWMmY2FtcGFpZ249KG5vdCBzZXQpJmNvbnRlbnQ9KG5vdCBzZXQp", // "medium=organic&campaign=(not set)&content=(not set)",
85-
"code is missing keys",
88+
"c291cmNlPWdvb2dsZS5jb21tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tbW1tJm1lZGl1bT1vcmdhbmljJmNhbXBhaWduPShub3Qgc2V0KSZjb250ZW50PShub3Qgc2V0KQ..", // source=google.commmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmmm&medium=organic&campaign=(not set)&content=(not set)
89+
"code longer than 1010 characters",
8690
},
8791
{
8892
"bm90YXJlYWxrZXk9b3JnYW5pYyZjYW1wYWlnbj0obm90IHNldCkmY29udGVudD0obm90IHNldCk.", // "notarealkey=organic&campaign=(not set)&content=(not set)",
8993
"notarealkey is not a valid attribution key",
9094
},
95+
{
96+
"", // blank
97+
"code is empty",
98+
},
9199
}
92100
for _, c := range invalidCodes {
93101
_, err := v.Validate(c.In, "")

stubservice/main.go

+2
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,8 @@ func pingdomHandler(w http.ResponseWriter, req *http.Request) {
148148
attrQuery.Set("medium", "pingdom")
149149
attrQuery.Set("campaign", "pingdom")
150150
attrQuery.Set("content", "pingdom")
151+
attrQuery.Set("experiment", "pingdom")
152+
attrQuery.Set("variation", "pingdom")
151153
b64AttrQuery := base64.URLEncoding.WithPadding('.').EncodeToString([]byte(attrQuery.Encode()))
152154

153155
query := url.Values{}

testing/stubtestclient/main.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@ import (
1515
var (
1616
baseURL string
1717

18-
campaign string
19-
content string
20-
medium string
21-
source string
18+
campaign string
19+
content string
20+
medium string
21+
source string
22+
experiment string
23+
variation string
2224

2325
lang string
2426
os string
@@ -50,6 +52,8 @@ func init() {
5052
flag.StringVar(&content, "content", "testcontent", "content")
5153
flag.StringVar(&medium, "medium", "testmedium", "medium")
5254
flag.StringVar(&source, "source", "mozilla.com", "source")
55+
flag.StringVar(&experiment, "experiment", "exp1", "experiment")
56+
flag.StringVar(&variation, "variation", "var1", "variation")
5357

5458
flag.StringVar(&lang, "lang", "en-US", "")
5559
flag.StringVar(&os, "os", "win", "")
@@ -66,6 +70,8 @@ func genCode() string {
6670
query.Set("content", content)
6771
query.Set("medium", medium)
6872
query.Set("source", source)
73+
query.Set("experiment", experiment)
74+
query.Set("variation", variation)
6975
query.Set("timestamp", fmt.Sprintf("%d", time.Now().UTC().Unix()))
7076

7177
b64Query := base64.URLEncoding.WithPadding('.').EncodeToString([]byte(query.Encode()))

0 commit comments

Comments
 (0)