Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

simplified attributes #64

Merged
merged 3 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package bascule

import (
"time"

"github.com/xmidt-org/arrange"
)

var nilTime = time.Time{}

type BasicAttributes map[string]interface{}

func (a BasicAttributes) Get(key string) (interface{}, bool) {
v, ok := a[key]
return v, ok
}

kristinapathak marked this conversation as resolved.
Show resolved Hide resolved
//NewAttributes builds an Attributes instance with
//the given map as datasource.
func NewAttributes(m map[string]interface{}) Attributes {
return BasicAttributes(m)
}

// GetNestedAttribute uses multiple keys in order to obtain an attribute.
func GetNestedAttribute(attributes Attributes, keys ...string) (interface{}, bool) {
// need at least one key.
if keys == nil || len(keys) == 0 {
return nil, false
}

var (
result interface{}
ok bool
)
result = attributes
for _, k := range keys {
var a Attributes
if result == nil {
return nil, false
}
ok = arrange.TryConvert(result,
func(attr Attributes) { a = attr },
func(m map[string]interface{}) { a = BasicAttributes(m) },
)
if !ok {
return nil, false
}
result, ok = a.Get(k)
if !ok {
return nil, false
}
}
return result, ok
}
87 changes: 87 additions & 0 deletions attributes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package bascule

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestGet(t *testing.T) {
assert := assert.New(t)
attributes := Attributes(attrs)

val, ok := attributes.Get("testkey")
assert.Equal("testval", val)
assert.True(ok)

val, ok = attributes.Get("noval")
assert.Empty(val)
assert.False(ok)

emptyAttributes := NewAttributes(map[string]interface{}{})
val, ok = emptyAttributes.Get("test")
assert.Nil(val)
assert.False(ok)
}

func TestGetNestedAttribute(t *testing.T) {
attributes := NewAttributes(map[string]interface{}{
"a": map[string]interface{}{"b": map[string]interface{}{"c": "answer"}},
"one level": "yay",
"bad": nil,
})
tests := []struct {
description string
keys []string
expectedResult interface{}
expectedOK bool
}{
// Success test is failing. ): getting nil, false
{
description: "Success",
keys: []string{"a", "b", "c"},
expectedResult: "answer",
expectedOK: true,
},
{
description: "Success single key",
keys: []string{"one level"},
expectedResult: "yay",
expectedOK: true,
},
{
description: "Success nil",
keys: []string{"bad"},
expectedResult: nil,
expectedOK: true,
},
{
description: "Nil Keys Error",
keys: nil,
},
{
description: "No Keys Error",
keys: []string{},
},
{
description: "Non Attribute Value Error",
keys: []string{"one level", "test"},
},
{
description: "Nil Attributes Error",
keys: []string{"bad", "more bad"},
},
{
description: "Missing Key Error",
keys: []string{"c", "b", "a"},
},
}
for _, tc := range tests {
t.Run(tc.description, func(t *testing.T) {
assert := assert.New(t)
val, ok := GetNestedAttribute(attributes, tc.keys...)
assert.Equal(tc.expectedResult, val)
assert.Equal(tc.expectedOK, ok)
})
}
}
5 changes: 3 additions & 2 deletions basculehttp/enforcer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ func TestEnforcer(t *testing.T) {
}),
WithEErrorResponseFunc(DefaultOnErrorResponse),
)
emptyAttributes := bascule.NewAttributes(map[string]interface{}{})
tests := []struct {
description string
enforcer func(http.Handler) http.Handler
Expand All @@ -36,7 +37,7 @@ func TestEnforcer(t *testing.T) {
enforcer: e2,
auth: bascule.Authentication{
Authorization: "jwt",
Token: bascule.NewToken("test", "", bascule.NewAttributes()),
Token: bascule.NewToken("test", "", emptyAttributes),
},
expectedStatusCode: http.StatusOK,
},
Expand All @@ -63,7 +64,7 @@ func TestEnforcer(t *testing.T) {
enforcer: e2,
auth: bascule.Authentication{
Authorization: "jwt",
Token: bascule.NewToken("", "", bascule.NewAttributes()),
Token: bascule.NewToken("", "", emptyAttributes),
},
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.NewAttributes()),
Token: bascule.NewToken("", "", bascule.NewAttributes(map[string]interface{}{})),
Request: bascule.Request{
URL: u,
Method: "get",
Expand Down
13 changes: 8 additions & 5 deletions basculehttp/tokenFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,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.NewAttributes()), nil
return bascule.NewToken("basic", principal, bascule.NewAttributes(map[string]interface{}{})), nil
}

// NewBasicTokenFactoryFromList takes a list of base64 encoded basic auth keys,
Expand Down Expand Up @@ -161,12 +161,15 @@ func (btf BearerTokenFactory) ParseAndValidate(ctx context.Context, _ *http.Requ
return nil, emperror.WrapWith(err, "failed to get map of claims", "claims struct", claims)
}

jwtClaims := bascule.NewAttributesFromMap(claimsMap)

principal, ok := jwtClaims.GetString(jwtPrincipalKey)
jwtClaims := bascule.NewAttributes(claimsMap)

principalVal, ok := jwtClaims.Get(jwtPrincipalKey)
if !ok {
return nil, emperror.WrapWith(ErrorInvalidPrincipal, "principal value not found", "principal key", jwtPrincipalKey, "jwtClaims", claimsMap)
}
principal, ok := principalVal.(string)
if !ok {
return nil, emperror.WrapWith(ErrorInvalidPrincipal, "principal value of proper type not found", "principal", principal, "jwtClaims", claimsMap)
return nil, emperror.WrapWith(ErrorInvalidPrincipal, "principal value not a string", "principal", principalVal)
}

return bascule.NewToken("jwt", principal, jwtClaims), nil
Expand Down
2 changes: 1 addition & 1 deletion 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.NewAttributes()),
expectedToken: bascule.NewToken("basic", "user", bascule.NewAttributes(map[string]interface{}{})),
},
{
description: "Can't Decode Error",
Expand Down
8 changes: 4 additions & 4 deletions checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ func CreateNonEmptyPrincipalCheck() ValidatorFunc {
// CreateListAttributeCheck returns a Validator that runs checks against the
// content found in the key given. It runs every check and returns all errors
// it finds.
func CreateListAttributeCheck(key string, checks ...func(context.Context, []interface{}) error) ValidatorFunc {
func CreateListAttributeCheck(keys []string, checks ...func(context.Context, []interface{}) error) ValidatorFunc {
return func(ctx context.Context, token Token) error {
val, ok := token.Attributes().Get(key)
val, ok := GetNestedAttribute(token.Attributes(), keys...)
if !ok {
return fmt.Errorf("couldn't find attribute with key %v", key)
return fmt.Errorf("couldn't find attribute with keys %v", keys)
}
strVal, ok := val.([]interface{})
if !ok {
Expand All @@ -80,7 +80,7 @@ func CreateListAttributeCheck(key string, checks ...func(context.Context, []inte
if len(errs) == 0 {
return nil
}
return emperror.Wrap(errs, fmt.Sprintf("attribute checks of key %v failed", key))
return emperror.Wrap(errs, fmt.Sprintf("attribute checks of keys %v failed", keys))
}
}

Expand Down
35 changes: 21 additions & 14 deletions checks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,60 +10,67 @@ import (
func TestCreateAllowAllCheck(t *testing.T) {
assert := assert.New(t)
f := CreateAllowAllCheck()
err := f(context.Background(), NewToken("", "", NewAttributes()))
err := f(context.Background(), NewToken("", "", NewAttributes(map[string]interface{}{})))
assert.Nil(err)
}

func TestCreateValidTypeCheck(t *testing.T) {
emptyAttributes := NewAttributes(map[string]interface{}{})
assert := assert.New(t)
f := CreateValidTypeCheck([]string{"valid", "type"})
err := f(context.Background(), NewToken("valid", "", NewAttributes()))
err := f(context.Background(), NewToken("valid", "", emptyAttributes))
assert.Nil(err)
err = f(context.Background(), NewToken("invalid", "", NewAttributes()))
err = f(context.Background(), NewToken("invalid", "", emptyAttributes))
assert.NotNil(err)
}

func TestCreateNonEmptyTypeCheck(t *testing.T) {
emptyAttributes := NewAttributes(map[string]interface{}{})
assert := assert.New(t)
f := CreateNonEmptyTypeCheck()
err := f(context.Background(), NewToken("type", "", NewAttributes()))
err := f(context.Background(), NewToken("type", "", emptyAttributes))
assert.Nil(err)
err = f(context.Background(), NewToken("", "", NewAttributes()))
err = f(context.Background(), NewToken("", "", emptyAttributes))
assert.NotNil(err)
}

func TestCreateNonEmptyPrincipalCheck(t *testing.T) {
emptyAttributes := NewAttributes(map[string]interface{}{})
assert := assert.New(t)
f := CreateNonEmptyPrincipalCheck()
err := f(context.Background(), NewToken("", "principal", NewAttributes()))
err := f(context.Background(), NewToken("", "principal", emptyAttributes))
assert.Nil(err)
err = f(context.Background(), NewToken("", "", NewAttributes()))
err = f(context.Background(), NewToken("", "", emptyAttributes))
assert.NotNil(err)
}

func TestCreateListAttributeCheck(t *testing.T) {
assert := assert.New(t)
f := CreateListAttributeCheck("testkey.subkey", NonEmptyStringListCheck)
f := CreateListAttributeCheck([]string{"testkey", "subkey"}, NonEmptyStringListCheck)

err := f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{
err := f(context.Background(), NewToken("", "", NewAttributes(map[string]interface{}{
"testkey": map[string]interface{}{"subkey": []interface{}{"a", "b", "c"}}})))
assert.Nil(err)

err = f(context.Background(), NewToken("", "", NewAttributes()))
err = f(context.Background(), NewToken("", "", NewAttributes(map[string]interface{}{})))
assert.NotNil(err)

err = f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{"testkey": ""})))
err = f(context.Background(), NewToken("", "", NewAttributes(map[string]interface{}{"testkey": ""})))
assert.NotNil(err)

err = f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{"testkey": map[string]interface{}{
err = f(context.Background(), NewToken("", "", NewAttributes(map[string]interface{}{"testkey": map[string]interface{}{
"subkey": 5555}})))
assert.NotNil(err)

err = f(context.Background(), NewToken("", "", NewAttributes(map[string]interface{}{"testkey": map[string]interface{}{
"subkey": []interface{}{}}})))
assert.NotNil(err)

err = f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{"testkey": map[string]interface{}{
err = f(context.Background(), NewToken("", "", NewAttributes(map[string]interface{}{"testkey": map[string]interface{}{
"subkey": []interface{}{5, 7, 6}}})))
assert.NotNil(err)

err = f(context.Background(), NewToken("", "", NewAttributesFromMap(map[string]interface{}{"testkey": map[string]interface{}{
err = f(context.Background(), NewToken("", "", NewAttributes(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: NewAttributesFromMap(map[string]interface{}{"testkey": "testval", "attr": 5}),
attributes: NewAttributes(map[string]interface{}{"testkey": "testval", "attr": 5}),
},
Request: Request{
URL: u,
Expand Down
11 changes: 7 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ go 1.12

require (
github.com/SermoDigital/jose v0.9.2-0.20161205224733-f6df55f235c2
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-kit/kit v0.8.0
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/pkg/errors v0.8.1
github.com/spf13/cast v1.3.0
github.com/spf13/viper v1.6.1
github.com/stretchr/testify v1.3.0
github.com/spf13/viper v1.7.0
github.com/stretchr/testify v1.4.0
github.com/ugorji/go v1.1.4 // indirect
github.com/xmidt-org/arrange v0.1.9
github.com/xmidt-org/webpa-common v1.1.0
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 // indirect
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
)
Loading