Skip to content

Commit

Permalink
Bascule Attributes should supported key path getters (#52)
Browse files Browse the repository at this point in the history
* 1st attempt for key path token attributes

* add token tests

* update test to use new supported key paths feature

* add notes for new release
  • Loading branch information
joe94 authored Jan 29, 2020
1 parent 24dca1c commit c31064f
Show file tree
Hide file tree
Showing 13 changed files with 552 additions and 51 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions basculehttp/enforcer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand All @@ -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,
},
Expand Down
2 changes: 1 addition & 1 deletion basculehttp/listener_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
33 changes: 18 additions & 15 deletions basculehttp/tokenFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@ const (
)

var (
ErrorMalformedValue = errors.New("expected <user>:<password> 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 <user>:<password> 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
Expand Down Expand Up @@ -63,15 +63,15 @@ 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
return nil, ErrorInvalidPassword
}
// "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.
Expand Down Expand Up @@ -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")
}
Expand All @@ -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
}
4 changes: 2 additions & 2 deletions basculehttp/tokenFactory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
38 changes: 24 additions & 14 deletions checks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
7 changes: 5 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
)
Loading

0 comments on commit c31064f

Please sign in to comment.