initial split from 04756eee65e20001b3a44af6249b60c52af446b7

This commit is contained in:
Darko Luketic 2021-11-14 15:05:55 +01:00
commit 9f706be828
14 changed files with 4745 additions and 0 deletions

4
README.md Normal file
View File

@ -0,0 +1,4 @@
# oidc
This package is split off from [go-oidc-middleware](https://github.com/XenitAB/go-oidc-middleware)
to provide a re-usable package.

191
cty.go Normal file
View File

@ -0,0 +1,191 @@
package oidc
import (
"fmt"
"github.com/zclconf/go-cty/cty"
"github.com/zclconf/go-cty/cty/gocty"
)
func getCtyValueWithImpliedType(a interface{}) (cty.Value, error) {
if a == nil {
return cty.NilVal, fmt.Errorf("input is nil")
}
valueType, err := gocty.ImpliedType(a)
if err != nil {
return cty.NilVal, fmt.Errorf("unable to get cty.Type: %w", err)
}
return getCtyValueWithType(a, valueType)
}
func getCtyValueWithType(a interface{}, vt cty.Type) (cty.Value, error) {
if a == nil {
return cty.NilVal, fmt.Errorf("input value is nil")
}
if vt == cty.NilType {
return cty.NilVal, fmt.Errorf("input type is nil")
}
value, err := gocty.ToCtyValue(a, vt)
if err != nil {
// we should never receive this error
return cty.NilVal, fmt.Errorf("unable to get cty.Value: %w", err)
}
return value, nil
}
func getCtyValues(a interface{}, b interface{}) (cty.Value, cty.Value, error) {
first, err := getCtyValueWithImpliedType(a)
if err != nil {
return cty.NilVal, cty.NilVal, err
}
second, err := getCtyValueWithType(b, first.Type())
if err != nil {
return cty.NilVal, cty.NilVal, err
}
return first, second, nil
}
func isCtyPrimitiveValueValid(a cty.Value, b cty.Value) bool {
if !isCtyTypeSame(a, b) {
return false
}
if getCtyType(a) != primitiveCtyType {
return false
}
return a.Equals(b) == cty.True
}
func isCtyListValid(a cty.Value, b cty.Value) bool {
if !isCtyTypeSame(a, b) {
return false
}
if getCtyType(a) != listCtyType {
return false
}
listA := a.AsValueSlice()
listB := b.AsValueSlice()
for i := range listA {
if !ctyListContains(listB, listA[i]) {
return false
}
}
return true
}
func isCtyMapValid(a cty.Value, b cty.Value) bool {
if !isCtyTypeSame(a, b) {
return false
}
if getCtyType(a) != mapCtyType {
return false
}
mapA := a.AsValueMap()
mapB := b.AsValueMap()
for k := range mapA {
mapBValue, ok := mapB[k]
if !ok {
return false
}
err := isCtyValueValid(mapA[k], mapBValue)
if err != nil {
return false
}
}
return true
}
func ctyListContains(a []cty.Value, b cty.Value) bool {
for i := range a {
err := isCtyValueValid(a[i], b)
if err == nil {
return true
}
}
return false
}
func isCtyTypeSame(a cty.Value, b cty.Value) bool {
return a.Type().Equals(b.Type())
}
func isCtyValueValid(a cty.Value, b cty.Value) error {
if !isCtyTypeSame(a, b) {
return fmt.Errorf("should be type %s, was type: %s", a.Type().GoString(), b.Type().GoString())
}
switch getCtyType(a) {
case primitiveCtyType:
valid := isCtyPrimitiveValueValid(a, b)
if !valid {
return fmt.Errorf("should be %s, was: %s", a.GoString(), b.GoString())
}
case listCtyType:
valid := isCtyListValid(a, b)
if !valid {
return fmt.Errorf("should contain %s, received: %s", a.GoString(), b.GoString())
}
case mapCtyType:
valid := isCtyMapValid(a, b)
if !valid {
return fmt.Errorf("should contain %s, received: %s", a.GoString(), b.GoString())
}
default:
return fmt.Errorf("non-implemented type - should be %s, received: %s", a.GoString(), b.GoString())
}
return nil
}
type ctyType int
const (
unknownCtyType = iota
primitiveCtyType
listCtyType
mapCtyType
)
func getCtyType(a cty.Value) ctyType {
if a.Type().IsPrimitiveType() {
return primitiveCtyType
}
switch {
case a.Type().IsListType():
return listCtyType
// Adding the other cases to make it easier in the
// future to build logic for more types.
case a.Type().IsMapType():
return mapCtyType
case a.Type().IsSetType():
return unknownCtyType
case a.Type().IsObjectType():
return unknownCtyType
case a.Type().IsTupleType():
return unknownCtyType
case a.Type().IsCapsuleType():
return unknownCtyType
}
return unknownCtyType
}

642
cty_test.go Normal file
View File

@ -0,0 +1,642 @@
package oidc
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
func TestGetCtyValueWithImpliedType(t *testing.T) {
cases := []struct {
testDescription string
input interface{}
expectedCtyType cty.Type
expectedError bool
}{
{
testDescription: "string as cty.String",
input: "foo",
expectedCtyType: cty.String,
expectedError: false,
},
{
testDescription: "string number as cty.String",
input: "1234",
expectedCtyType: cty.String,
expectedError: false,
},
{
testDescription: "int as cty.Number",
input: int(1234),
expectedCtyType: cty.Number,
expectedError: false,
},
{
testDescription: "float64 as cty.Number",
input: float64(1234),
expectedCtyType: cty.Number,
expectedError: false,
},
{
testDescription: "list of strings as cty.List(cty.String)",
input: []string{"foo"},
expectedCtyType: cty.List(cty.String),
expectedError: false,
},
{
testDescription: "string map as cty.Map(cty.String)",
input: map[string]string{"foo": "bar"},
expectedCtyType: cty.Map(cty.String),
expectedError: false,
},
{
testDescription: "empty array of interfaces as cty.NilType and error",
input: []interface{}{},
expectedCtyType: cty.NilType,
expectedError: true,
},
{
testDescription: "nil as cty.NilType and error",
input: nil,
expectedCtyType: cty.NilType,
expectedError: true,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
v, err := getCtyValueWithImpliedType(c.input)
require.Equal(t, c.expectedCtyType, v.Type())
if c.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}
func TestGetCtyValueWithType(t *testing.T) {
cases := []struct {
testDescription string
input interface{}
inputType cty.Type
expectedCtyType cty.Type
expectedError bool
}{
{
testDescription: "string as cty.String",
input: "foo",
inputType: cty.String,
expectedCtyType: cty.String,
expectedError: false,
},
{
testDescription: "string number as cty.String",
input: "1234",
inputType: cty.String,
expectedCtyType: cty.String,
expectedError: false,
},
{
testDescription: "int as cty.Number",
input: int(1234),
inputType: cty.Number,
expectedCtyType: cty.Number,
expectedError: false,
},
{
testDescription: "float64 as cty.Number",
input: float64(1234),
inputType: cty.Number,
expectedCtyType: cty.Number,
expectedError: false,
},
{
testDescription: "list of strings as cty.List(cty.String)",
input: []string{"foo"},
inputType: cty.List(cty.String),
expectedCtyType: cty.List(cty.String),
expectedError: false,
},
{
testDescription: "string map as cty.Map(cty.String)",
input: map[string]string{"foo": "bar"},
inputType: cty.Map(cty.String),
expectedCtyType: cty.Map(cty.String),
expectedError: false,
},
{
testDescription: "empty array of interfaces as cty.NilType and error",
input: []interface{}{},
inputType: cty.NilType,
expectedCtyType: cty.NilType,
expectedError: true,
},
{
testDescription: "nil as cty.NilType and error",
input: nil,
inputType: cty.NilType,
expectedCtyType: cty.NilType,
expectedError: true,
},
{
testDescription: "interface list in an interface map",
input: map[string]interface{}{
"foo": map[string]interface{}{
"bar": []interface{}{
"uno",
"dos",
"tres",
},
},
},
inputType: cty.Map(cty.Map(cty.List(cty.String))),
expectedCtyType: cty.Map(cty.Map(cty.List(cty.String))),
expectedError: false,
},
{
testDescription: "interface list in an interface map with wrong input type",
input: map[string]interface{}{
"foo": map[string]interface{}{
"bar": []interface{}{
"uno",
"dos",
"tres",
},
},
},
inputType: cty.String,
expectedCtyType: cty.NilType,
expectedError: true,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
v, err := getCtyValueWithType(c.input, c.inputType)
require.Equal(t, c.expectedCtyType, v.Type())
if c.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}
func TestGetCtyValues(t *testing.T) {
var a, b interface{}
a = "foo"
b = "bar"
ctyA, ctyB, err := getCtyValues(a, b)
require.NoError(t, err)
require.Equal(t, "cty.StringVal(\"foo\")", ctyA.GoString())
require.Equal(t, "cty.StringVal(\"bar\")", ctyB.GoString())
}
func TestIsCtyPrimitiveValueValid(t *testing.T) {
cases := []struct {
testDescription string
firstValue cty.Value
secondValue cty.Value
expectedResult bool
}{
{
testDescription: "same input strings",
firstValue: cty.StringVal("foo"),
secondValue: cty.StringVal("foo"),
expectedResult: true,
},
{
testDescription: "same input numbers",
firstValue: cty.NumberIntVal(1337),
secondValue: cty.NumberIntVal(1337),
expectedResult: true,
},
{
testDescription: "different input strings",
firstValue: cty.StringVal("foo"),
secondValue: cty.StringVal("bar"),
expectedResult: false,
},
{
testDescription: "different input numbers",
firstValue: cty.NumberIntVal(1337),
secondValue: cty.NumberIntVal(7331),
expectedResult: false,
},
{
testDescription: "different types",
firstValue: cty.StringVal("bar"),
secondValue: cty.NumberIntVal(7331),
expectedResult: false,
},
{
testDescription: "input list",
firstValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
secondValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
expectedResult: false,
},
{
testDescription: "input map",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
expectedResult: false,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
result := isCtyPrimitiveValueValid(c.firstValue, c.secondValue)
require.Equal(t, c.expectedResult, result)
}
}
func TestIsCtyListValid(t *testing.T) {
cases := []struct {
testDescription string
firstValue cty.Value
secondValue cty.Value
expectedResult bool
}{
{
testDescription: "same input string",
firstValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
secondValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
expectedResult: true,
},
{
testDescription: "same input int",
firstValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1337)}),
secondValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1337)}),
expectedResult: true,
},
{
testDescription: "different input string",
firstValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
secondValue: cty.ListVal([]cty.Value{cty.StringVal("bar")}),
expectedResult: false,
},
{
testDescription: "different input int",
firstValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1337)}),
secondValue: cty.ListVal([]cty.Value{cty.NumberIntVal(7331)}),
expectedResult: false,
},
{
testDescription: "same input multiple second",
firstValue: cty.ListVal([]cty.Value{cty.StringVal("bar")}),
secondValue: cty.ListVal([]cty.Value{cty.StringVal("foo"), cty.StringVal("bar"), cty.StringVal("baz")}),
expectedResult: true,
},
{
testDescription: "input string",
firstValue: cty.StringVal("foo"),
secondValue: cty.StringVal("foo"),
expectedResult: false,
},
{
testDescription: "same input map",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
expectedResult: false,
},
{
testDescription: "different types",
firstValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
secondValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1337)}),
expectedResult: false,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
result := isCtyListValid(c.firstValue, c.secondValue)
require.Equal(t, c.expectedResult, result)
}
}
func TestIsCtyMapValid(t *testing.T) {
cases := []struct {
testDescription string
firstValue cty.Value
secondValue cty.Value
expectedResult bool
}{
{
testDescription: "same input string",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
expectedResult: true,
},
{
testDescription: "same input int",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(1337)}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(1337)}),
expectedResult: true,
},
{
testDescription: "different input string",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("bar")}),
expectedResult: false,
},
{
testDescription: "different input int",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(1337)}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(7331)}),
expectedResult: false,
},
{
testDescription: "different types",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(1337)}),
expectedResult: false,
},
{
testDescription: "input string",
firstValue: cty.StringVal("foo"),
secondValue: cty.StringVal("foo"),
expectedResult: false,
},
{
testDescription: "input list",
firstValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
secondValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
expectedResult: false,
},
{
testDescription: "same input multiple second",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
secondValue: cty.MapVal(map[string]cty.Value{"a": cty.StringVal("b"), "foo": cty.StringVal("foo"), "c": cty.StringVal("d")}),
expectedResult: true,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
result := isCtyMapValid(c.firstValue, c.secondValue)
require.Equal(t, c.expectedResult, result)
}
}
func TestCtyListContains(t *testing.T) {
cases := []struct {
testDescription string
slice []cty.Value
value cty.Value
expectedResult bool
}{
{
testDescription: "same input string",
slice: []cty.Value{cty.StringVal("foo")},
value: cty.StringVal("foo"),
expectedResult: true,
},
{
testDescription: "same input int",
slice: []cty.Value{cty.NumberIntVal(1337)},
value: cty.NumberIntVal(1337),
expectedResult: true,
},
{
testDescription: "different input string",
slice: []cty.Value{cty.StringVal("foo")},
value: cty.StringVal("bar"),
expectedResult: false,
},
{
testDescription: "different input int",
slice: []cty.Value{cty.NumberIntVal(1337)},
value: cty.NumberIntVal(7331),
expectedResult: false,
},
{
testDescription: "same input string multiple",
slice: []cty.Value{cty.StringVal("foo"), cty.StringVal("bar"), cty.StringVal("baz")},
value: cty.StringVal("bar"),
expectedResult: true,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
result := ctyListContains(c.slice, c.value)
require.Equal(t, c.expectedResult, result)
}
}
func TestIsCtyTypeSame(t *testing.T) {
cases := []struct {
testDescription string
firstValue cty.Value
secondValue cty.Value
expectedResult bool
}{
{
testDescription: "same input strings",
firstValue: cty.StringVal("foo"),
secondValue: cty.StringVal("foo"),
expectedResult: true,
},
{
testDescription: "same input numbers",
firstValue: cty.NumberIntVal(1337),
secondValue: cty.NumberIntVal(1337),
expectedResult: true,
},
{
testDescription: "different input strings",
firstValue: cty.StringVal("foo"),
secondValue: cty.StringVal("bar"),
expectedResult: true,
},
{
testDescription: "different input numbers",
firstValue: cty.NumberIntVal(1337),
secondValue: cty.NumberIntVal(7331),
expectedResult: true,
},
{
testDescription: "different types",
firstValue: cty.StringVal("foo"),
secondValue: cty.NumberIntVal(1337),
expectedResult: false,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
result := isCtyTypeSame(c.firstValue, c.secondValue)
require.Equal(t, c.expectedResult, result)
}
}
func TestIsCtyValueValid(t *testing.T) {
cases := []struct {
testDescription string
firstValue cty.Value
secondValue cty.Value
expectedError bool
}{
{
testDescription: "same input strings",
firstValue: cty.StringVal("foo"),
secondValue: cty.StringVal("foo"),
expectedError: false,
},
{
testDescription: "same input numbers",
firstValue: cty.NumberIntVal(1337),
secondValue: cty.NumberIntVal(1337),
expectedError: false,
},
{
testDescription: "different input strings",
firstValue: cty.StringVal("foo"),
secondValue: cty.StringVal("bar"),
expectedError: true,
},
{
testDescription: "different input numbers",
firstValue: cty.NumberIntVal(1337),
secondValue: cty.NumberIntVal(7331),
expectedError: true,
},
{
testDescription: "different types",
firstValue: cty.StringVal("foo"),
secondValue: cty.NumberIntVal(1337),
expectedError: true,
},
{
testDescription: "same input list string",
firstValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
secondValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
expectedError: false,
},
{
testDescription: "same input list int",
firstValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1337)}),
secondValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1337)}),
expectedError: false,
},
{
testDescription: "different input list string",
firstValue: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
secondValue: cty.ListVal([]cty.Value{cty.StringVal("bar")}),
expectedError: true,
},
{
testDescription: "different input list int",
firstValue: cty.ListVal([]cty.Value{cty.NumberIntVal(1337)}),
secondValue: cty.ListVal([]cty.Value{cty.NumberIntVal(7331)}),
expectedError: true,
},
{
testDescription: "same input map string",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
expectedError: false,
},
{
testDescription: "same input map int",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(1337)}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(1337)}),
expectedError: false,
},
{
testDescription: "different input map string",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("bar")}),
expectedError: true,
},
{
testDescription: "different input map int",
firstValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(1337)}),
secondValue: cty.MapVal(map[string]cty.Value{"foo": cty.NumberIntVal(7331)}),
expectedError: true,
},
{
testDescription: "non-imlemented type",
firstValue: cty.SetVal([]cty.Value{cty.StringVal("foo")}),
secondValue: cty.SetVal([]cty.Value{cty.StringVal("foo")}),
expectedError: true,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
err := isCtyValueValid(c.firstValue, c.secondValue)
if c.expectedError {
require.Error(t, err)
} else {
require.NoError(t, err)
}
}
}
func TestGetCtyType(t *testing.T) {
cases := []struct {
testDescription string
input cty.Value
expectedType ctyType
}{
{
testDescription: "string is primitiveCtyType",
input: cty.StringVal("foo"),
expectedType: primitiveCtyType,
},
{
testDescription: "int is primitiveCtyType",
input: cty.NumberIntVal(1337),
expectedType: primitiveCtyType,
},
{
testDescription: "float is primitiveCtyType",
input: cty.NumberFloatVal(1337),
expectedType: primitiveCtyType,
},
{
testDescription: "bool is primitiveCtyType",
input: cty.BoolVal(true),
expectedType: primitiveCtyType,
},
{
testDescription: "slice is listCtyType",
input: cty.ListVal([]cty.Value{cty.StringVal("foo")}),
expectedType: listCtyType,
},
{
testDescription: "map is mapCtyType",
input: cty.MapVal(map[string]cty.Value{"foo": cty.StringVal("foo")}),
expectedType: mapCtyType,
},
{
testDescription: "set is unknownCtyType",
input: cty.SetVal([]cty.Value{cty.StringVal("foo")}),
expectedType: unknownCtyType,
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
resultType := getCtyType(c.input)
require.Equal(t, c.expectedType, resultType)
}
}

47
go.mod Normal file
View File

@ -0,0 +1,47 @@
module git.icod.de/dalu/oidc
go 1.17
require (
github.com/lestrrat-go/jwx v1.2.11
github.com/stretchr/testify v1.7.0
github.com/xenitab/dispans v0.0.10
github.com/zclconf/go-cty v1.10.0
go.uber.org/ratelimit v0.2.0
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
)
require (
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
github.com/go-oauth2/oauth2/v4 v4.4.2 // indirect
github.com/go-session/session v3.1.2+incompatible // indirect
github.com/goccy/go-json v0.7.10 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
github.com/lestrrat-go/blackmagic v1.0.0 // indirect
github.com/lestrrat-go/httpcc v1.0.0 // indirect
github.com/lestrrat-go/iter v1.0.1 // indirect
github.com/lestrrat-go/option v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tidwall/btree v0.6.1 // indirect
github.com/tidwall/buntdb v1.2.7 // indirect
github.com/tidwall/gjson v1.11.0 // indirect
github.com/tidwall/grect v0.1.3 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/rtred v0.1.2 // indirect
github.com/tidwall/tinyqueue v0.1.1 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211104170005-ce137452f963 // indirect
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

548
go.sum Normal file
View File

@ -0,0 +1,548 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI=
github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129/go.mod h1:rFgpPQZYZ8vdbc+48xibu8ALc3yeyd64IhHS+PU6Yyg=
github.com/andybalholm/brotli v1.0.0 h1:7UCwP93aiSfvWpapti8g88vVVGp2qqtGyePsSuDafo4=
github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0-20210816181553-5444fa50b93d/go.mod h1:tmAIfUFEirG/Y8jhZ9M+h36obRZAk/1fcSpXwAVlfqE=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1/go.mod h1:hyedUtir6IdtD/7lIxGeCxkaw7y45JueMRL4DIyJDKs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-oauth2/oauth2/v4 v4.4.2 h1:tWQlR5I4/qhWiyOME67BAFmo622yi+2mm7DMm8DpMdg=
github.com/go-oauth2/oauth2/v4 v4.4.2/go.mod h1:K4DemYzNwwYnIDOPdHtX/7SlO0AHdtlphsTgE7lA3PA=
github.com/go-session/session v3.1.2+incompatible h1:yStchEObKg4nk2F7JGE7KoFIrA/1Y078peagMWcrncg=
github.com/go-session/session v3.1.2+incompatible/go.mod h1:8B3iivBQjrz/JtC68Np2T1yBBLxTan3mn/3OM0CyRt0=
github.com/goccy/go-json v0.7.10 h1:ulhbuNe1JqE68nMRXXTJRrUu0uhouf0VevLINxQq4Ec=
github.com/goccy/go-json v0.7.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk=
github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.10.10 h1:a/y8CglcM7gLGYmlbP/stPE5sR3hbhFRUjCBfd/0B3I=
github.com/klauspost/compress v1.10.10/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
github.com/lestrrat-go/jwx v1.2.11 h1:e9BS5NQ003hxXogNsgf5fEWf01ZJvj4Aj1qy7Dykqm8=
github.com/lestrrat-go/jwx v1.2.11/go.mod h1:25DcLbNWArPA/Ew5CcBmewl32cJKxOk5cbepBsIJFzw=
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.13.0/go.mod h1:+REjRxOmWfHCjfv9TTWB1jD1Frx4XydAD3zm1lskyM0=
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/assert v0.1.0 h1:aWcKyRBUAdLoVebxo95N7+YZVTFF/ASTr7BN4sLP6XI=
github.com/tidwall/assert v0.1.0/go.mod h1:QLYtGyeqse53vuELQheYl9dngGCJQ+mTtlxcktb+Kj8=
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274/go.mod h1:huei1BkDWJ3/sLXmO+bsCNELL+Bp2Kks9OLyQFkzvA8=
github.com/tidwall/btree v0.6.1 h1:75VVgBeviiDO+3g4U+7+BaNBNhNINxB0ULPT3fs9pMY=
github.com/tidwall/btree v0.6.1/go.mod h1:TzIRzen6yHbibdSfK6t8QimqbUnoxUSrZfeW7Uob0q4=
github.com/tidwall/buntdb v1.1.2/go.mod h1:xAzi36Hir4FarpSHyfuZ6JzPJdjRZ8QlLZSntE2mqlI=
github.com/tidwall/buntdb v1.2.7 h1:SIyObKAymzLyGhDeIhVk2Yc1/EwfCC75Uyu77CHlVoA=
github.com/tidwall/buntdb v1.2.7/go.mod h1:b6KvZM27x/8JLI5hgRhRu60pa3q0Tz9c50TyD46OHUM=
github.com/tidwall/gjson v1.3.4/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls=
github.com/tidwall/gjson v1.10.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/gjson v1.11.0 h1:C16pk7tQNiH6VlCrtIXL1w8GaOsi1X3W8KDkE1BuYd4=
github.com/tidwall/gjson v1.11.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/grect v0.0.0-20161006141115-ba9a043346eb/go.mod h1:lKYYLFIr9OIgdgrtgkZ9zgRxRdvPYsExnYBsEAd8W5M=
github.com/tidwall/grect v0.1.3 h1:z9YwQAMUxVSBde3b7Sl8Da37rffgNfZ6Fq6h9t6KdXE=
github.com/tidwall/grect v0.1.3/go.mod h1:8GMjwh3gPZVpLBI/jDz9uslCe0dpxRpWDdtN0lWAS/E=
github.com/tidwall/lotsa v1.0.2 h1:dNVBH5MErdaQ/xd9s769R31/n2dXavsQ0Yf4TMEHHw8=
github.com/tidwall/lotsa v1.0.2/go.mod h1:X6NiU+4yHA3fE3Puvpnn1XMDrFZrE9JO2/w+UMuqgR8=
github.com/tidwall/match v1.0.1/go.mod h1:LujAq0jyVjBy028G1WhWfIzbpQfMO8bBZ6Tyb0+pL9E=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/rtred v0.1.2 h1:exmoQtOLvDoO8ud++6LwVsAMTu0KPzLTUrMln8u1yu8=
github.com/tidwall/rtred v0.1.2/go.mod h1:hd69WNXQ5RP9vHd7dqekAz+RIdtfBogmglkZSRxCHFQ=
github.com/tidwall/rtree v0.0.0-20180113144539-6cd427091e0e/go.mod h1:/h+UnNGt0IhNNJLkGikcdcJqm66zGD/uJGMRxK/9+Ao=
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563/go.mod h1:mLqSmt7Dv/CNneF2wfcChfN1rvapyQr01LGKnKex0DQ=
github.com/tidwall/tinyqueue v0.1.1 h1:SpNEvEggbpyN5DIReaJ2/1ndroY8iyEGxPYxoSaymYE=
github.com/tidwall/tinyqueue v0.1.1/go.mod h1:O/QNHwrnjqr6IHItYrzoHAKYhBkLI67Q096fQP5zMYw=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.14.0 h1:67bfuW9azCMwW/Jlq/C+VeihNpAuJMWkYPBig1gdi3A=
github.com/valyala/fasthttp v1.14.0/go.mod h1:ol1PCaL0dX20wC0htZ7sYCsvCYmrouYra0zHzaclZhE=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xenitab/dispans v0.0.10 h1:S+gSUM14rDJWK7MYNrjb8JbjeQPip6mlNJyLX+g7Agc=
github.com/xenitab/dispans v0.0.10/go.mod h1:CqxCO5jE4j2+6BK7z3pofaVTenZfRMqWtUAR+ucbi58=
github.com/xenitab/pkg v0.0.3 h1:1hFU9GWxXgKhqvZFvoVznz5j63OlM5Fl3UacEhvPKdU=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0=
github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/ratelimit v0.2.0 h1:UQE2Bgi7p2B85uP5dC2bbRtig0C+OeNRnNEafLjsLPA=
go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6mUg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201217014255-9d1352758620/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 h1:7I4JAnoQBe7ZtJcBaYHi5UtiO8tQHbUSXxL+pnGRANg=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20211104170005-ce137452f963 h1:8gJUadZl+kWvZBqG/LautX0X6qe5qTC2VI/3V3NBRAY=
golang.org/x/net v0.0.0-20211104170005-ce137452f963/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

167
keyhandler.go Normal file
View File

@ -0,0 +1,167 @@
package oidc
import (
"context"
"fmt"
"net/http"
"sync"
"time"
"github.com/lestrrat-go/jwx/jwk"
"go.uber.org/ratelimit"
"golang.org/x/sync/semaphore"
)
type keyHandler struct {
sync.RWMutex
jwksURI string
disableKeyID bool
keySet jwk.Set
fetchTimeout time.Duration
keyUpdateSemaphore *semaphore.Weighted
keyUpdateChannel chan keyUpdate
keyUpdateCount int
keyUpdateLimiter ratelimit.Limiter
httpClient *http.Client
}
type keyUpdate struct {
keySet jwk.Set
err error
}
func newKeyHandler(httpClient *http.Client, jwksUri string, fetchTimeout time.Duration, keyUpdateRPS uint, disableKeyID bool) (*keyHandler, error) {
h := &keyHandler{
jwksURI: jwksUri,
disableKeyID: disableKeyID,
fetchTimeout: fetchTimeout,
keyUpdateSemaphore: semaphore.NewWeighted(int64(1)),
keyUpdateChannel: make(chan keyUpdate),
keyUpdateLimiter: ratelimit.New(int(keyUpdateRPS)),
httpClient: httpClient,
}
ctx := context.Background()
_, err := h.updateKeySet(ctx)
if err != nil {
return nil, err
}
return h, nil
}
func (h *keyHandler) updateKeySet(ctx context.Context) (jwk.Set, error) {
ctx, cancel := context.WithTimeout(ctx, h.fetchTimeout)
defer cancel()
keySet, err := jwk.Fetch(ctx, h.jwksURI, jwk.WithHTTPClient(h.httpClient))
if err != nil {
return nil, fmt.Errorf("unable to fetch keys from %q: %w", h.jwksURI, err)
}
if h.disableKeyID && keySet.Len() != 1 {
return nil, fmt.Errorf("keyID is disabled, but received a keySet with more than one key: %d", keySet.Len())
}
h.Lock()
h.keySet = keySet
h.keyUpdateCount++
h.Unlock()
return keySet, nil
}
// waitForUpdateKeySetSet handles concurrent requests to update the jwks as well as rate limiting.
func (h *keyHandler) waitForUpdateKeySetAndGetKeySet(ctx context.Context) (jwk.Set, error) {
// ok will be false if there's already an update in progress.
ok := h.keyUpdateSemaphore.TryAcquire(1)
if ok {
defer h.keyUpdateSemaphore.Release(1)
_ = h.keyUpdateLimiter.Take()
keySet, err := h.updateKeySet(ctx)
result := keyUpdate{
keySet,
err,
}
// start go routine to handle all requests waiting for result.
go func(res keyUpdate) {
// for each request waiting for update, send result to them.
for {
select {
case h.keyUpdateChannel <- res:
default:
return
}
}
}(result)
return keySet, err
}
// wait for the request that is updating keys and return the result from it
result := <-h.keyUpdateChannel
return result.keySet, result.err
}
func (h *keyHandler) waitForUpdateKeySetAndGetKey(ctx context.Context) (jwk.Key, error) {
keySet, err := h.waitForUpdateKeySetAndGetKeySet(ctx)
if err != nil {
return nil, err
}
key, found := keySet.Get(0)
if !found {
return nil, fmt.Errorf("no key found")
}
return key, nil
}
func (h *keyHandler) getKey(ctx context.Context, keyID string) (jwk.Key, error) {
if h.disableKeyID {
return h.getKeyWithoutKeyID()
}
return h.getKeyFromID(ctx, keyID)
}
func (h *keyHandler) getKeySet() jwk.Set {
h.RLock()
defer h.RUnlock()
return h.keySet
}
func (h *keyHandler) getKeyFromID(ctx context.Context, keyID string) (jwk.Key, error) {
keySet := h.getKeySet()
key, found := keySet.LookupKeyID(keyID)
if !found {
updatedKeySet, err := h.waitForUpdateKeySetAndGetKeySet(ctx)
if err != nil {
return nil, fmt.Errorf("unable to update key set for key %q: %w", keyID, err)
}
updatedKey, found := updatedKeySet.LookupKeyID(keyID)
if !found {
return nil, fmt.Errorf("unable to find key %q", keyID)
}
return updatedKey, nil
}
return key, nil
}
func (h *keyHandler) getKeyWithoutKeyID() (jwk.Key, error) {
keySet := h.getKeySet()
key, found := keySet.Get(0)
if !found {
return nil, fmt.Errorf("no key found")
}
return key, nil
}

339
keyhandler_test.go Normal file
View File

@ -0,0 +1,339 @@
package oidc
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
"github.com/lestrrat-go/jwx/jwk"
"github.com/stretchr/testify/require"
"github.com/xenitab/dispans/server"
)
func TestNewKeyHandler(t *testing.T) {
ctx := context.Background()
op := server.NewTesting(t)
issuer := op.GetURL(t)
discoveryUri := GetDiscoveryUriFromIssuer(issuer)
jwksUri, err := getJwksUriFromDiscoveryUri(http.DefaultClient, discoveryUri, 10*time.Millisecond)
require.NoError(t, err)
keyHandler, err := newKeyHandler(http.DefaultClient, jwksUri, 10*time.Millisecond, 100, false)
require.NoError(t, err)
keySet1 := keyHandler.getKeySet()
require.Equal(t, 1, keySet1.Len())
expectedKey1, ok := keySet1.Get(0)
require.True(t, ok)
token1 := op.GetToken(t)
keyID1, err := getKeyIDFromTokenString(token1.AccessToken)
require.NoError(t, err)
// Test valid key id
key1, err := keyHandler.getKeyFromID(ctx, keyID1)
require.NoError(t, err)
require.Equal(t, expectedKey1, key1)
// Test invalid key id
_, err = keyHandler.getKeyFromID(ctx, "foo")
require.Error(t, err)
// Test with rotated keys
op.RotateKeys(t)
token2 := op.GetToken(t)
keyID2, err := getKeyIDFromTokenString(token2.AccessToken)
require.NoError(t, err)
key2, err := keyHandler.getKeyFromID(ctx, keyID2)
require.NoError(t, err)
keySet2 := keyHandler.getKeySet()
require.Equal(t, 1, keySet2.Len())
expectedKey2, ok := keySet2.Get(0)
require.True(t, ok)
require.Equal(t, expectedKey2, key2)
// Test that old key doesn't match new key
require.NotEqual(t, key1, key2)
// Validate that error is returned when using fake jwks uri
_, err = newKeyHandler(http.DefaultClient, "http://foo.bar/baz", 10*time.Millisecond, 100, false)
require.Error(t, err)
// Validate that error is returned when keys are rotated,
// new token with new key and jwks uri isn't accessible
op.RotateKeys(t)
token3 := op.GetToken(t)
keyID3, err := getKeyIDFromTokenString(token3.AccessToken)
require.NoError(t, err)
op.Close(t)
_, err = keyHandler.getKeyFromID(ctx, keyID3)
require.Error(t, err)
}
func TestUpdate(t *testing.T) {
ctx := context.Background()
op := server.NewTesting(t)
issuer := op.GetURL(t)
discoveryUri := GetDiscoveryUriFromIssuer(issuer)
jwksUri, err := getJwksUriFromDiscoveryUri(http.DefaultClient, discoveryUri, 10*time.Millisecond)
require.NoError(t, err)
rateLimit := uint(10)
keyHandler, err := newKeyHandler(http.DefaultClient, jwksUri, 10*time.Millisecond, rateLimit, false)
require.NoError(t, err)
require.Equal(t, 1, keyHandler.keyUpdateCount)
_, err = keyHandler.waitForUpdateKeySetAndGetKeySet(ctx)
require.NoError(t, err)
require.Equal(t, 2, keyHandler.keyUpdateCount)
concurrentUpdate := func(workers int) {
wg1 := sync.WaitGroup{}
wg1.Add(1)
wg2 := sync.WaitGroup{}
for i := 0; i < workers; i++ {
wg2.Add(1)
go func() {
wg1.Wait()
_, err := keyHandler.waitForUpdateKeySetAndGetKeySet(ctx)
require.NoError(t, err)
wg2.Done()
}()
}
wg1.Done()
wg2.Wait()
}
concurrentUpdate(100)
require.Equal(t, 3, keyHandler.keyUpdateCount)
concurrentUpdate(100)
require.Equal(t, 4, keyHandler.keyUpdateCount)
concurrentUpdate(100)
require.Equal(t, 5, keyHandler.keyUpdateCount)
multipleConcurrentUpdates := func() {
wg1 := sync.WaitGroup{}
wg1.Add(1)
wg2 := sync.WaitGroup{}
for i := 0; i < 10; i++ {
wg2.Add(1)
go func() {
wg1.Wait()
concurrentUpdate(10)
wg2.Done()
}()
}
wg1.Done()
wg2.Wait()
}
multipleConcurrentUpdates()
require.Equal(t, 6, keyHandler.keyUpdateCount)
// test rate limit
time.Sleep(10 * time.Millisecond)
start := time.Now()
_, err = keyHandler.waitForUpdateKeySetAndGetKeySet(ctx)
require.NoError(t, err)
stop := time.Now()
expectedStop := start.Add(time.Second / time.Duration(rateLimit))
require.WithinDuration(t, expectedStop, stop, 20*time.Millisecond)
require.Equal(t, 7, keyHandler.keyUpdateCount)
}
func TestNewKeyHandlerWithKeyIDDisabled(t *testing.T) {
disableKeyID := true
keySets := testNewTestKeySet(t)
keySets.setKeys(testNewKeySet(t, 1, disableKeyID))
testServer := testNewJwksServer(t, keySets)
defer testServer.Close()
_, err := newKeyHandler(http.DefaultClient, testServer.URL, 10*time.Millisecond, 100, disableKeyID)
require.NoError(t, err)
keySets.setKeys(testNewKeySet(t, 2, disableKeyID))
_, err = newKeyHandler(http.DefaultClient, testServer.URL, 10*time.Millisecond, 100, disableKeyID)
require.Error(t, err)
}
func TestNewKeyHandlerWithKeyIDEnabled(t *testing.T) {
disableKeyID := false
keySets := testNewTestKeySet(t)
keySets.setKeys(testNewKeySet(t, 1, disableKeyID))
testServer := testNewJwksServer(t, keySets)
defer testServer.Close()
_, err := newKeyHandler(http.DefaultClient, testServer.URL, 10*time.Millisecond, 100, disableKeyID)
require.NoError(t, err)
keySets.setKeys(testNewKeySet(t, 2, disableKeyID))
_, err = newKeyHandler(http.DefaultClient, testServer.URL, 10*time.Millisecond, 100, disableKeyID)
require.NoError(t, err)
}
func TestUpdateKeySetWithKeyIDDisabled(t *testing.T) {
ctx := context.Background()
disableKeyID := true
keySets := testNewTestKeySet(t)
keySets.setKeys(testNewKeySet(t, 1, disableKeyID))
testServer := testNewJwksServer(t, keySets)
defer testServer.Close()
keyHandler, err := newKeyHandler(http.DefaultClient, testServer.URL, 10*time.Millisecond, 100, disableKeyID)
require.NoError(t, err)
_, err = keyHandler.updateKeySet(ctx)
require.NoError(t, err)
keySets.setKeys(testNewKeySet(t, 2, disableKeyID))
_, err = keyHandler.updateKeySet(ctx)
require.Error(t, err)
}
func TestUpdateKeySetWithKeyIDEnabled(t *testing.T) {
ctx := context.Background()
disableKeyID := false
keySets := testNewTestKeySet(t)
keySets.setKeys(testNewKeySet(t, 1, disableKeyID))
testServer := testNewJwksServer(t, keySets)
defer testServer.Close()
keyHandler, err := newKeyHandler(http.DefaultClient, testServer.URL, 100*time.Millisecond, 100, disableKeyID)
require.NoError(t, err)
_, err = keyHandler.updateKeySet(ctx)
require.NoError(t, err)
keySets.setKeys(testNewKeySet(t, 2, disableKeyID))
_, err = keyHandler.updateKeySet(ctx)
require.NoError(t, err)
}
func TestWaitForUpdateKeySetWithKeyIDDisabled(t *testing.T) {
ctx := context.Background()
disableKeyID := true
keySets := testNewTestKeySet(t)
keySets.setKeys(testNewKeySet(t, 1, disableKeyID))
testServer := testNewJwksServer(t, keySets)
defer testServer.Close()
keyHandler, err := newKeyHandler(http.DefaultClient, testServer.URL, 10*time.Millisecond, 100, disableKeyID)
require.NoError(t, err)
_, err = keyHandler.waitForUpdateKeySetAndGetKey(ctx)
require.NoError(t, err)
keySets.setKeys(testNewKeySet(t, 2, disableKeyID))
_, err = keyHandler.waitForUpdateKeySetAndGetKey(ctx)
require.Error(t, err)
}
func TestWaitForUpdateKeySetWithKeyIDEnabled(t *testing.T) {
ctx := context.Background()
disableKeyID := false
keySets := testNewTestKeySet(t)
keySets.setKeys(testNewKeySet(t, 1, disableKeyID))
testServer := testNewJwksServer(t, keySets)
defer testServer.Close()
keyHandler, err := newKeyHandler(http.DefaultClient, testServer.URL, 10*time.Millisecond, 100, disableKeyID)
require.NoError(t, err)
_, err = keyHandler.waitForUpdateKeySetAndGetKey(ctx)
require.NoError(t, err)
keySets.setKeys(testNewKeySet(t, 2, disableKeyID))
_, err = keyHandler.waitForUpdateKeySetAndGetKey(ctx)
require.NoError(t, err)
}
func testNewJwksServer(t *testing.T, keySets *testKeySets) *httptest.Server {
t.Helper()
testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
err := json.NewEncoder(w).Encode(keySets.publicKeySet)
require.NoError(t, err)
}))
return testServer
}
type testKeySets struct {
privateKeySet jwk.Set
publicKeySet jwk.Set
}
func testNewTestKeySet(t *testing.T) *testKeySets {
t.Helper()
return &testKeySets{}
}
func (k *testKeySets) setKeys(privKeySet jwk.Set, pubKeySet jwk.Set) {
k.privateKeySet = privKeySet
k.publicKeySet = pubKeySet
}
func testNewKeySet(t *testing.T, numKeys int, disableKeyID bool) (jwk.Set, jwk.Set) {
t.Helper()
privKeySet := jwk.NewSet()
pubKeySet := jwk.NewSet()
for i := 0; i < numKeys; i++ {
privKey, pubKey := testNewKey(t)
if disableKeyID {
err := privKey.Remove(jwk.KeyIDKey)
require.NoError(t, err)
err = pubKey.Remove(jwk.KeyIDKey)
require.NoError(t, err)
}
privKeySet.Add(privKey)
pubKeySet.Add(pubKey)
}
return privKeySet, pubKeySet
}

391
oidc.go Normal file
View File

@ -0,0 +1,391 @@
package oidc
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strings"
"time"
"git.icod.de/dalu/oidc/options"
"github.com/lestrrat-go/jwx/jwa"
"github.com/lestrrat-go/jwx/jwk"
"github.com/lestrrat-go/jwx/jws"
"github.com/lestrrat-go/jwx/jwt"
)
var (
errSignatureVerification = fmt.Errorf("failed to verify signature")
)
type handler struct {
issuer string
discoveryUri string
jwksUri string
jwksFetchTimeout time.Duration
jwksRateLimit uint
fallbackSignatureAlgorithm jwa.SignatureAlgorithm
allowedTokenDrift time.Duration
requiredAudience string
requiredTokenType string
requiredClaims map[string]interface{}
disableKeyID bool
httpClient *http.Client
keyHandler *keyHandler
}
func NewHandler(setters ...options.Option) (*handler, error) {
opts := options.New(setters...)
h := &handler{
issuer: opts.Issuer,
discoveryUri: opts.DiscoveryUri,
jwksUri: opts.JwksUri,
jwksFetchTimeout: opts.JwksFetchTimeout,
jwksRateLimit: opts.JwksRateLimit,
allowedTokenDrift: opts.AllowedTokenDrift,
requiredTokenType: opts.RequiredTokenType,
requiredAudience: opts.RequiredAudience,
requiredClaims: opts.RequiredClaims,
disableKeyID: opts.DisableKeyID,
httpClient: opts.HttpClient,
}
if h.issuer == "" {
return nil, fmt.Errorf("issuer is empty")
}
if h.discoveryUri == "" {
h.discoveryUri = GetDiscoveryUriFromIssuer(h.issuer)
}
if opts.FallbackSignatureAlgorithm != "" {
alg, err := getSignatureAlgorithmFromString(opts.FallbackSignatureAlgorithm)
if err != nil {
return nil, fmt.Errorf("FallbackSignatureAlgorithm not accepted: %w", err)
}
h.fallbackSignatureAlgorithm = alg
}
if !opts.LazyLoadJwks {
err := h.loadJwks()
if err != nil {
return nil, fmt.Errorf("unable to load jwks: %w", err)
}
}
return h, nil
}
func (h *handler) loadJwks() error {
if h.jwksUri == "" {
jwksUri, err := getJwksUriFromDiscoveryUri(h.httpClient, h.discoveryUri, 5*time.Second)
if err != nil {
return fmt.Errorf("unable to fetch jwksUri from discoveryUri (%s): %w", h.discoveryUri, err)
}
h.jwksUri = jwksUri
}
keyHandler, err := newKeyHandler(h.httpClient, h.jwksUri, h.jwksFetchTimeout, h.jwksRateLimit, h.disableKeyID)
if err != nil {
return fmt.Errorf("unable to initialize keyHandler: %w", err)
}
h.keyHandler = keyHandler
return nil
}
func (h *handler) SetIssuer(issuer string) {
h.issuer = issuer
}
func (h *handler) SetDiscoveryUri(discoveryUri string) {
h.discoveryUri = discoveryUri
}
type ParseTokenFunc func(ctx context.Context, tokenString string) (jwt.Token, error)
func (h *handler) ParseToken(ctx context.Context, tokenString string) (jwt.Token, error) {
if h.keyHandler == nil {
err := h.loadJwks()
if err != nil {
return nil, fmt.Errorf("unable to load jwks: %w", err)
}
}
tokenTypeValid := isTokenTypeValid(h.requiredTokenType, tokenString)
if !tokenTypeValid {
return nil, fmt.Errorf("token type %q required", h.requiredTokenType)
}
keyID := ""
if !h.disableKeyID {
var err error
keyID, err = getKeyIDFromTokenString(tokenString)
if err != nil {
return nil, err
}
}
key, err := h.keyHandler.getKey(ctx, keyID)
if err != nil {
return nil, fmt.Errorf("unable to get public key: %w", err)
}
alg, err := getSignatureAlgorithm(key.KeyType(), key.Algorithm(), h.fallbackSignatureAlgorithm)
if err != nil {
return nil, err
}
token, err := getAndValidateTokenFromString(tokenString, key, alg)
if err != nil {
if h.disableKeyID && errors.Is(err, errSignatureVerification) {
updatedKey, err := h.keyHandler.waitForUpdateKeySetAndGetKey(ctx)
if err != nil {
return nil, err
}
alg, err := getSignatureAlgorithm(key.KeyType(), key.Algorithm(), h.fallbackSignatureAlgorithm)
if err != nil {
return nil, err
}
token, err = getAndValidateTokenFromString(tokenString, updatedKey, alg)
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
validExpiration := isTokenExpirationValid(token.Expiration(), h.allowedTokenDrift)
if !validExpiration {
return nil, fmt.Errorf("token has expired: %s", token.Expiration())
}
validIssuer := isTokenIssuerValid(h.issuer, token.Issuer())
if !validIssuer {
return nil, fmt.Errorf("required issuer %q was not found, received: %s", h.issuer, token.Issuer())
}
validAudience := isTokenAudienceValid(h.requiredAudience, token.Audience())
if !validAudience {
return nil, fmt.Errorf("required audience %q was not found, received: %v", h.requiredAudience, token.Audience())
}
if h.requiredClaims != nil {
tokenClaims, err := token.AsMap(ctx)
if err != nil {
return nil, fmt.Errorf("unable to get token claims: %w", err)
}
err = isRequiredClaimsValid(h.requiredClaims, tokenClaims)
if err != nil {
return nil, fmt.Errorf("unable to validate required claims: %w", err)
}
}
return token, nil
}
func GetDiscoveryUriFromIssuer(issuer string) string {
return fmt.Sprintf("%s/.well-known/openid-configuration", strings.TrimSuffix(issuer, "/"))
}
func getJwksUriFromDiscoveryUri(httpClient *http.Client, discoveryUri string, fetchTimeout time.Duration) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), fetchTimeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, discoveryUri, nil)
if err != nil {
return "", err
}
req.Header.Set("Accept", "application/json")
res, err := httpClient.Do(req)
if err != nil {
return "", err
}
bodyBytes, err := io.ReadAll(res.Body)
if err != nil {
return "", err
}
err = res.Body.Close()
if err != nil {
return "", err
}
var discoveryData struct {
JwksUri string `json:"jwks_uri"`
}
err = json.Unmarshal(bodyBytes, &discoveryData)
if err != nil {
return "", err
}
if discoveryData.JwksUri == "" {
return "", fmt.Errorf("JwksUri is empty")
}
return discoveryData.JwksUri, nil
}
func getKeyIDFromTokenString(tokenString string) (string, error) {
headers, err := getHeadersFromTokenString(tokenString)
if err != nil {
return "", err
}
keyID := headers.KeyID()
if keyID == "" {
return "", fmt.Errorf("token header does not contain key id (kid)")
}
return keyID, nil
}
func getTokenTypeFromTokenString(tokenString string) (string, error) {
headers, err := getHeadersFromTokenString(tokenString)
if err != nil {
return "", err
}
tokenType := headers.Type()
if tokenType == "" {
return "", fmt.Errorf("token header does not contain type (typ)")
}
return tokenType, nil
}
func getHeadersFromTokenString(tokenString string) (jws.Headers, error) {
msg, err := jws.ParseString(tokenString)
if err != nil {
return nil, fmt.Errorf("unable to parse tokenString: %w", err)
}
signatures := msg.Signatures()
if len(signatures) != 1 {
return nil, fmt.Errorf("more than one signature in token")
}
headers := signatures[0].ProtectedHeaders()
return headers, nil
}
func isTokenAudienceValid(requiredAudience string, audiences []string) bool {
if requiredAudience == "" {
return true
}
for _, audience := range audiences {
if audience == requiredAudience {
return true
}
}
return false
}
func isTokenExpirationValid(expiration time.Time, allowedDrift time.Duration) bool {
expirationWithAllowedDrift := expiration.Round(0).Add(allowedDrift)
return expirationWithAllowedDrift.After(time.Now())
}
func isTokenIssuerValid(requiredIssuer string, tokenIssuer string) bool {
if requiredIssuer == "" {
return false
}
return tokenIssuer == requiredIssuer
}
func isTokenTypeValid(requiredTokenType string, tokenString string) bool {
if requiredTokenType == "" {
return true
}
tokenType, err := getTokenTypeFromTokenString(tokenString)
if err != nil {
return false
}
if tokenType != requiredTokenType {
return false
}
return true
}
func isRequiredClaimsValid(requiredClaims map[string]interface{}, tokenClaims map[string]interface{}) error {
for requiredKey, requiredValue := range requiredClaims {
tokenValue, ok := tokenClaims[requiredKey]
if !ok {
return fmt.Errorf("token does not have the claim: %s", requiredKey)
}
required, received, err := getCtyValues(requiredValue, tokenValue)
if err != nil {
return err
}
err = isCtyValueValid(required, received)
if err != nil {
return fmt.Errorf("claim %q not valid: %w", requiredKey, err)
}
}
return nil
}
func getAndValidateTokenFromString(tokenString string, key jwk.Key, alg jwa.SignatureAlgorithm) (jwt.Token, error) {
token, err := jwt.ParseString(tokenString, jwt.WithVerify(alg, key))
if err != nil {
if strings.Contains(err.Error(), errSignatureVerification.Error()) {
return nil, errSignatureVerification
}
return nil, err
}
return token, nil
}
func getSignatureAlgorithm(kty jwa.KeyType, keyAlg string, fallbackAlg jwa.SignatureAlgorithm) (jwa.SignatureAlgorithm, error) {
if keyAlg != "" {
return getSignatureAlgorithmFromString(keyAlg)
}
if fallbackAlg != "" {
return fallbackAlg, nil
}
switch kty {
case jwa.RSA:
return jwa.RS256, nil
case jwa.EC:
return jwa.ES256, nil
default:
return "", fmt.Errorf("unable to get signature algorithm with kty=%s, alg=%s, fallbackAlg=%s", kty, keyAlg, fallbackAlg)
}
}
func getSignatureAlgorithmFromString(s string) (jwa.SignatureAlgorithm, error) {
var alg jwa.SignatureAlgorithm
err := alg.Accept(s)
if err != nil {
return "", err
}
return alg, nil
}

1484
oidc_test.go Normal file

File diff suppressed because it is too large Load Diff

264
options/options.go Normal file
View File

@ -0,0 +1,264 @@
package options
import (
"net/http"
"time"
)
// ClaimsContextKeyName is the type for they key value used to pass claims using request context.
// Using separate type because of the following: https://staticcheck.io/docs/checks#SA1029
type ClaimsContextKeyName string
// DefaultClaimsContextKeyName is of type ClaimsContextKeyName and defaults to "claims"
const DefaultClaimsContextKeyName ClaimsContextKeyName = "claims"
// ErrorHandler is called by the middleware if not nil
type ErrorHandler func(description ErrorDescription, err error)
// ErrorDescription is used to pass the description of the error to ErrorHandler
type ErrorDescription string
const (
// GetTokenErrorDescription is returned to ErrorHandler if the middleware is unable to get a token from the request
GetTokenErrorDescription ErrorDescription = "unable to get token string"
// ParseTokenErrorDescription is returned to ErrorHandler if the middleware is unable to parse the token extracted from the request
ParseTokenErrorDescription ErrorDescription = "unable to parse token string"
// ConvertTokenErrorDescription is returned to ErrorHandler if the middleware is unable to convert the token to a map
ConvertTokenErrorDescription ErrorDescription = "unable to convert token to map"
)
// Options defines the options for OIDC Middleware.
type Options struct {
Issuer string
DiscoveryUri string
JwksUri string
JwksFetchTimeout time.Duration
JwksRateLimit uint
FallbackSignatureAlgorithm string
AllowedTokenDrift time.Duration
LazyLoadJwks bool
RequiredTokenType string
RequiredAudience string
RequiredClaims map[string]interface{}
DisableKeyID bool
HttpClient *http.Client
TokenString [][]TokenStringOption
ClaimsContextKeyName ClaimsContextKeyName
ErrorHandler ErrorHandler
}
// New takes Option setters and returns an Options pointer.
// Mainly used by the internal functions and most likely not
// needed by any external application using this library.
func New(setters ...Option) *Options {
opts := &Options{
JwksFetchTimeout: 5 * time.Second,
JwksRateLimit: 1,
AllowedTokenDrift: 10 * time.Second,
HttpClient: http.DefaultClient,
ClaimsContextKeyName: DefaultClaimsContextKeyName,
}
for _, setter := range setters {
setter(opts)
}
return opts
}
// Option returns a function that modifies an Options pointer.
type Option func(*Options)
// WithIssuer sets the Issuer parameter for Options.
// Issuer is the authority that issues the tokens
func WithIssuer(opt string) Option {
return func(opts *Options) {
opts.Issuer = opt
}
}
// WithDiscoveryUri sets the Issuer parameter for an Options pointer.
// DiscoveryUri is where the `jwks_uri` will be grabbed
// Defaults to `fmt.Sprintf("%s/.well-known/openid-configuration", strings.TrimSuffix(issuer, "/"))`
func WithDiscoveryUri(opt string) Option {
return func(opts *Options) {
opts.DiscoveryUri = opt
}
}
// WithJwksUri sets the JwksUri parameter for an Options pointer.
// JwksUri is used to download the public key(s)
// Defaults to the `jwks_uri` from the response of DiscoveryUri
func WithJwksUri(opt string) Option {
return func(opts *Options) {
opts.JwksUri = opt
}
}
// WithJwksFetchTimeout sets the JwksFetchTimeout parameter for an Options pointer.
// JwksFetchTimeout sets the context timeout when downloading the jwks
// Defaults to 5 seconds
func WithJwksFetchTimeout(opt time.Duration) Option {
return func(opts *Options) {
opts.JwksFetchTimeout = opt
}
}
// WithJwksRateLimit sets the JwksFetchTimeout parameter for an Options pointer.
// JwksRateLimit takes an uint and makes sure that the jwks will at a maximum
// be requested these many times per second.
// Defaults to 1 (Request Per Second)
// Please observe: Requests that force update of jwks (like wrong keyID) will be rate limited
func WithJwksRateLimit(opt uint) Option {
return func(opts *Options) {
opts.JwksRateLimit = opt
}
}
// WithFallbackSignatureAlgorithm sets the FallbackSignatureAlgorithm parameter for an Options pointer.
// FallbackSignatureAlgorithm needs to be used when the jwks doesn't contain the alg key.
// If not specified and jwks doesn't contain alg key, will default to:
// - RS256 for key type (kty) RSA
// - ES256 for key type (kty) EC
//
// When specified and jwks contains alg key, alg key from jwks will be used.
//
// Example values (one of them): RS256 RS384 RS512 ES256 ES384 ES512
func WithFallbackSignatureAlgorithm(opt string) Option {
return func(opts *Options) {
opts.FallbackSignatureAlgorithm = opt
}
}
// WithAllowedTokenDrift sets the AllowedTokenDrift parameter for an Options pointer.
// AllowedTokenDrift adds the duration to the token expiration to allow
// for time drift between parties.
// Defaults to 10 seconds
func WithAllowedTokenDrift(opt time.Duration) Option {
return func(opts *Options) {
opts.AllowedTokenDrift = opt
}
}
// WithLazyLoadJwks sets the LazyLoadJwks parameter for an Options pointer.
// LazyLoadJwks makes it possible to use OIDC Discovery without being
// able to load the keys at startup.
// Default setting is disabled.
// Please observe: If enabled, it will always load even though settings
// may be wrong / not working.
func WithLazyLoadJwks(opt bool) Option {
return func(opts *Options) {
opts.LazyLoadJwks = opt
}
}
// WithRequiredTokenType sets the RequiredTokenType parameter for an Options pointer.
// RequiredTokenType is used if only specific tokens should be allowed.
// Default is empty string `""` and means all token types are allowed.
// Use case could be to configure this if the TokenType (set in the header of the JWT)
// should be `JWT` or maybe even `JWT+AT` to differentiate between access tokens and
// id tokens. Not all providers support or use this.
func WithRequiredTokenType(opt string) Option {
return func(opts *Options) {
opts.RequiredTokenType = opt
}
}
// WithRequiredAudience sets the RequiredAudience parameter for an Options pointer.
// RequiredAudience is used to require a specific Audience `aud` in the claims.
// Defaults to empty string `""` and means all audiences are allowed.
func WithRequiredAudience(opt string) Option {
return func(opts *Options) {
opts.RequiredAudience = opt
}
}
// WithRequiredClaims sets the RequiredClaims parameter for an Options pointer.
// RequiredClaims is used to require specific claims in the token
// Defaults to empty map (nil) and won't check for anything else
// Works with primitive types, slices and maps.
// Please observe: slices and strings checks that the token contains it, but more is allowed.
// Required claim []string{"bar"} matches token []string{"foo", "bar", "baz"}
// Required claim map[string]string{{"foo": "bar"}} matches token map[string]string{{"a": "b"},{"foo": "bar"},{"c": "d"}}
//
// Example:
//
// ```go
// map[string]interface{}{
// "foo": "bar",
// "bar": 1337,
// "baz": []string{"bar"},
// "oof": []map[string]string{
// {"bar": "baz"},
// },
// },
// ```
func WithRequiredClaims(opt map[string]interface{}) Option {
return func(opts *Options) {
opts.RequiredClaims = opt
}
}
// WithDisableKeyID sets the DisableKeyID parameter for an Options pointer.
// DisableKeyID adjusts if a KeyID needs to be extracted from the token or not
// Defaults to false and means KeyID is required to be present in both the jwks and token
// The OIDC specification doesn't require KeyID if there's only one key in the jwks:
// https://openid.net/specs/openid-connect-core-1_0.html#Signing
//
// This also means that if enabled, refresh of the jwks will be done if the token can't be
// validated due to invalid key. The JWKS fetch will fail if there's more than one key present.
func WithDisableKeyID(opt bool) Option {
return func(opts *Options) {
opts.DisableKeyID = opt
}
}
// WithHttpClient sets the HttpClient parameter for an Options pointer.
// HttpClient takes a *http.Client for external calls
// Defaults to http.DefaultClient
func WithHttpClient(opt *http.Client) Option {
return func(opts *Options) {
opts.HttpClient = opt
}
}
// WithTokenString sets the TokenString parameter for an Options pointer.
// TokenString makes it possible to configure how the JWT token should be extracted from
// an http header. Not supported by Echo JWT and will be ignored if used by it.
// Defaults to: 'Authorization: Bearer JWT'
func WithTokenString(setters ...TokenStringOption) Option {
var tokenString []TokenStringOption
tokenString = append(tokenString, setters...)
return func(opts *Options) {
opts.TokenString = append(opts.TokenString, tokenString)
}
}
// WithClaimsContextKeyName sets the ClaimsContextKeyName parameter for an Options pointer.
// ClaimsContextKeyName is the name of key that will be used to pass claims using request context.
// Not supported by Echo JWT and will be ignored if used by it.
//
// Important note: If you change this using `options.WithClaimsContextKeyName("foo")`, then
// you also need to use it like this:
// `claims, ok := r.Context().Value(options.ClaimsContextKeyName("foo")).(map[string]interface{})`
//
// Default: `options.DefaultClaimsContextKeyName`
// Used like this: ``claims, ok := r.Context().Value(options.DefaultClaimsContextKeyName).(map[string]interface{})``
//
// When used with gin, it is converted to normal string - by default:
// `claimsValue, found := c.Get("claims")`
func WithClaimsContextKeyName(opt string) Option {
return func(opts *Options) {
opts.ClaimsContextKeyName = ClaimsContextKeyName(opt)
}
}
// WithErrorHandler sets the ErrorHandler parameter for an Options pointer.
// You can pass a function to run custom logic on errors, logging as an example.
// Defaults to nil
func WithErrorHandler(opt ErrorHandler) Option {
return func(opts *Options) {
opts.ErrorHandler = opt
}
}

101
options/options_test.go Normal file
View File

@ -0,0 +1,101 @@
package options
import (
"net/http"
"testing"
"time"
"github.com/stretchr/testify/require"
)
func TestOptions(t *testing.T) {
expectedResult := &Options{
Issuer: "foo",
DiscoveryUri: "foo",
JwksUri: "foo",
JwksFetchTimeout: 1234 * time.Second,
JwksRateLimit: 1234,
FallbackSignatureAlgorithm: "foo",
AllowedTokenDrift: 1234 * time.Second,
LazyLoadJwks: true,
RequiredTokenType: "foo",
RequiredAudience: "foo",
RequiredClaims: map[string]interface{}{
"foo": "bar",
},
DisableKeyID: true,
HttpClient: &http.Client{
Timeout: 1234 * time.Second,
},
TokenString: nil,
ClaimsContextKeyName: ClaimsContextKeyName("foo"),
ErrorHandler: nil,
}
expectedFirstTokenString := &TokenStringOptions{
HeaderName: "foo",
TokenPrefix: "bar_",
ListSeparator: ",",
}
expectedSecondTokenString := &TokenStringOptions{
HeaderName: "too",
TokenPrefix: "lar_",
ListSeparator: "",
}
setters := []Option{
WithIssuer("foo"),
WithDiscoveryUri("foo"),
WithJwksUri("foo"),
WithJwksFetchTimeout(1234 * time.Second),
WithJwksRateLimit(1234),
WithFallbackSignatureAlgorithm("foo"),
WithAllowedTokenDrift(1234 * time.Second),
WithLazyLoadJwks(true),
WithRequiredTokenType("foo"),
WithRequiredAudience("foo"),
WithRequiredClaims(map[string]interface{}{
"foo": "bar",
}),
WithDisableKeyID(true),
WithHttpClient(&http.Client{
Timeout: 1234 * time.Second,
}),
WithTokenString(
WithTokenStringHeaderName("foo"),
WithTokenStringTokenPrefix("bar_"),
WithTokenStringListSeparator(","),
),
WithTokenString(
WithTokenStringHeaderName("too"),
WithTokenStringTokenPrefix("lar_"),
),
WithClaimsContextKeyName("foo"),
WithErrorHandler(nil),
}
result := &Options{}
for _, setter := range setters {
setter(result)
}
resultFirstTokenString := &TokenStringOptions{}
resultSecondTokenString := &TokenStringOptions{}
for _, setter := range result.TokenString[0] {
setter(resultFirstTokenString)
}
for _, setter := range result.TokenString[1] {
setter(resultSecondTokenString)
}
// Needed or else expectedResult can't be compared to result
result.TokenString = nil
require.Equal(t, expectedResult, result)
require.Equal(t, expectedFirstTokenString, resultFirstTokenString)
require.Equal(t, expectedSecondTokenString, resultSecondTokenString)
}

69
options/tokenstring.go Normal file
View File

@ -0,0 +1,69 @@
package options
// TokenStringOptions handles the settings for how to extract the token from a request.
type TokenStringOptions struct {
HeaderName string
TokenPrefix string
ListSeparator string
PostExtractionFn func(string) (string, error)
}
// NewTokenString takes TokenStringOption setters and returns
// a TokenStringOptions pointer.
// Mainly used by the internal functions and most likely not
// needed by any external application using this library.
func NewTokenString(setters ...TokenStringOption) *TokenStringOptions {
opts := &TokenStringOptions{
HeaderName: "Authorization",
TokenPrefix: "Bearer ",
ListSeparator: "",
PostExtractionFn: nil,
}
for _, setter := range setters {
setter(opts)
}
return opts
}
// TokenStringOption returns a function that modifies a TokenStringOptions pointer.
type TokenStringOption func(*TokenStringOptions)
// WithTokenStringHeaderName sets the HeaderName parameter for a TokenStringOptions pointer.
// HeaderName is the name of the header.
// Default: "Authorization"
func WithTokenStringHeaderName(opt string) TokenStringOption {
return func(opts *TokenStringOptions) {
opts.HeaderName = opt
}
}
// WithTokenStringTokenPrefix sets the TokenPrefix parameter for a TokenStringOptions pointer.
// TokenPrefix defines the prefix that should be trimmed from the header value
// to extract the token.
// Default: "Bearer "
func WithTokenStringTokenPrefix(opt string) TokenStringOption {
return func(opts *TokenStringOptions) {
opts.TokenPrefix = opt
}
}
// WithTokenStringListSeparator sets the ListSeparator parameter for a TokenStringOptions pointer.
// ListSeparator defines if the value of the header is a list or not.
// The value will be split (up to 20 slices) by the ListSeparator.
// Default disabled: ""
func WithTokenStringListSeparator(opt string) TokenStringOption {
return func(opts *TokenStringOptions) {
opts.ListSeparator = opt
}
}
// WithTokenStringPostExtractionFn sets the PostExtractionFn parameter for a TokenStringOptions pointer.
// PostExtractionFn will be run if not nil after a token has been successfully extracted.
// Default: nil
func WithTokenStringPostExtractionFn(opt func(string) (string, error)) TokenStringOption {
return func(opts *TokenStringOptions) {
opts.PostExtractionFn = opt
}
}

93
tokenstring.go Normal file
View File

@ -0,0 +1,93 @@
package oidc
import (
"fmt"
"strings"
"git.icod.de/dalu/oidc/options"
)
type GetHeaderFn func(key string) string
const maxListSeparatorSlices = 20
// GetTokenString extracts a token string.
func GetTokenString(getHeaderFn GetHeaderFn, tokenStringOpts [][]options.TokenStringOption) (string, error) {
optsList := tokenStringOpts
if len(optsList) == 0 {
optsList = append(optsList, []options.TokenStringOption{})
}
var err error
for _, setters := range optsList {
opts := options.NewTokenString(setters...)
var tokenString string
tokenString, err = getTokenString(getHeaderFn, opts)
if err == nil && tokenString != "" {
// if a PostExtractionFn is defined, pass the token to it
if opts.PostExtractionFn != nil {
tokenString, err = opts.PostExtractionFn(tokenString)
if err != nil {
// if the PostExtractionFn returns an error, continue the loop
continue
}
if tokenString == "" {
// if the PostExtractionFn returns an empty string, continue the loop
err = fmt.Errorf("post extraction function returned an empty token string")
continue
}
return tokenString, nil
}
return tokenString, nil
}
}
return "", fmt.Errorf("unable to extract token: %w", err)
}
func getTokenString(getHeaderFn GetHeaderFn, opts *options.TokenStringOptions) (string, error) {
headerValue := getHeaderFn(opts.HeaderName)
if headerValue == "" {
return "", fmt.Errorf("%s header empty", opts.HeaderName)
}
if opts.ListSeparator != "" && strings.Contains(headerValue, opts.ListSeparator) {
headerValueList := strings.SplitN(headerValue, opts.ListSeparator, maxListSeparatorSlices)
return getTokenFromList(headerValueList, opts)
}
return getTokenFromString(headerValue, opts)
}
func getTokenFromList(headerValueList []string, opts *options.TokenStringOptions) (string, error) {
for _, headerValue := range headerValueList {
tokenString, err := getTokenFromString(headerValue, opts)
if err == nil && tokenString != "" {
return tokenString, nil
}
}
return "", fmt.Errorf("no token found in list")
}
func getTokenFromString(headerValue string, opts *options.TokenStringOptions) (string, error) {
if headerValue == "" {
return "", fmt.Errorf("%s header empty", opts.HeaderName)
}
if !strings.HasPrefix(headerValue, opts.TokenPrefix) {
return "", fmt.Errorf("%s header does not begin with: %s", opts.HeaderName, opts.TokenPrefix)
}
token := strings.TrimPrefix(headerValue, opts.TokenPrefix)
if token == "" {
return "", fmt.Errorf("%s header empty after prefix is trimmed", opts.HeaderName)
}
return token, nil
}

405
tokenstring_test.go Normal file
View File

@ -0,0 +1,405 @@
package oidc
import (
"encoding/base64"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"git.icod.de/dalu/oidc/options"
"github.com/stretchr/testify/require"
)
func TestGetTokenString(t *testing.T) {
cases := []struct {
testDescription string
headers map[string][]string
options [][]options.TokenStringOption
expectedToken string
expectedErrorContains string
}{
{
testDescription: "empty headers",
headers: make(map[string][]string),
expectedToken: "",
expectedErrorContains: "Authorization header empty",
},
{
testDescription: "Authorization header empty",
headers: map[string][]string{
"Authorization": {},
},
expectedToken: "",
expectedErrorContains: "Authorization header empty",
},
{
testDescription: "Authorization header empty string",
headers: map[string][]string{
"Authorization": {""},
},
expectedToken: "",
expectedErrorContains: "Authorization header empty",
},
{
testDescription: "Authorization header first empty string",
headers: map[string][]string{
"Authorization": {"", "Bearer foobar"},
},
expectedToken: "",
expectedErrorContains: "Authorization header empty",
},
{
testDescription: "Authorization header single component",
headers: map[string][]string{
"Authorization": {"foo"},
},
expectedToken: "",
expectedErrorContains: "Authorization header does not begin with: Bearer ",
},
{
testDescription: "Authorization header three component",
headers: map[string][]string{
"Authorization": {"foo bar baz"},
},
expectedToken: "",
expectedErrorContains: "Authorization header does not begin with: Bearer ",
},
{
testDescription: "Authorization header two components",
headers: map[string][]string{
"Authorization": {"foo bar"},
},
expectedToken: "",
expectedErrorContains: "Authorization header does not begin with: Bearer ",
},
{
testDescription: "Authorization header two components",
headers: map[string][]string{
"Authorization": {"Bearer foobar"},
},
expectedToken: "foobar",
expectedErrorContains: "",
},
{
testDescription: "test options",
headers: map[string][]string{
"Foo": {"Bar_baz"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Foo"),
options.WithTokenStringTokenPrefix("Bar_"),
},
},
expectedToken: "baz",
expectedErrorContains: "",
},
{
testDescription: "test multiple options second header",
headers: map[string][]string{
"Too": {"Lar_kaz"},
"Foo": {"Bar_baz"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Foo"),
options.WithTokenStringTokenPrefix("Bar_"),
},
{
options.WithTokenStringHeaderName("Too"),
options.WithTokenStringTokenPrefix("Lar_"),
},
},
expectedToken: "baz",
expectedErrorContains: "",
},
{
testDescription: "test multiple options first header",
headers: map[string][]string{
"Too": {"Lar_kaz"},
"Foo": {"Bar_baz"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Too"),
options.WithTokenStringTokenPrefix("Lar_"),
},
{
options.WithTokenStringHeaderName("Foo"),
options.WithTokenStringTokenPrefix("Bar_"),
},
},
expectedToken: "kaz",
expectedErrorContains: "",
},
{
testDescription: "websockets",
headers: map[string][]string{
"Sec-WebSocket-Protocol": {"Foo.bar,Too.lar,Koo.nar"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Sec-WebSocket-Protocol"),
options.WithTokenStringTokenPrefix("Too."),
options.WithTokenStringListSeparator(","),
},
},
expectedToken: "lar",
expectedErrorContains: "",
},
{
testDescription: "websockets",
headers: map[string][]string{
"Sec-WebSocket-Protocol": {"Foo.bar,Too.lar,Koo.nar"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Sec-WebSocket-Protocol"),
options.WithTokenStringTokenPrefix("Baz."),
options.WithTokenStringListSeparator(","),
},
},
expectedToken: "",
expectedErrorContains: "no token found in list",
},
{
testDescription: "authorization first and and then websockets",
headers: map[string][]string{
"Authorization": {"Bearer foobar"},
"Sec-WebSocket-Protocol": {"Foo.bar,Too.lar,Koo.nar"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Authorization"),
options.WithTokenStringTokenPrefix("Bearer "),
},
{
options.WithTokenStringHeaderName("Sec-WebSocket-Protocol"),
options.WithTokenStringTokenPrefix("Too."),
options.WithTokenStringListSeparator(","),
},
},
expectedToken: "foobar",
expectedErrorContains: "",
},
{
testDescription: "websockets first and then authorization",
headers: map[string][]string{
"Authorization": {"Bearer foobar"},
"Sec-WebSocket-Protocol": {"Foo.bar,Too.lar,Koo.nar"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Sec-WebSocket-Protocol"),
options.WithTokenStringTokenPrefix("Too."),
options.WithTokenStringListSeparator(","),
},
{
options.WithTokenStringHeaderName("Authorization"),
options.WithTokenStringTokenPrefix("Bearer "),
},
},
expectedToken: "lar",
expectedErrorContains: "",
},
{
testDescription: "websockets first and then authorization, but without a token in websockets",
headers: map[string][]string{
"Authorization": {"Bearer foobar"},
"Sec-WebSocket-Protocol": {"Foo.bar"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Sec-WebSocket-Protocol"),
options.WithTokenStringTokenPrefix("Too."),
options.WithTokenStringListSeparator(","),
},
{
options.WithTokenStringHeaderName("Authorization"),
options.WithTokenStringTokenPrefix("Bearer "),
},
},
expectedToken: "foobar",
expectedErrorContains: "",
},
{
testDescription: "one header with PostExtractionFn",
headers: map[string][]string{
"Authorization": {"Bearer Zm9vYmFy"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Authorization"),
options.WithTokenStringTokenPrefix("Bearer "),
options.WithTokenStringPostExtractionFn(func(s string) (string, error) {
bytes, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(bytes), nil
}),
},
},
expectedToken: "foobar",
expectedErrorContains: "",
},
{
testDescription: "two headers with PostExtractionFn error",
headers: map[string][]string{
"Foo": {"Bar_baz"},
"Authorization": {"Bearer foobar"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Authorization"),
options.WithTokenStringTokenPrefix("Bearer "),
options.WithTokenStringPostExtractionFn(func(s string) (string, error) {
return "", fmt.Errorf("fake error")
}),
},
{
options.WithTokenStringHeaderName("Foo"),
options.WithTokenStringTokenPrefix("Bar_"),
},
},
expectedToken: "baz",
expectedErrorContains: "",
},
{
testDescription: "one header with PostExtractionFn error",
headers: map[string][]string{
"Authorization": {"Bearer foobar"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Authorization"),
options.WithTokenStringTokenPrefix("Bearer "),
options.WithTokenStringPostExtractionFn(func(s string) (string, error) {
return "", fmt.Errorf("fake error")
}),
},
},
expectedToken: "",
expectedErrorContains: "fake error",
},
{
testDescription: "one header with PostExtractionFn returns empty string",
headers: map[string][]string{
"Authorization": {"Bearer foobar"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Authorization"),
options.WithTokenStringTokenPrefix("Bearer "),
options.WithTokenStringPostExtractionFn(func(s string) (string, error) {
return "", nil
}),
},
},
expectedToken: "",
expectedErrorContains: "post extraction function returned an empty token string",
},
{
testDescription: "kubernetes websocket test",
headers: map[string][]string{
"Sec-WebSocket-Protocol": {"foo,bar,base64url.bearer.authorization.k8s.io.Rm9vQmFyQmF6,baz,test"},
},
options: [][]options.TokenStringOption{
{
options.WithTokenStringHeaderName("Authorization"),
options.WithTokenStringTokenPrefix("Bearer "),
},
{
options.WithTokenStringHeaderName("Sec-WebSocket-Protocol"),
options.WithTokenStringTokenPrefix("base64url.bearer.authorization.k8s.io."),
options.WithTokenStringListSeparator(","),
options.WithTokenStringPostExtractionFn(func(s string) (string, error) {
bytes, err := base64.RawStdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(bytes), nil
}),
},
},
expectedToken: "FooBarBaz",
expectedErrorContains: "",
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
req := httptest.NewRequest(http.MethodGet, "/", nil)
for k, values := range c.headers {
for _, v := range values {
req.Header.Add(k, v)
}
}
token, err := GetTokenString(req.Header.Get, c.options)
require.Equal(t, c.expectedToken, token)
if c.expectedErrorContains == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), c.expectedErrorContains)
}
}
}
func TestGetTokenFromString(t *testing.T) {
cases := []struct {
testDescription string
options []options.TokenStringOption
headerValue string
expectedToken string
expectedErrorContains string
}{
{
testDescription: "default working",
headerValue: "Bearer foobar",
expectedToken: "foobar",
expectedErrorContains: "",
},
{
testDescription: "empty header",
headerValue: "",
expectedToken: "",
expectedErrorContains: "header empty",
},
{
testDescription: "header doesn't begin with 'Bearer '",
headerValue: "Foo_bar",
expectedToken: "",
expectedErrorContains: "header does not begin with",
},
{
testDescription: "header contains 'Bearer ' but nothing else",
headerValue: "Bearer ",
expectedToken: "",
expectedErrorContains: "header empty after prefix is trimmed",
},
}
for i, c := range cases {
t.Logf("Test iteration %d: %s", i, c.testDescription)
opts := options.NewTokenString(c.options...)
token, err := getTokenFromString(c.headerValue, opts)
require.Equal(t, c.expectedToken, token)
if c.expectedErrorContains == "" {
require.NoError(t, err)
} else {
require.Error(t, err)
require.Contains(t, err.Error(), c.expectedErrorContains)
}
}
}