From 19ae566cbac5cf5ec321392edf2eada9faabb4c9 Mon Sep 17 00:00:00 2001 From: Kristina Pathak Date: Tue, 15 Jun 2021 11:59:07 -0700 Subject: [PATCH] Token parser (#110) * added default keys update interval * added raw parsers for bearer acquirer * updated changelog, prep for release --- CHANGELOG.md | 7 +++- acquire/bearer.go | 29 +------------- acquire/parsers.go | 85 +++++++++++++++++++++++++++++++++++++++++ acquire/parsers_test.go | 65 +++++++++++++++++++++++++++++++ key/resolverFactory.go | 5 +++ 5 files changed, 162 insertions(+), 29 deletions(-) create mode 100644 acquire/parsers.go create mode 100644 acquire/parsers_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 44c261e..3d25dff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [v0.10.1] +- Added raw parsers for bearer acquirer. [#110](https://github.com/xmidt-org/bascule/pull/110) +- Added default keys update interval value. [#110](https://github.com/xmidt-org/bascule/pull/110) + ## [v0.10.0] - fixed Authorization keys in the constructor to be case sensitive. [#74](https://github.com/xmidt-org/bascule/pull/74) - Removed unused check. [#79](https://github.com/xmidt-org/bascule/pull/79) @@ -99,7 +103,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.10.0...HEAD +[Unreleased]: https://github.com/xmidt-org/bascule/compare/v0.10.1...HEAD +[v0.10.1]: https://github.com/xmidt-org/bascule/compare/v0.10.0...v0.10.1 [v0.10.0]: https://github.com/xmidt-org/bascule/compare/v0.9.0...v0.10.0 [v0.9.0]: https://github.com/xmidt-org/bascule/compare/v0.8.1...v0.9.0 [v0.8.1]: https://github.com/xmidt-org/bascule/compare/v0.8.0...v0.8.1 diff --git a/acquire/bearer.go b/acquire/bearer.go index f264bed..5d72c60 100644 --- a/acquire/bearer.go +++ b/acquire/bearer.go @@ -1,5 +1,5 @@ /** - * Copyright 2020 Comcast Cable Communications Management, LLC + * Copyright 2021 Comcast Cable Communications Management, LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ package acquire import ( - "encoding/json" "fmt" "io/ioutil" "net/http" @@ -26,32 +25,6 @@ import ( "time" ) -// TokenParser defines the function signature of a bearer token extractor from a payload. -type TokenParser func([]byte) (string, error) - -// ParseExpiration defines the function signature of a bearer token expiration date extractor. -type ParseExpiration func([]byte) (time.Time, error) - -// DefaultTokenParser extracts a bearer token as defined by a SimpleBearer in a payload. -func DefaultTokenParser(data []byte) (string, error) { - var bearer SimpleBearer - - if errUnmarshal := json.Unmarshal(data, &bearer); errUnmarshal != nil { - return "", fmt.Errorf("unable to parse bearer token: %w", errUnmarshal) - } - return bearer.Token, nil -} - -// DefaultExpirationParser extracts a bearer token expiration date as defined by a SimpleBearer in a payload. -func DefaultExpirationParser(data []byte) (time.Time, error) { - var bearer SimpleBearer - - if errUnmarshal := json.Unmarshal(data, &bearer); errUnmarshal != nil { - return time.Time{}, fmt.Errorf("unable to parse bearer token expiration: %w", errUnmarshal) - } - return time.Now().Add(time.Duration(bearer.ExpiresInSeconds) * time.Second), nil -} - // RemoteBearerTokenAcquirerOptions provides configuration for the RemoteBearerTokenAcquirer. type RemoteBearerTokenAcquirerOptions struct { AuthURL string `json:"authURL"` diff --git a/acquire/parsers.go b/acquire/parsers.go new file mode 100644 index 0000000..2ee5367 --- /dev/null +++ b/acquire/parsers.go @@ -0,0 +1,85 @@ +/** + * Copyright 2021 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package acquire + +import ( + "encoding/json" + "fmt" + "time" + + "github.com/dgrijalva/jwt-go" + "github.com/pkg/errors" + "github.com/spf13/cast" +) + +var ( + errMissingExpClaim = errors.New("missing exp claim in jwt") + errUnexpectedCasting = errors.New("unexpected casting error") +) + +// TokenParser defines the function signature of a bearer token extractor from a payload. +type TokenParser func([]byte) (string, error) + +// ParseExpiration defines the function signature of a bearer token expiration date extractor. +type ParseExpiration func([]byte) (time.Time, error) + +// DefaultTokenParser extracts a bearer token as defined by a SimpleBearer in a payload. +func DefaultTokenParser(data []byte) (string, error) { + var bearer SimpleBearer + + if errUnmarshal := json.Unmarshal(data, &bearer); errUnmarshal != nil { + return "", fmt.Errorf("unable to parse bearer token: %w", errUnmarshal) + } + return bearer.Token, nil +} + +// DefaultExpirationParser extracts a bearer token expiration date as defined by a SimpleBearer in a payload. +func DefaultExpirationParser(data []byte) (time.Time, error) { + var bearer SimpleBearer + + if errUnmarshal := json.Unmarshal(data, &bearer); errUnmarshal != nil { + return time.Time{}, fmt.Errorf("unable to parse bearer token expiration: %w", errUnmarshal) + } + return time.Now().Add(time.Duration(bearer.ExpiresInSeconds) * time.Second), nil +} + +func RawTokenParser(data []byte) (string, error) { + return string(data), nil +} + +func RawTokenExpirationParser(data []byte) (time.Time, error) { + p := jwt.Parser{SkipClaimsValidation: true} + token, _, err := p.ParseUnverified(string(data), jwt.MapClaims{}) + if err != nil { + return time.Time{}, err + } + claims, ok := token.Claims.(jwt.MapClaims) + if !ok { + return time.Time{}, errUnexpectedCasting + } + expVal, ok := claims["exp"] + if !ok { + return time.Time{}, errMissingExpClaim + } + + exp, err := cast.ToInt64E(expVal) + if err != nil { + return time.Time{}, err + } + return time.Unix(exp, 0), nil +} diff --git a/acquire/parsers_test.go b/acquire/parsers_test.go new file mode 100644 index 0000000..8b427b5 --- /dev/null +++ b/acquire/parsers_test.go @@ -0,0 +1,65 @@ +/** + * Copyright 2021 Comcast Cable Communications Management, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package acquire + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" +) + +func TestRawTokenParser(t *testing.T) { + assert := assert.New(t) + payload := []byte("eyJhbGciOiJSUzI1NiIsImtpZCI6ImRldmVsb3BtZW50IiwidHlwIjoiSldUIn0.eyJhbGxvd2VkUmVzb3VyY2VzIjp7ImFsbG93ZWRQYXJ0bmVycyI6WyJjb21jYXN0Il19LCJhdWQiOiJYTWlEVCIsImNhcGFiaWxpdGllcyI6WyJ4MTppc3N1ZXI6dGVzdDouKjphbGwiLCJ4MTppc3N1ZXI6dWk6YWxsIl0sImV4cCI6MTYyMjE1Nzk4MSwiaWF0IjoxNjIyMDcxNTgxLCJpc3MiOiJkZXZlbG9wbWVudCIsImp0aSI6ImN4ZmkybTZDWnJjaFNoZ1Nzdi1EM3ciLCJuYmYiOjE2MjIwNzE1NjYsInBhcnRuZXItaWQiOiJjb21jYXN0Iiwic3ViIjoiY2xpZW50LXN1cHBsaWVkIiwidHJ1c3QiOjEwMDB9.7QzRWJgxGs1cEZunMOewYCnEDiq2CTDh5R5F47PYhkMVb2KxSf06PRRGN-rQSWPhhBbev1fGgu63mr3yp_VDmdVvHR2oYiKyxP2skJTSzfQmiRyLMYY5LcLn3BObyQxU8EnLhnqGIjpORW0L5Dd4QsaZmXRnkC73yGnJx4XCx0I") + token, err := RawTokenParser(payload) + assert.Equal(string(payload), token) + assert.Nil(err) +} + +func TestRawExpirationParser(t *testing.T) { + tcs := []struct { + Description string + Payload []byte + ShouldFail bool + ExpectedTime time.Time + }{ + { + Description: "Not a JWT", + Payload: []byte("xyz==abcNotAJWT"), + ShouldFail: true, + }, + { + Description: "A jwt", + Payload: []byte("eyJhbGciOiJSUzI1NiIsImtpZCI6ImRldmVsb3BtZW50IiwidHlwIjoiSldUIn0.eyJhbGxvd2VkUmVzb3VyY2VzIjp7ImFsbG93ZWRQYXJ0bmVycyI6WyJjb21jYXN0Il19LCJhdWQiOiJYTWlEVCIsImNhcGFiaWxpdGllcyI6WyJ4MTppc3N1ZXI6dGVzdDouKjphbGwiLCJ4MTppc3N1ZXI6dWk6YWxsIl0sImV4cCI6MTYyMjE1Nzk4MSwiaWF0IjoxNjIyMDcxNTgxLCJpc3MiOiJkZXZlbG9wbWVudCIsImp0aSI6ImN4ZmkybTZDWnJjaFNoZ1Nzdi1EM3ciLCJuYmYiOjE2MjIwNzE1NjYsInBhcnRuZXItaWQiOiJjb21jYXN0Iiwic3ViIjoiY2xpZW50LXN1cHBsaWVkIiwidHJ1c3QiOjEwMDB9.7QzRWJgxGs1cEZunMOewYCnEDiq2CTDh5R5F47PYhkMVb2KxSf06PRRGN-rQSWPhhBbev1fGgu63mr3yp_VDmdVvHR2oYiKyxP2skJTSzfQmiRyLMYY5LcLn3BObyQxU8EnLhnqGIjpORW0L5Dd4QsaZmXRnkC73yGnJx4XCx0I"), + ExpectedTime: time.Unix(1622157981, 0), + }, + } + + for _, tc := range tcs { + assert := assert.New(t) + exp, err := RawTokenExpirationParser(tc.Payload) + if tc.ShouldFail { + assert.NotNil(err) + assert.Empty(exp) + } else { + assert.Nil(err) + assert.Equal(tc.ExpectedTime, exp) + } + } +} diff --git a/key/resolverFactory.go b/key/resolverFactory.go index 7db2f4f..5cb6e86 100644 --- a/key/resolverFactory.go +++ b/key/resolverFactory.go @@ -32,6 +32,8 @@ const ( // if there are any parameters. URI templates accepted by this package have either no parameters // or exactly one (1) parameter with this name. KeyIdParameterName = "keyId" + + DefaultKeysUpdateInterval = 24 * time.Hour ) var ( @@ -139,6 +141,9 @@ func ProvideResolver(key string, optional bool) fx.Option { } return nil, fmt.Errorf("%w at key %s", ErrNoResolverFactory, key) } + if in.R.UpdateInterval < 1 { + in.R.UpdateInterval = DefaultKeysUpdateInterval + } return in.R.NewResolver() }, },