diff --git a/CHANGELOG.md b/CHANGELOG.md index bec191d..562426c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v0.8.0] +- Add support for key paths in token attribute getters [#52](https://github.com/xmidt-org/bascule/pull/52) + ## [v0.7.0] - Modified URL in context to be a *url.URL [#47](https://github.com/xmidt-org/bascule/pull/47) - Added a ParseURL function into the basculehttp constructor [#47](https://github.com/xmidt-org/bascule/pull/47) @@ -68,7 +71,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added constructor, enforcer, and listener alice decorators - Basic code and structure established -[Unreleased]: https://github.com/xmidt-org/bascule/compare/v0.7.0...HEAD +[Unreleased]: https://github.com/xmidt-org/bascule/compare/v0.8.0...HEAD +[v0.8.0]: https://github.com/xmidt-org/bascule/compare/v0.7.0...v0.8.0 [v0.7.0]: https://github.com/xmidt-org/bascule/compare/v0.6.0...v0.7.0 [v0.6.0]: https://github.com/xmidt-org/bascule/compare/v0.5.0...v0.6.0 [v0.5.0]: https://github.com/xmidt-org/bascule/compare/v0.4.0...v0.5.0 diff --git a/basculehttp/enforcer_test.go b/basculehttp/enforcer_test.go index 319fc79..b8d0163 100644 --- a/basculehttp/enforcer_test.go +++ b/basculehttp/enforcer_test.go @@ -36,7 +36,7 @@ func TestEnforcer(t *testing.T) { enforcer: e2, auth: bascule.Authentication{ Authorization: "jwt", - Token: bascule.NewToken("test", "", bascule.Attributes{}), + Token: bascule.NewToken("test", "", bascule.NewAttributes()), }, expectedStatusCode: http.StatusOK, }, @@ -63,7 +63,7 @@ func TestEnforcer(t *testing.T) { enforcer: e2, auth: bascule.Authentication{ Authorization: "jwt", - Token: bascule.NewToken("", "", bascule.Attributes{}), + Token: bascule.NewToken("", "", bascule.NewAttributes()), }, expectedStatusCode: http.StatusForbidden, }, diff --git a/basculehttp/listener_test.go b/basculehttp/listener_test.go index c17861c..09eee5f 100644 --- a/basculehttp/listener_test.go +++ b/basculehttp/listener_test.go @@ -35,7 +35,7 @@ func TestListenerDecorator(t *testing.T) { ctx := bascule.WithAuthentication(context.Background(), bascule.Authentication{ Authorization: "jwt", - Token: bascule.NewToken("", "", bascule.Attributes{}), + Token: bascule.NewToken("", "", bascule.NewAttributes()), Request: bascule.Request{ URL: u, Method: "get", diff --git a/basculehttp/tokenFactory.go b/basculehttp/tokenFactory.go index ddee6d5..c560daa 100644 --- a/basculehttp/tokenFactory.go +++ b/basculehttp/tokenFactory.go @@ -18,15 +18,15 @@ const ( ) var ( - ErrorMalformedValue = errors.New("expected : in decoded value") - ErrorNotInMap = errors.New("principal not found") - ErrorInvalidPassword = errors.New("invalid password") - ErrorNoProtectedHeader = errors.New("missing protected header") - ErrorNoSigningMethod = errors.New("signing method (alg) is missing or unrecognized") - ErrorUnexpectedPayload = errors.New("payload isn't a map of strings to interfaces") - ErrorUnexpectedPrincipal = errors.New("principal isn't a string") - ErrorInvalidToken = errors.New("token isn't valid") - ErrorUnexpectedClaims = errors.New("claims wasn't MapClaims as expected") + ErrorMalformedValue = errors.New("expected : in decoded value") + ErrorPrincipalNotFound = errors.New("principal not found") + ErrorInvalidPassword = errors.New("invalid password") + ErrorNoProtectedHeader = errors.New("missing protected header") + ErrorNoSigningMethod = errors.New("signing method (alg) is missing or unrecognized") + ErrorUnexpectedPayload = errors.New("payload isn't a map of strings to interfaces") + ErrorInvalidPrincipal = errors.New("principal must be a non-empty string") + ErrorInvalidToken = errors.New("token isn't valid") + ErrorUnexpectedClaims = errors.New("claims wasn't MapClaims as expected") ) // TokenFactory is a strategy interface responsible for creating and validating @@ -63,7 +63,7 @@ func (btf BasicTokenFactory) ParseAndValidate(ctx context.Context, _ *http.Reque principal := string(decoded[:i]) val, ok := btf[principal] if !ok { - return nil, ErrorNotInMap + return nil, ErrorPrincipalNotFound } if val != string(decoded[i+1:]) { // failed authentication @@ -71,7 +71,7 @@ func (btf BasicTokenFactory) ParseAndValidate(ctx context.Context, _ *http.Reque } // "basic" is a placeholder here ... token types won't always map to the // Authorization header. For example, a JWT should have a type of "jwt" or some such, not "bearer" - return bascule.NewToken("basic", principal, bascule.Attributes{}), nil + return bascule.NewToken("basic", principal, bascule.NewAttributes()), nil } // BearerTokenFactory parses and does basic validation for a JWT token. @@ -118,6 +118,7 @@ func (btf BearerTokenFactory) ParseAndValidate(ctx context.Context, _ *http.Requ } claims, ok := jwsToken.Claims.(*bascule.ClaimsWithLeeway) + if !ok { return nil, emperror.Wrap(ErrorUnexpectedClaims, "failed to parse JWS") } @@ -126,12 +127,14 @@ func (btf BearerTokenFactory) ParseAndValidate(ctx context.Context, _ *http.Requ if err != nil { return nil, emperror.WrapWith(err, "failed to get map of claims", "claims struct", claims) } - payload := bascule.Attributes(claimsMap) - principal, ok := payload[jwtPrincipalKey].(string) + jwtClaims := bascule.NewAttributesFromMap(claimsMap) + + principal, ok := jwtClaims.GetString(jwtPrincipalKey) + if !ok { - return nil, emperror.WrapWith(ErrorUnexpectedPrincipal, "failed to get and convert principal", "principal", payload[jwtPrincipalKey], "payload", payload) + return nil, emperror.WrapWith(ErrorInvalidPrincipal, "principal value of proper type not found", "principal", principal, "jwtClaims", claimsMap) } - return bascule.NewToken("jwt", principal, payload), nil + return bascule.NewToken("jwt", principal, jwtClaims), nil } diff --git a/basculehttp/tokenFactory_test.go b/basculehttp/tokenFactory_test.go index 9edac5b..f5c001a 100644 --- a/basculehttp/tokenFactory_test.go +++ b/basculehttp/tokenFactory_test.go @@ -25,7 +25,7 @@ func TestBasicTokenFactory(t *testing.T) { { description: "Sucess", value: base64.StdEncoding.EncodeToString([]byte("user:pass")), - expectedToken: bascule.NewToken("basic", "user", bascule.Attributes{}), + expectedToken: bascule.NewToken("basic", "user", bascule.NewAttributes()), }, { description: "Can't Decode Error", @@ -40,7 +40,7 @@ func TestBasicTokenFactory(t *testing.T) { { description: "Key Not in Map Error", value: base64.StdEncoding.EncodeToString([]byte("u:p")), - expectedErr: ErrorNotInMap, + expectedErr: ErrorPrincipalNotFound, }, { description: "Invalid Password Error", diff --git a/checks.go b/checks.go index ece8761..c21e849 100644 --- a/checks.go +++ b/checks.go @@ -62,7 +62,7 @@ func CreateNonEmptyPrincipalCheck() ValidatorFunc { // it finds. func CreateListAttributeCheck(key string, checks ...func(context.Context, []interface{}) error) ValidatorFunc { return func(ctx context.Context, token Token) error { - val, ok := token.Attributes()[key] + val, ok := token.Attributes().Get(key) if !ok { return fmt.Errorf("couldn't find attribute with key %v", key) } diff --git a/checks_test.go b/checks_test.go index a832bb9..e1b5290 100644 --- a/checks_test.go +++ b/checks_test.go @@ -10,50 +10,60 @@ import ( func TestCreateAllowAllCheck(t *testing.T) { assert := assert.New(t) f := CreateAllowAllCheck() - err := f(context.Background(), NewToken("", "", Attributes{})) + err := f(context.Background(), NewToken("", "", NewAttributes())) assert.Nil(err) } func TestCreateValidTypeCheck(t *testing.T) { assert := assert.New(t) f := CreateValidTypeCheck([]string{"valid", "type"}) - err := f(context.Background(), NewToken("valid", "", Attributes{})) + err := f(context.Background(), NewToken("valid", "", NewAttributes())) assert.Nil(err) - err = f(context.Background(), NewToken("invalid", "", Attributes{})) + err = f(context.Background(), NewToken("invalid", "", NewAttributes())) assert.NotNil(err) } func TestCreateNonEmptyTypeCheck(t *testing.T) { assert := assert.New(t) f := CreateNonEmptyTypeCheck() - err := f(context.Background(), NewToken("type", "", Attributes{})) + err := f(context.Background(), NewToken("type", "", NewAttributes())) assert.Nil(err) - err = f(context.Background(), NewToken("", "", Attributes{})) + err = f(context.Background(), NewToken("", "", NewAttributes())) assert.NotNil(err) } func TestCreateNonEmptyPrincipalCheck(t *testing.T) { assert := assert.New(t) f := CreateNonEmptyPrincipalCheck() - err := f(context.Background(), NewToken("", "principal", Attributes{})) + err := f(context.Background(), NewToken("", "principal", NewAttributes())) assert.Nil(err) - err = f(context.Background(), NewToken("", "", Attributes{})) + err = f(context.Background(), NewToken("", "", NewAttributes())) assert.NotNil(err) } func TestCreateListAttributeCheck(t *testing.T) { assert := assert.New(t) - f := CreateListAttributeCheck("testkey", NonEmptyStringListCheck) - err := f(context.Background(), NewToken("", "", map[string]interface{}{"testkey": []interface{}{"a", "b", "c"}})) + f := CreateListAttributeCheck("testkey.subkey", NonEmptyStringListCheck) + + err := f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{ + "testkey": map[string]interface{}{"subkey": []interface{}{"a", "b", "c"}}}))) assert.Nil(err) - err = f(context.Background(), NewToken("", "", Attributes{})) + + err = f(context.Background(), NewToken("", "", NewAttributes())) assert.NotNil(err) - err = f(context.Background(), NewToken("", "", map[string]interface{}{"testkey": ""})) + + err = f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{"testkey": ""}))) assert.NotNil(err) - err = f(context.Background(), NewToken("", "", map[string]interface{}{"testkey": []interface{}{}})) + + err = f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{"testkey": map[string]interface{}{ + "subkey": []interface{}{}}}))) assert.NotNil(err) - err = f(context.Background(), NewToken("", "", map[string]interface{}{"testkey": []interface{}{5, 7, 6}})) + + err = f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{"testkey": map[string]interface{}{ + "subkey": []interface{}{5, 7, 6}}}))) assert.NotNil(err) - err = f(context.Background(), NewToken("", "", map[string]interface{}{"testkey": []interface{}{""}})) + + err = f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{"testkey": map[string]interface{}{ + "subkey": []interface{}{""}}}))) assert.NotNil(err) } diff --git a/context_test.go b/context_test.go index a245c20..6992438 100644 --- a/context_test.go +++ b/context_test.go @@ -17,7 +17,7 @@ func TestContext(t *testing.T) { Token: simpleToken{ tokenType: "test", principal: "test principal", - attributes: map[string]interface{}{"testkey": "testval", "attr": 5}, + attributes: NewAttributesFromMap(map[string]interface{}{"testkey": "testval", "attr": 5}), }, Request: Request{ URL: u, diff --git a/go.mod b/go.mod index 735daeb..5cdec1f 100644 --- a/go.mod +++ b/go.mod @@ -6,11 +6,14 @@ require ( github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2 github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/go-kit/kit v0.8.0 - github.com/go-logfmt/logfmt v0.4.0 // indirect - github.com/go-stack/stack v1.8.0 // indirect github.com/goph/emperror v0.17.1 + github.com/gorilla/mux v1.7.3 // indirect github.com/jtacoma/uritemplates v1.0.0 // indirect + github.com/justinas/alice v1.2.0 // indirect github.com/pkg/errors v0.8.0 + github.com/spf13/cast v1.3.0 + github.com/spf13/viper v1.6.1 github.com/stretchr/testify v1.3.0 github.com/xmidt-org/webpa-common v1.1.0 + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect ) diff --git a/go.sum b/go.sum index 679f358..5cde5ba 100644 --- a/go.sum +++ b/go.sum @@ -1,33 +1,75 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2 h1:koK7z0nSsRiRiBWwa+E714Puh+DO+ZRdIyAXiXzL+lg= github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2/go.mod h1:ARgCUhI1MHQH+ONky/PAtmVHQrP5JlGY0F3poXOp/fA= github.com/airbrake/gobrake v3.6.1+incompatible/go.mod h1:wM4gu3Cn0W0K7GUuVWnlXZU11AGBXMILnrdOU8Kn00o= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bugsnag/bugsnag-go v1.4.0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= github.com/bugsnag/panicwrap v1.2.0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= github.com/certifi/gocertifi v0.0.0-20190105021004-abcd57078448/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 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/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 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/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/goph/emperror v0.17.1 h1:6lOybhIvG/BB6VGoWfdv30FVZeZFBBZ9VvgzGXLVkyY= github.com/goph/emperror v0.17.1/go.mod h1:+ZbQ+fUNO/6FNiUo0ujtMjhgad9Xa6fQL9KhH4LNHic= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jtacoma/uritemplates v1.0.0 h1:xwx5sBF7pPAb0Uj8lDC1Q/aBPpOFyQza7OC705ZlLCo= github.com/jtacoma/uritemplates v1.0.0/go.mod h1:IhIICdE9OcvgUnGwTtJxgBQ+VrTrti5PcbLVSJianO8= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/justinas/alice v1.2.0 h1:+MHSA/vccVCF4Uq37S42jwlkvI2Xzl7zTPCN5BnZNVo= +github.com/justinas/alice v1.2.0/go.mod h1:fN5HRH/reO/zrUflLfTN43t3vXvKzvZIENsNEe7i7qA= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -36,31 +78,110 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN 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/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/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_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rollbar/rollbar-go v1.0.2/go.mod h1:AcFs5f0I+c71bpHlXNNDbOWJiKwjFDtISeXco0L5PKQ= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.6.1 h1:VPZzIkznI1YhVMRi6vNFLHSwhnhReBfgTxIPccpfdZk= +github.com/spf13/viper v1.6.1/go.mod h1:t3iDnF5Jlj76alVNuyFBk5oUMCvsrkbvZK0WQdfDi5k= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xmidt-org/webpa-common v1.1.0 h1:JG3lzyV70BpsVvKKvAn+/9uNI0wNW9H1r3qr0M+dQNM= github.com/xmidt-org/webpa-common v1.1.0/go.mod h1:oCpKzOC+9h2vYHVzAU/06tDTQuBN4RZz+rhgIXptpOI= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +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-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/token.go b/token.go index 35f570f..78c621b 100644 --- a/token.go +++ b/token.go @@ -3,15 +3,201 @@ // which can be used to validate are also provided. package bascule -type Attributes map[string]interface{} +import ( + "time" -// TODO: Add dotted path support and support for common concrete types, e.g. GetString -func (a Attributes) Get(key string) (interface{}, bool) { - if a == nil { + "github.com/spf13/cast" + "github.com/spf13/viper" +) + +//Attributes is the interface that wraps methods which dictate how to interact +//with a token's attributes. Getter functions return a boolean as second element +//which indicates that a value of the requested type exists at the given key path. +//Key path separators are configurable through AttributeOptions +type Attributes interface { + Get(key string) (interface{}, bool) + GetBool(key string) (bool, bool) + GetDuration(key string) (time.Duration, bool) + GetFloat64(key string) (float64, bool) + GetInt64(key string) (int64, bool) + GetIntSlice(key string) ([]int, bool) + GetString(key string) (string, bool) + GetStringMap(key string) (map[string]interface{}, bool) + GetStringSlice(key string) ([]string, bool) + GetTime(key string) (time.Time, bool) + IsSet(key string) bool + FullView() map[string]interface{} +} + +var nilTime = time.Time{} + +//AttributesOptions allows customizing Attributes initialization +type AttributesOptions struct { + //KeyDelimiter configures the separator for building key paths + //for the Attributes getter functions. Defaults to '.' + KeyDelimiter string + + //AttributesMap is used as the initial attributes datasource + AttributesMap map[string]interface{} +} + +type attributes struct { + v *viper.Viper + + //Note: having m is superfluous given v. However, it's a caching + //optimization for FullView() since v.AllSettings() is a relatively + //expensive operation + m map[string]interface{} +} + +func (a *attributes) Get(key string) (interface{}, bool) { + if !a.v.IsSet(key) { return nil, false } - v, ok := a[key] - return v, ok + + return a.v.Get(key), true +} + +func (a *attributes) GetBool(key string) (bool, bool) { + if !a.v.IsSet(key) { + return false, false + } + v, err := cast.ToBoolE(a.v.Get(key)) + if err != nil { + return false, false + } + return v, true +} + +func (a *attributes) GetDuration(key string) (time.Duration, bool) { + if !a.v.IsSet(key) { + return 0, false + } + v, err := cast.ToDurationE(a.v.Get(key)) + if err != nil { + return 0, false + } + + return v, true +} +func (a *attributes) GetFloat64(key string) (float64, bool) { + if !a.v.IsSet(key) { + return 0, false + } + v, err := cast.ToFloat64E(a.v.Get(key)) + if err != nil { + return 0, false + } + + return v, true +} + +func (a *attributes) GetInt64(key string) (int64, bool) { + if !a.v.IsSet(key) { + return 0, false + } + v, err := cast.ToInt64E(a.v.Get(key)) + if err != nil { + return 0, false + } + return v, true +} + +func (a *attributes) GetIntSlice(key string) ([]int, bool) { + if !a.v.IsSet(key) { + return nil, false + } + v, err := cast.ToIntSliceE(a.v.Get(key)) + if err != nil { + return nil, false + } + return v, true + +} +func (a *attributes) GetString(key string) (string, bool) { + if !a.v.IsSet(key) { + return "", false + } + v, err := cast.ToStringE(a.v.Get(key)) + if err != nil { + return "", false + } + return v, true +} +func (a *attributes) GetStringMap(key string) (map[string]interface{}, bool) { + if !a.v.IsSet(key) { + return nil, false + } + v, err := cast.ToStringMapE(a.v.Get(key)) + if err != nil { + return nil, false + } + return v, true + +} +func (a *attributes) GetStringSlice(key string) ([]string, bool) { + if !a.v.IsSet(key) { + return nil, false + } + v, err := cast.ToStringSliceE(a.v.Get(key)) + if err != nil { + return nil, false + } + return v, true +} + +func (a *attributes) GetTime(key string) (time.Time, bool) { + if !a.v.IsSet(key) { + return nilTime, false + } + v, err := cast.ToTimeE(a.v.Get(key)) + if err != nil { + return nilTime, false + } + return v, true +} + +func (a *attributes) IsSet(key string) bool { + return a.v.IsSet(key) +} + +func (a *attributes) FullView() map[string]interface{} { + return a.m +} + +//NewAttributes builds an empty Attributes instance. +func NewAttributes() Attributes { + return NewAttributesWithOptions(AttributesOptions{}) +} + +//NewAttributesFromMap builds an Attributes instance with +//the given map as datasource. Default AttributeOptions are used. +func NewAttributesFromMap(m map[string]interface{}) Attributes { + return NewAttributesWithOptions(AttributesOptions{ + AttributesMap: m, + }) +} + +//NewAttributesWithOptions builds an Attributes instance from the given +//options. Zero value options are ok. +func NewAttributesWithOptions(o AttributesOptions) Attributes { + var ( + options []viper.Option + v *viper.Viper + ) + + if o.KeyDelimiter != "" { + options = append(options, viper.KeyDelimiter(o.KeyDelimiter)) + } + + v = viper.NewWithOptions(options...) + + v.MergeConfigMap(o.AttributesMap) + + return &attributes{ + v: v, + m: o.AttributesMap, + } } // Token is the behavior supplied by all secure tokens diff --git a/token_test.go b/token_test.go index e4706ba..aeb6757 100644 --- a/token_test.go +++ b/token_test.go @@ -1,13 +1,25 @@ package bascule import ( + "fmt" "testing" + "time" "github.com/stretchr/testify/assert" ) -var ( - attrs = map[string]interface{}{"testkey": "testval", "attr": 5} +var attrs = NewAttributesFromMap(map[string]interface{}{"testkey": "testval", "attr": 5}) + +const ( + boolGetter = iota + durationGetter + float64Getter + int64Getter + intSliceGetter + stringGetter + stringMapGetter + stringSliceGetter + timeGetter ) func TestToken(t *testing.T) { @@ -17,7 +29,7 @@ func TestToken(t *testing.T) { token := NewToken(tokenType, principal, attrs) assert.Equal(tokenType, token.Type()) assert.Equal(principal, token.Principal()) - assert.Equal(Attributes(attrs), token.Attributes()) + assert.Equal(attrs, token.Attributes()) } func TestGet(t *testing.T) { @@ -32,8 +44,170 @@ func TestGet(t *testing.T) { assert.Empty(val) assert.False(ok) - emptyAttributes := Attributes(nil) + emptyAttributes := NewAttributes() val, ok = emptyAttributes.Get("test") assert.Nil(val) assert.False(ok) } + +func TestTypedGetters(t *testing.T) { + testCases := []struct { + typeEnum int + name string + key string + v interface{} + }{ + { + typeEnum: boolGetter, + name: "getBool", + v: true, + }, + + { + typeEnum: durationGetter, + name: "getDuration", + v: time.Second * 1, + }, + + { + typeEnum: float64Getter, + name: "getFloat64", + v: 3.14, + }, + { + typeEnum: int64Getter, + name: "getInt64", + v: int64(1 << 40), + }, + { + typeEnum: intSliceGetter, + name: "getIntSlice", + v: []int{1, 2}, + }, + + { + typeEnum: stringGetter, + name: "getString", + v: "string", + }, + + { + typeEnum: stringMapGetter, + name: "getStringMap", + v: map[string]interface{}{"string": "map"}, + }, + + { + typeEnum: stringSliceGetter, + name: "getStringSlice", + v: []string{"string", "slice"}, + }, + + { + typeEnum: timeGetter, + name: "getTime", + v: time.Now(), + }, + } + + var ( + sep = ">" + topKey = "nested" + notFoundKey = "notfound" + badTypeKey = "noneOfTheAboveType" + + topKeyMap = make(map[string]interface{}) + + m = map[string]interface{}{ + "nonOfTheAboveType": struct{}{}, + topKey: topKeyMap, + } + ) + + //sync test cases and attributes + for i, testCase := range testCases { + topKeyMap[testCase.name] = testCase.v + testCases[i].key = fmt.Sprintf("%s%s%s", topKey, sep, testCase.name) + } + + a := NewAttributesWithOptions(AttributesOptions{ + KeyDelimiter: sep, + AttributesMap: m, + }) + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + assert := assert.New(t) + switch testCase.typeEnum { + case boolGetter: + _, okNotFound := a.GetBool(notFoundKey) + _, okBadType := a.GetBool(badTypeKey) + fmt.Println(testCase.key) + v, okValid := a.GetBool(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + case durationGetter: + _, okNotFound := a.GetDuration(notFoundKey) + _, okBadType := a.GetDuration(badTypeKey) + v, okValid := a.GetDuration(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + case float64Getter: + _, okNotFound := a.GetFloat64(notFoundKey) + _, okBadType := a.GetFloat64(badTypeKey) + v, okValid := a.GetFloat64(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + case int64Getter: + _, okNotFound := a.GetInt64(notFoundKey) + _, okBadType := a.GetInt64(badTypeKey) + v, okValid := a.GetInt64(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + case intSliceGetter: + _, okNotFound := a.GetIntSlice(notFoundKey) + _, okBadType := a.GetIntSlice(badTypeKey) + v, okValid := a.GetIntSlice(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + case stringGetter: + _, okNotFound := a.GetString(notFoundKey) + _, okBadType := a.GetString(badTypeKey) + v, okValid := a.GetString(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + case stringMapGetter: + _, okNotFound := a.GetStringMap(notFoundKey) + _, okBadType := a.GetStringMap(badTypeKey) + v, okValid := a.GetStringMap(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + case stringSliceGetter: + _, okNotFound := a.GetStringSlice(notFoundKey) + _, okBadType := a.GetStringSlice(badTypeKey) + v, okValid := a.GetStringSlice(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + case timeGetter: + _, okNotFound := a.GetTime(notFoundKey) + _, okBadType := a.GetTime(badTypeKey) + v, okValid := a.GetTime(testCase.key) + assertAll(assert, assertData{okNotFound, okBadType, okValid, testCase.v, v}) + } + }) + } +} + +func TestFullView(t *testing.T) { + m := map[string]interface{}{"k0": 0, "k1": 1} + a := NewAttributesFromMap(m) + assert := assert.New(t) + assert.Equal(m, a.FullView()) +} + +type assertData struct { + okNotFound bool + okBadType bool + okValid bool + expected interface{} + actual interface{} +} + +func assertAll(a *assert.Assertions, d assertData) { + a.False(d.okNotFound) + a.False(d.okBadType) + a.True(d.okValid) + a.Equal(d.expected, d.actual) +} diff --git a/validator_test.go b/validator_test.go index bc7ac31..e4c7c58 100644 --- a/validator_test.go +++ b/validator_test.go @@ -10,9 +10,9 @@ import ( func TestValidators(t *testing.T) { assert := assert.New(t) validatorList := Validators([]Validator{CreateNonEmptyTypeCheck(), CreateNonEmptyPrincipalCheck()}) - err := validatorList.Check(context.Background(), NewToken("type", "principal", Attributes{})) + err := validatorList.Check(context.Background(), NewToken("type", "principal", NewAttributes())) assert.Nil(err) - errs := validatorList.Check(context.Background(), NewToken("", "", Attributes{})) + errs := validatorList.Check(context.Background(), NewToken("", "", NewAttributes())) assert.NotNil(errs) _, ok := errs.(Errors) assert.True(ok)