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  }