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

feat(auth): add new jsonweb package #15151

Merged
merged 1 commit into from
Sep 19, 2019
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

### Features

1. [15151](https://github.com/influxdata/influxdb/pull/15151): Add jsonweb package for future JWT support

### UI Improvements

1. [15099](https://github.com/influxdata/influxdb/pull/15099): Add viewport scaling to html meta for responsive mobile scaling
Expand Down
115 changes: 115 additions & 0 deletions jsonweb/token.go
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
}
85 changes: 85 additions & 0 deletions jsonweb/token_test.go
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)
}
})
}
}