Skip to content

Commit

Permalink
Merge eb75811 into 7b94f47
Browse files Browse the repository at this point in the history
  • Loading branch information
ccbrown committed Jan 25, 2017
2 parents 7b94f47 + eb75811 commit 35d3a5e
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 65 deletions.
26 changes: 13 additions & 13 deletions authorize_helper_test.go
Expand Up @@ -75,22 +75,22 @@ func TestDoesClientWhiteListRedirect(t *testing.T) {
isError: true,
},
{
client: &DefaultClient{RedirectURIs: []string{"wta://auth"}},
url: "wta://auth",
client: &DefaultClient{RedirectURIs: []string{"wta://auth"}},
url: "wta://auth",
expected: "wta://auth",
isError: false,
isError: false,
},
{
client: &DefaultClient{RedirectURIs: []string{"wta:///auth"}},
url: "wta:///auth",
client: &DefaultClient{RedirectURIs: []string{"wta:///auth"}},
url: "wta:///auth",
expected: "wta:///auth",
isError: false,
isError: false,
},
{
client: &DefaultClient{RedirectURIs: []string{"wta://foo/auth"}},
url: "wta://foo/auth",
client: &DefaultClient{RedirectURIs: []string{"wta://foo/auth"}},
url: "wta://foo/auth",
expected: "wta://foo/auth",
isError: false,
isError: false,
},
{
client: &DefaultClient{RedirectURIs: []string{"https://bar.com/cb"}},
Expand Down Expand Up @@ -131,10 +131,10 @@ func TestDoesClientWhiteListRedirect(t *testing.T) {
}

func TestIsRedirectURISecure(t *testing.T) {
for d, c := range []struct{
u string
for d, c := range []struct {
u string
err bool
} {
}{
{u: "http://google.com", err: true},
{u: "https://google.com", err: false},
{u: "http://localhost", err: false},
Expand All @@ -144,4 +144,4 @@ func TestIsRedirectURISecure(t *testing.T) {
require.Nil(t, err)
assert.Equal(t, !c.err, IsRedirectURISecure(uu), "case %d", d)
}
}
}
8 changes: 4 additions & 4 deletions compose/compose.go
Expand Up @@ -6,7 +6,7 @@ import (
"github.com/ory-am/fosite"
)

type handler func(config *Config, storage interface{}, strategy interface{}) interface{}
type Factory func(config *Config, storage interface{}, strategy interface{}) interface{}

// Compose takes a config, a storage, a strategy and handlers to instantiate an OAuth2Provider:
//
Expand All @@ -30,7 +30,7 @@ type handler func(config *Config, storage interface{}, strategy interface{}) int
// )
//
// Compose makes use of interface{} types in order to be able to handle a all types of stores, strategies and handlers.
func Compose(config *Config, storage interface{}, strategy interface{}, handlers ...handler) fosite.OAuth2Provider {
func Compose(config *Config, storage interface{}, strategy interface{}, factories ...Factory) fosite.OAuth2Provider {
f := &fosite.Fosite{
Store: storage.(fosite.Storage),
AuthorizeEndpointHandlers: fosite.AuthorizeEndpointHandlers{},
Expand All @@ -41,8 +41,8 @@ func Compose(config *Config, storage interface{}, strategy interface{}, handlers
ScopeStrategy: fosite.HierarchicScopeStrategy,
}

for _, h := range handlers {
res := h(config, storage, strategy)
for _, factory := range factories {
res := factory(config, storage, strategy)
if ah, ok := res.(fosite.AuthorizeEndpointHandler); ok {
f.AuthorizeEndpointHandlers.Append(ah)
}
Expand Down
14 changes: 14 additions & 0 deletions compose/compose_oauth2.go
Expand Up @@ -87,3 +87,17 @@ func OAuth2TokenIntrospectionFactory(config *Config, storage interface{}, strate
ScopeStrategy: fosite.HierarchicScopeStrategy,
}
}

// OAuth2StatelessJWTIntrospectionFactory creates an OAuth2 token introspection handler and
// registers an access token validator. This can only be used to validate JWTs and does so
// statelessly, meaning it uses only the data available in the JWT itself, and does not access the
// storage implementation at all.
//
// Due to the stateless nature of this factory, THE BUILT-IN REVOCATION MECHANISMS WILL NOT WORK.
// If you need revocation, you can validate JWTs statefully, using the other factories.
func OAuth2StatelessJWTIntrospectionFactory(config *Config, storage interface{}, strategy interface{}) interface{} {
return &oauth2.StatelessJWTValidator{
JWTAccessTokenStrategy: strategy.(oauth2.JWTAccessTokenStrategy),
ScopeStrategy: fosite.HierarchicScopeStrategy,
}
}
43 changes: 43 additions & 0 deletions handler/oauth2/introspector_jwt.go
@@ -0,0 +1,43 @@
package oauth2

import (
"github.com/ory-am/fosite"
"github.com/pkg/errors"
"golang.org/x/net/context"
)

type JWTAccessTokenStrategy interface {
AccessTokenStrategy
JWTStrategy
}

type StatelessJWTValidator struct {
JWTAccessTokenStrategy
ScopeStrategy fosite.ScopeStrategy
}

func (v *StatelessJWTValidator) IntrospectToken(ctx context.Context, token string, tokenType fosite.TokenType, accessRequest fosite.AccessRequester, scopes []string) (err error) {
or, err := v.JWTAccessTokenStrategy.ValidateJWT(fosite.AccessToken, token)
if err != nil {
return err
}

for _, scope := range scopes {
if scope == "" {
continue
}

if !v.ScopeStrategy(or.GetGrantedScopes(), scope) {
return errors.WithStack(fosite.ErrInvalidScope)
}
}

accessRequest.Merge(or)
return nil
}

// Revocation is not supported with the stateless validator. If you need revocation, use the
// CoreValidator struct instead.
func (v *StatelessJWTValidator) RevokeToken(ctx context.Context, token string, tokenType fosite.TokenType) error {
return errors.Wrap(fosite.ErrMisconfiguration, "Token revocation is not supported")
}
127 changes: 127 additions & 0 deletions handler/oauth2/introspector_jwt_test.go
@@ -0,0 +1,127 @@
package oauth2

import (
"encoding/base64"
"strings"
"testing"

"github.com/ory-am/fosite"
"github.com/ory-am/fosite/internal"
"github.com/ory-am/fosite/token/jwt"
"github.com/pkg/errors"
"github.com/stretchr/testify/assert"
)

func TestIntrospectJWT(t *testing.T) {
strat := &RS256JWTStrategy{
RS256JWTStrategy: &jwt.RS256JWTStrategy{
PrivateKey: internal.MustRSAKey(),
},
}

v := &StatelessJWTValidator{
JWTAccessTokenStrategy: strat,
ScopeStrategy: fosite.HierarchicScopeStrategy,
}

for k, c := range []struct {
description string
token func() string
expectErr error
scopes []string
}{
{
description: "should fail because jwt is expired",
token: func() string {
jwt := jwtExpiredCase(fosite.AccessToken)
token, _, err := strat.GenerateAccessToken(nil, jwt)
assert.NoError(t, err)
return token
},
expectErr: fosite.ErrTokenExpired,
},
{
description: "should pass because scope was granted",
token: func() string {
jwt := jwtValidCase(fosite.AccessToken)
jwt.GrantedScopes = []string{"foo", "bar"}
token, _, err := strat.GenerateAccessToken(nil, jwt)
assert.NoError(t, err)
return token
},
scopes: []string{"foo"},
},
{
description: "should fail because scope was not granted",
token: func() string {
jwt := jwtValidCase(fosite.AccessToken)
token, _, err := strat.GenerateAccessToken(nil, jwt)
assert.NoError(t, err)
return token
},
scopes: []string{"foo"},
expectErr: fosite.ErrInvalidScope,
},
{
description: "should fail because signature is invalid",
token: func() string {
jwt := jwtValidCase(fosite.AccessToken)
token, _, err := strat.GenerateAccessToken(nil, jwt)
assert.NoError(t, err)
parts := strings.Split(token, ".")
dec, err := base64.RawURLEncoding.DecodeString(parts[1])
assert.NoError(t, err)
s := strings.Replace(string(dec), "peter", "piper", -1)
parts[1] = base64.RawURLEncoding.EncodeToString([]byte(s))
return strings.Join(parts, ".")
},
expectErr: fosite.ErrTokenSignatureMismatch,
},
{
description: "should pass",
token: func() string {
jwt := jwtValidCase(fosite.AccessToken)
token, _, err := strat.GenerateAccessToken(nil, jwt)
assert.NoError(t, err)
return token
},
},
} {
if c.scopes == nil {
c.scopes = []string{}
}
areq := fosite.NewAccessRequest(nil)
err := v.IntrospectToken(nil, c.token(), fosite.AccessToken, areq, c.scopes)

assert.True(t, errors.Cause(err) == c.expectErr, "(%d) %s\n%s\n%s", k, c.description, err, c.expectErr)

if err == nil {
assert.Equal(t, "peter", areq.Session.GetSubject())
}

t.Logf("Passed test case %d", k)
}
}

func BenchmarkIntrospectJWT(b *testing.B) {
strat := &RS256JWTStrategy{
RS256JWTStrategy: &jwt.RS256JWTStrategy{
PrivateKey: internal.MustRSAKey(),
},
}

v := &StatelessJWTValidator{
JWTAccessTokenStrategy: strat,
}

jwt := jwtValidCase(fosite.AccessToken)
token, _, err := strat.GenerateAccessToken(nil, jwt)
assert.NoError(b, err)
areq := fosite.NewAccessRequest(nil)

for n := 0; n < b.N; n++ {
err = v.IntrospectToken(nil, token, fosite.AccessToken, areq, []string{})
}

assert.NoError(b, err)
}
4 changes: 4 additions & 0 deletions handler/oauth2/strategy.go
Expand Up @@ -11,6 +11,10 @@ type CoreStrategy interface {
AuthorizeCodeStrategy
}

type JWTStrategy interface {
ValidateJWT(tokenType fosite.TokenType, token string) (requester fosite.Requester, err error)
}

type AccessTokenStrategy interface {
AccessTokenSignature(token string) string
GenerateAccessToken(ctx context.Context, requester fosite.Requester) (token string, signature string, err error)
Expand Down

0 comments on commit 35d3a5e

Please sign in to comment.