-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
202 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package jsonweb | ||
|
||
import ( | ||
"errors" | ||
|
||
"github.com/dgrijalva/jwt-go" | ||
"github.com/influxdata/influxdb" | ||
) | ||
|
||
const kind = "jwt" | ||
|
||
// ErrKeyNotFound should be returned by a KeyStore when | ||
// a key cannot be located for the provided key ID | ||
var ErrKeyNotFound = errors.New("key not found") | ||
|
||
// ensure Token implements Authorizer | ||
var _ influxdb.Authorizer = (*Token)(nil) | ||
|
||
// KeyStore is a type which holds a set of keys accessed | ||
// via an id | ||
type KeyStore interface { | ||
Key(string) ([]byte, error) | ||
} | ||
|
||
// KeyStoreFunc is a function which can be used as a KeyStore | ||
type KeyStoreFunc func(string) ([]byte, error) | ||
|
||
// Key delegates to the receiver KeyStoreFunc | ||
func (k KeyStoreFunc) Key(v string) ([]byte, error) { return k(v) } | ||
|
||
// TokenParser is a type which can parse and validate tokens | ||
type TokenParser struct { | ||
keyStore KeyStore | ||
parser *jwt.Parser | ||
} | ||
|
||
// NewTokenParser returns a configured token parser used to | ||
// parse Token types from strings | ||
func NewTokenParser(keyStore KeyStore) TokenParser { | ||
return TokenParser{ | ||
keyStore: keyStore, | ||
parser: &jwt.Parser{ | ||
ValidMethods: []string{jwt.SigningMethodHS256.Alg()}, | ||
}, | ||
} | ||
} | ||
|
||
// Parse takes a string then parses and validates it as a jwt based on | ||
// the key described within the token | ||
func (t *TokenParser) Parse(v string) (*Token, error) { | ||
jwt, err := t.parser.ParseWithClaims(v, &Token{}, func(jwt *jwt.Token) (interface{}, error) { | ||
token, ok := jwt.Claims.(*Token) | ||
if !ok { | ||
return nil, errors.New("missing kid in token claims") | ||
} | ||
|
||
// fetch key for "kid" from key store | ||
return t.keyStore.Key(token.KeyID) | ||
}) | ||
|
||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
token, ok := jwt.Claims.(*Token) | ||
if !ok { | ||
return nil, errors.New("token is unexpected type") | ||
} | ||
|
||
return token, nil | ||
} | ||
|
||
// Token is a structure which is serialized as a json web token | ||
// It contains the necessary claims required to authorize | ||
type Token struct { | ||
jwt.StandardClaims | ||
// KeyID is the identifier of the key used to sign the token | ||
KeyID string `json:"kid"` | ||
// Permissions is the set of authorized permissions for the token | ||
Permissions []influxdb.Permission `json:"permissions"` | ||
} | ||
|
||
// Allowed returns whether or not a permission is allowed based | ||
// on the set of permissions within the Token | ||
func (t *Token) Allowed(p influxdb.Permission) bool { | ||
if err := p.Valid(); err != nil { | ||
return false | ||
} | ||
|
||
for _, perm := range t.Permissions { | ||
if perm.Matches(p) { | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
// Identifier returns the identifier for this Token | ||
// as found in the standard claims | ||
func (t *Token) Identifier() influxdb.ID { | ||
id, _ := influxdb.IDFromString(t.Id) | ||
return *id | ||
} | ||
|
||
// GetUserID returns an invalid id as tokens are generated | ||
// with permissions rather than for or by a particular user | ||
func (t *Token) GetUserID() influxdb.ID { | ||
return influxdb.InvalidID() | ||
} | ||
|
||
// Kind returns the string "jwt" which is used for auditing | ||
func (t *Token) Kind() string { | ||
return kind | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
package jsonweb | ||
|
||
import ( | ||
"reflect" | ||
"testing" | ||
|
||
"github.com/dgrijalva/jwt-go" | ||
"github.com/google/go-cmp/cmp" | ||
"github.com/influxdata/influxdb" | ||
) | ||
|
||
var ( | ||
one = influxdb.ID(1) | ||
two = influxdb.ID(2) | ||
keyStore = KeyStoreFunc(func(kid string) ([]byte, error) { | ||
if kid != "some-key" { | ||
return nil, ErrKeyNotFound | ||
} | ||
|
||
return []byte("correct-key"), nil | ||
}) | ||
) | ||
|
||
func Test_TokenParser(t *testing.T) { | ||
for _, test := range []struct { | ||
name string | ||
keyStore KeyStore | ||
input string | ||
// expectations | ||
token *Token | ||
err error | ||
}{ | ||
{ | ||
name: "happy path", | ||
input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbG91ZDIuaW5mbHV4ZGF0YS5jb20iLCJhdWQiOiJnYXRld2F5LmluZmx1eGRhdGEuY29tIiwiaWF0IjoxNTY4NjI4OTgwLCJraWQiOiJzb21lLWtleSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJ3cml0ZSIsInJlc291cmNlIjp7InR5cGUiOiJidWNrZXRzIiwiaWQiOiIwMDAwMDAwMDAwMDAwMDAxIiwib3JnSUQiOiIwMDAwMDAwMDAwMDAwMDAyIn19XX0.74vjbExiOd702VSIMmQWaDT_GFvUI0-_P-SfQ_OOHB0", | ||
token: &Token{ | ||
StandardClaims: jwt.StandardClaims{ | ||
Issuer: "cloud2.influxdata.com", | ||
Audience: "gateway.influxdata.com", | ||
IssuedAt: 1568628980, | ||
}, | ||
KeyID: "some-key", | ||
Permissions: []influxdb.Permission{ | ||
{ | ||
Action: influxdb.WriteAction, | ||
Resource: influxdb.Resource{ | ||
Type: influxdb.BucketsResourceType, | ||
ID: &one, | ||
OrgID: &two, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
{ | ||
name: "key not found", | ||
input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbG91ZDIuaW5mbHV4ZGF0YS5jb20iLCJhdWQiOiJnYXRld2F5LmluZmx1eGRhdGEuY29tIiwiaWF0IjoxNTY4NjMxMTQ0LCJraWQiOiJzb21lLW90aGVyLWtleSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJyZWFkIiwicmVzb3VyY2UiOnsidHlwZSI6InRhc2tzIiwiaWQiOiIwMDAwMDAwMDAwMDAwMDAzIiwib3JnSUQiOiIwMDAwMDAwMDAwMDAwMDA0In19XX0.QVXJ3kGP1gsxisNZe7QmphXox-vjZr6MAMbd00CQlfA", | ||
err: &jwt.ValidationError{ | ||
Inner: ErrKeyNotFound, | ||
Errors: jwt.ValidationErrorUnverifiable, | ||
}, | ||
}, | ||
{ | ||
name: "invalid signature", | ||
input: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJjbG91ZDIuaW5mbHV4ZGF0YS5jb20iLCJhdWQiOiJnYXRld2F5LmluZmx1eGRhdGEuY29tIiwiaWF0IjoxNTY4NjMxMTQ0LCJraWQiOiJzb21lLWtleSIsInBlcm1pc3Npb25zIjpbeyJhY3Rpb24iOiJyZWFkIiwicmVzb3VyY2UiOnsidHlwZSI6InRhc2tzIiwiaWQiOiIwMDAwMDAwMDAwMDAwMDAzIiwib3JnSUQiOiIwMDAwMDAwMDAwMDAwMDA0In19XX0.RwmNs5u6NnjNq9xTdAIERFrI5ow-6lJpND3jRrTwkaE", | ||
err: &jwt.ValidationError{ | ||
Inner: jwt.ErrSignatureInvalid, | ||
Errors: jwt.ValidationErrorSignatureInvalid, | ||
}, | ||
}, | ||
} { | ||
t.Run(test.name, func(t *testing.T) { | ||
parser := NewTokenParser(keyStore) | ||
|
||
token, err := parser.Parse(test.input) | ||
if !reflect.DeepEqual(test.err, err) { | ||
t.Errorf("expected %[1]s (%#[1]v), got %[2]s (%#[2]v)", test.err, err) | ||
} | ||
|
||
if diff := cmp.Diff(test.token, token); diff != "" { | ||
t.Errorf("unexpected token:\n%s", diff) | ||
} | ||
}) | ||
} | ||
} |