go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/tokens/tokens_test.go (about) 1 // Copyright 2015 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tokens 16 17 import ( 18 "context" 19 "fmt" 20 "strings" 21 "testing" 22 "time" 23 24 "go.chromium.org/luci/common/clock" 25 "go.chromium.org/luci/common/clock/testclock" 26 27 "go.chromium.org/luci/server/secrets" 28 "go.chromium.org/luci/server/secrets/testsecrets" 29 30 . "github.com/smartystreets/goconvey/convey" 31 . "go.chromium.org/luci/common/testing/assertions" 32 ) 33 34 func Example() { 35 kind := TokenKind{ 36 Algo: TokenAlgoHmacSHA256, 37 Expiration: 30 * time.Minute, 38 SecretKey: "secret_key_name", 39 Version: 1, 40 } 41 42 ctx := context.Background() 43 ctx, _ = testclock.UseTime(ctx, time.Unix(1444945245, 0)) 44 ctx = secrets.Use(ctx, &testsecrets.Store{}) 45 46 // Make a token. 47 token, err := kind.Generate(ctx, []byte("state"), map[string]string{"k": "v"}, 0) 48 if err != nil { 49 fmt.Printf("error - %s\n", err) 50 return 51 } 52 fmt.Printf("token - %s\n", token) 53 54 // Validate it, extract embedded data. 55 embedded, err := kind.Validate(ctx, token, []byte("state")) 56 if err != nil { 57 fmt.Printf("error - %s\n", err) 58 return 59 } 60 fmt.Printf("embedded - %s\n", embedded) 61 62 // Output: 63 // token - AXsiX2kiOiIxNDQ0OTQ1MjQ1MDAwIiwiayI6InYifQJ85lxSuuoYaZ2q0ecPB5-E8Wv9J2Llh0D4Y4wRWCbx 64 // embedded - map[k:v] 65 } 66 67 func TestGenerate(t *testing.T) { 68 kind := TokenKind{ 69 Algo: TokenAlgoHmacSHA256, 70 Expiration: 30 * time.Minute, 71 SecretKey: "secret_key_name", 72 Version: 1, 73 } 74 75 Convey("Works", t, func() { 76 ctx := testContext() 77 token, err := kind.Generate(ctx, nil, nil, 0) 78 So(token, ShouldNotEqual, "") 79 So(err, ShouldBeNil) 80 }) 81 82 Convey("Empty key", t, func() { 83 ctx := testContext() 84 token, err := kind.Generate(ctx, nil, map[string]string{"": "v"}, 0) 85 So(token, ShouldEqual, "") 86 So(err, ShouldErrLike, "empty key") 87 }) 88 89 Convey("Forbidden key", t, func() { 90 ctx := testContext() 91 token, err := kind.Generate(ctx, nil, map[string]string{"_x": "v"}, 0) 92 So(token, ShouldEqual, "") 93 So(err, ShouldErrLike, "bad key") 94 }) 95 96 Convey("Negative exp", t, func() { 97 ctx := testContext() 98 token, err := kind.Generate(ctx, nil, nil, -time.Minute) 99 So(token, ShouldEqual, "") 100 So(err, ShouldErrLike, "expiration can't be negative") 101 }) 102 103 Convey("Unknown algo", t, func() { 104 ctx := testContext() 105 k2 := kind 106 k2.Algo = "unknown" 107 token, err := k2.Generate(ctx, nil, nil, 0) 108 So(token, ShouldEqual, "") 109 So(err, ShouldErrLike, "unknown algo") 110 }) 111 } 112 113 func TestValidate(t *testing.T) { 114 kind := TokenKind{ 115 Algo: TokenAlgoHmacSHA256, 116 Expiration: 30 * time.Minute, 117 SecretKey: "secret_key_name", 118 Version: 1, 119 } 120 121 Convey("Works", t, func() { 122 ctx := testContext() 123 token, err := kind.Generate(ctx, []byte("state"), map[string]string{ 124 "key1": "value1", 125 "key2": "value2", 126 }, 0) 127 So(err, ShouldBeNil) 128 129 // Good state. 130 embedded, err := kind.Validate(ctx, token, []byte("state")) 131 So(err, ShouldBeNil) 132 So(embedded, ShouldResemble, map[string]string{ 133 "key1": "value1", 134 "key2": "value2", 135 }) 136 137 // Bad state. 138 embedded, err = kind.Validate(ctx, token, []byte("???")) 139 So(err, ShouldErrLike, "bad token MAC") 140 141 // Not base64. 142 embedded, err = kind.Validate(ctx, "?"+token[1:], []byte("state")) 143 So(err, ShouldErrLike, "illegal base64 data") 144 145 // Corrupted. 146 embedded, err = kind.Validate(ctx, "X"+token[1:], []byte("state")) 147 So(err, ShouldErrLike, "bad token MAC") 148 149 // Too short. 150 embedded, err = kind.Validate(ctx, token[:10], []byte("state")) 151 So(err, ShouldErrLike, "too small") 152 153 // Make it expired by rolling time forward. 154 tc := clock.Get(ctx).(testclock.TestClock) 155 tc.Add(31 * time.Minute) 156 embedded, err = kind.Validate(ctx, token, []byte("state")) 157 So(err, ShouldErrLike, "token expired") 158 }) 159 160 Convey("Custom expiration time", t, func() { 161 ctx := testContext() 162 token, err := kind.Generate(ctx, nil, nil, time.Minute) 163 So(err, ShouldBeNil) 164 165 // Valid. 166 _, err = kind.Validate(ctx, token, nil) 167 So(err, ShouldBeNil) 168 169 // No longer valid. 170 tc := clock.Get(ctx).(testclock.TestClock) 171 tc.Add(2 * time.Minute) 172 _, err = kind.Validate(ctx, token, nil) 173 So(err, ShouldErrLike, "token expired") 174 }) 175 176 Convey("Unknown algo", t, func() { 177 ctx := testContext() 178 k2 := kind 179 k2.Algo = "unknown" 180 _, err := k2.Validate(ctx, "token", nil) 181 So(err, ShouldErrLike, "unknown algo") 182 }) 183 184 Convey("Padding", t, func() { 185 // Produce tokens of various length to ensure base64 padding stripping 186 // works. 187 ctx := testContext() 188 for i := 0; i < 10; i++ { 189 data := map[string]string{ 190 "k": strings.Repeat("a", i), 191 } 192 token, err := kind.Generate(ctx, nil, data, 0) 193 So(err, ShouldBeNil) 194 extracted, err := kind.Validate(ctx, token, nil) 195 So(err, ShouldBeNil) 196 So(extracted, ShouldResemble, data) 197 } 198 }) 199 } 200 201 func testContext() context.Context { 202 ctx := context.Background() 203 ctx, _ = testclock.UseTime(ctx, time.Unix(1444945245, 0)) 204 ctx = secrets.Use(ctx, &testsecrets.Store{}) 205 return ctx 206 }