Skip to content

Commit

Permalink
chore: resolve comments
Browse files Browse the repository at this point in the history
  • Loading branch information
wood-push-melon committed Mar 12, 2024
1 parent bd62680 commit e2aff7a
Show file tree
Hide file tree
Showing 11 changed files with 366 additions and 144 deletions.
7 changes: 5 additions & 2 deletions compose/compose_oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,12 @@ func OAuth2AuthorizeExplicitAuthFactory(config fosite.Configurator, storage inte
func Oauth2AuthorizeExplicitTokenFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
return &oauth2.AuthorizeExplicitTokenEndpointHandler{
GenericCodeTokenEndpointHandler: oauth2.GenericCodeTokenEndpointHandler{
CodeTokenEndpointHandler: &oauth2.AuthorizeExplicitGrantTokenHandler{
AccessRequestValidator: &oauth2.AuthorizeExplicitGrantAccessRequestValidator{},
CodeHandler: &oauth2.AuthorizeCodeHandler{
AuthorizeCodeStrategy: strategy.(oauth2.AuthorizeCodeStrategy),
AuthorizeCodeStorage: storage.(oauth2.AuthorizeCodeStorage),
},
SessionHandler: &oauth2.AuthorizeExplicitGrantSessionHandler{
AuthorizeCodeStorage: storage.(oauth2.AuthorizeCodeStorage),
},

AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
Expand Down
7 changes: 5 additions & 2 deletions compose/compose_rfc8628.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ func RFC8628DeviceFactory(config fosite.Configurator, storage interface{}, strat
func RFC8628DeviceAuthorizationTokenFactory(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} {
return &rfc8628.DeviceCodeTokenEndpointHandler{
GenericCodeTokenEndpointHandler: oauth2.GenericCodeTokenEndpointHandler{
CodeTokenEndpointHandler: &rfc8628.DeviceTokenHandler{
AccessRequestValidator: &rfc8628.DeviceAccessRequestValidator{},
CodeHandler: &rfc8628.DeviceCodeHandler{
DeviceRateLimitStrategy: strategy.(rfc8628.DeviceRateLimitStrategy),
DeviceCodeStrategy: strategy.(rfc8628.DeviceCodeStrategy),
DeviceCodeStorage: storage.(rfc8628.DeviceCodeStorage),
},
SessionHandler: &rfc8628.DeviceSessionHandler{
DeviceCodeStorage: storage.(rfc8628.DeviceCodeStorage),
},

AccessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
Expand Down
1 change: 0 additions & 1 deletion fosite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ func TestAuthorizeEndpointHandlers(t *testing.T) {
}

func TestTokenEndpointHandlers(t *testing.T) {
// h := &oauth2.AuthorizeExplicitGrantHandler{}
h := &oauth2.GenericCodeTokenEndpointHandler{}
hs := TokenEndpointHandlers{}
hs.Append(h)
Expand Down
8 changes: 4 additions & 4 deletions handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

type AuthorizeEndpointHandler interface {
// HandleAuthorizeEndpointRequest handles an authorize endpoint request. To extend the handler's capabilities, the http request
// HandleAuthorizeRequest handles an authorize endpoint request. To extend the handler's capabilities, the http request
// is passed along, if further information retrieval is required. If the handler feels that he is not responsible for
// the authorize request, he must return nil and NOT modify session nor responder neither requester.
//
Expand All @@ -31,12 +31,12 @@ type TokenEndpointHandler interface {
// the request, this method should return ErrUnknownRequest and otherwise handle the request.
HandleTokenEndpointRequest(ctx context.Context, requester AccessRequester) error

// CanSkipClientAuth indicates if client authentication can be skipped. By default, it MUST be false, unless you are
// CanSkipClientAuth indicates if client authentication can be skipped. By default it MUST be false, unless you are
// implementing extension grant type, which allows unauthenticated client. CanSkipClientAuth must be called
// before HandleTokenEndpointRequest to decide, if AccessRequester will contain authenticated client.
CanSkipClientAuth(ctx context.Context, requester AccessRequester) bool

// CanHandleTokenEndpointRequest indicates, if TokenEndpointHandler can handle this request or not. If true,
// CanHandleRequest indicates, if TokenEndpointHandler can handle this request or not. If true,
// HandleTokenEndpointRequest can be called.
CanHandleTokenEndpointRequest(ctx context.Context, requester AccessRequester) bool
}
Expand All @@ -61,7 +61,7 @@ type RevocationHandler interface {

// PushedAuthorizeEndpointHandler is the interface that handles PAR (https://datatracker.ietf.org/doc/html/rfc9126)
type PushedAuthorizeEndpointHandler interface {
// HandlePushedAuthorizeEndpointRequest handles a pushed authorize endpoint request. To extend the handler's capabilities, the http request
// HandlePushedAuthorizeRequest handles a pushed authorize endpoint request. To extend the handler's capabilities, the http request
// is passed along, if further information retrieval is required. If the handler feels that he is not responsible for
// the pushed authorize request, he must return nil and NOT modify session nor responder neither requester.
HandlePushedAuthorizeEndpointRequest(ctx context.Context, requester AuthorizeRequester, responder PushedAuthorizeResponder) error
Expand Down
84 changes: 62 additions & 22 deletions handler/oauth2/flow_authorize_code_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,54 +6,94 @@ package oauth2
import (
"context"

"github.com/pkg/errors"

"github.com/ory/x/errorsx"

"github.com/ory/fosite"
)

// AuthorizeExplicitGrantTokenHandler is a response handler for the Authorize Code grant using the explicit grant type
// as defined in https://tools.ietf.org/html/rfc6749#section-4.1
type AuthorizeExplicitGrantTokenHandler struct {
type AuthorizeCodeHandler struct {
AuthorizeCodeStrategy AuthorizeCodeStrategy
AuthorizeCodeStorage AuthorizeCodeStorage
}

func (c AuthorizeExplicitGrantTokenHandler) ValidateGrantTypes(ctx context.Context, requester fosite.AccessRequester) error {
if !requester.GetClient().GetGrantTypes().Has("authorization_code") {
return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"authorization_code\"."))
}

return nil
func (c AuthorizeCodeHandler) Code(ctx context.Context, requester fosite.AccessRequester) (code string, signature string, err error) {
code = requester.GetRequestForm().Get("code")
signature = c.AuthorizeCodeStrategy.AuthorizeCodeSignature(ctx, code)
return code, signature, nil
}

func (c AuthorizeExplicitGrantTokenHandler) ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error {
func (c AuthorizeCodeHandler) ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error {
return c.AuthorizeCodeStrategy.ValidateAuthorizeCode(ctx, requester, code)
}

func (c AuthorizeExplicitGrantTokenHandler) GetCodeAndSession(ctx context.Context, requester fosite.AccessRequester) (code string, signature string, authorizeRequest fosite.Requester, err error) {
code = requester.GetRequestForm().Get("code")
signature = c.AuthorizeCodeStrategy.AuthorizeCodeSignature(ctx, code)
req, err := c.AuthorizeCodeStorage.GetAuthorizeCodeSession(ctx, signature, requester.GetSession())
return code, signature, req, err
type AuthorizeExplicitGrantSessionHandler struct {
AuthorizeCodeStorage AuthorizeCodeStorage
}

func (c AuthorizeExplicitGrantTokenHandler) InvalidateSession(ctx context.Context, signature string) error {
return c.AuthorizeCodeStorage.InvalidateAuthorizeCodeSession(ctx, signature)
func (s AuthorizeExplicitGrantSessionHandler) Session(ctx context.Context, requester fosite.AccessRequester, codeSignature string) (fosite.Requester, error) {
req, err := s.AuthorizeCodeStorage.GetAuthorizeCodeSession(ctx, codeSignature, requester.GetSession())

if err != nil && errors.Is(err, fosite.ErrInvalidatedAuthorizeCode) {
if req == nil {
return req, fosite.ErrServerError.
WithHint("Misconfigured code lead to an error that prohibited the OAuth 2.0 Framework from processing this request.").
WithDebug("\"GetAuthorizeCodeSession\" must return a value for \"fosite.Requester\" when returning \"ErrInvalidatedAuthorizeCode\".")
}

return req, err
}

if err != nil && errors.Is(err, fosite.ErrNotFound) {
return nil, errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error()))
}

if err != nil {
return nil, errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
}

return req, err
}

func (c AuthorizeExplicitGrantTokenHandler) CanSkipClientAuth(ctx context.Context, requester fosite.AccessRequester) bool {
return false
func (s AuthorizeExplicitGrantSessionHandler) InvalidateSession(ctx context.Context, codeSignature string) error {
return s.AuthorizeCodeStorage.InvalidateAuthorizeCodeSession(ctx, codeSignature)
}

func (c AuthorizeExplicitGrantTokenHandler) CanHandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) bool {
type AuthorizeExplicitGrantAccessRequestValidator struct{}

func (v AuthorizeExplicitGrantAccessRequestValidator) ValidateRequest(requester fosite.AccessRequester) bool {
return requester.GetGrantTypes().ExactOne("authorization_code")
}

func (v AuthorizeExplicitGrantAccessRequestValidator) ValidateClientAuth(requester fosite.AccessRequester) bool {
return false
}

func (v AuthorizeExplicitGrantAccessRequestValidator) ValidateGrantTypes(requester fosite.AccessRequester) error {
if !requester.GetClient().GetGrantTypes().Has("authorization_code") {
return errorsx.WithStack(fosite.ErrUnauthorizedClient.WithHint("The OAuth 2.0 Client is not allowed to use authorization grant \"authorization_code\"."))
}

return nil
}

func (v AuthorizeExplicitGrantAccessRequestValidator) ValidateRedirectURI(accessRequester fosite.AccessRequester, authorizeRequester fosite.Requester) error {
forcedRedirectURI := authorizeRequester.GetRequestForm().Get("redirect_uri")
requestedRedirectURI := accessRequester.GetRequestForm().Get("redirect_uri")
if forcedRedirectURI != "" && forcedRedirectURI != requestedRedirectURI {
return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The \"redirect_uri\" from this request does not match the one from the authorize request."))
}

return nil
}

type AuthorizeExplicitTokenEndpointHandler struct {
GenericCodeTokenEndpointHandler
}

var (
_ CodeTokenEndpointHandler = (*AuthorizeExplicitGrantTokenHandler)(nil)
_ AccessRequestValidator = (*AuthorizeExplicitGrantAccessRequestValidator)(nil)
_ CodeHandler = (*AuthorizeCodeHandler)(nil)
_ SessionHandler = (*AuthorizeExplicitGrantSessionHandler)(nil)
_ fosite.TokenEndpointHandler = (*AuthorizeExplicitTokenEndpointHandler)(nil)
)
19 changes: 14 additions & 5 deletions handler/oauth2/flow_authorize_code_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,9 +208,12 @@ func TestAuthorizeCode_PopulateTokenEndpointResponse(t *testing.T) {
RefreshTokenScopes: []string{"offline"},
}
h = GenericCodeTokenEndpointHandler{
CodeTokenEndpointHandler: &AuthorizeExplicitGrantTokenHandler{
AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{},
CodeHandler: &AuthorizeCodeHandler{
AuthorizeCodeStrategy: strategy,
AuthorizeCodeStorage: store,
},
SessionHandler: &AuthorizeExplicitGrantSessionHandler{
AuthorizeCodeStorage: store,
},
AccessTokenStrategy: strategy,
RefreshTokenStrategy: strategy,
Expand Down Expand Up @@ -252,9 +255,12 @@ func TestAuthorizeCode_HandleTokenEndpointRequest(t *testing.T) {
AuthorizeCodeLifespan: time.Minute,
}
h := GenericCodeTokenEndpointHandler{
CodeTokenEndpointHandler: &AuthorizeExplicitGrantTokenHandler{
AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{},
CodeHandler: &AuthorizeCodeHandler{
AuthorizeCodeStrategy: strategy,
AuthorizeCodeStorage: store,
},
SessionHandler: &AuthorizeExplicitGrantSessionHandler{
AuthorizeCodeStorage: store,
},
TokenRevocationStorage: store,
Config: config,
Expand Down Expand Up @@ -668,8 +674,11 @@ func TestAuthorizeCodeTransactional_HandleTokenEndpointRequest(t *testing.T) {
AuthorizeCodeLifespan: time.Minute,
}
handler := GenericCodeTokenEndpointHandler{
CodeTokenEndpointHandler: &AuthorizeExplicitGrantTokenHandler{
AccessRequestValidator: &AuthorizeExplicitGrantAccessRequestValidator{},
CodeHandler: &AuthorizeCodeHandler{
AuthorizeCodeStrategy: &strategy,
},
SessionHandler: &AuthorizeExplicitGrantSessionHandler{
AuthorizeCodeStorage: authorizeTransactionalStore{
mockTransactional,
mockAuthorizeStore,
Expand Down
95 changes: 52 additions & 43 deletions handler/oauth2/flow_generic_code_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,31 +18,46 @@ import (
"github.com/ory/fosite"
)

// CodeTokenEndpointHandler handles the differences between Authorize code grant and extended grant types.
type CodeTokenEndpointHandler interface {
// ValidateGrantTypes validates the authorization grant type.
ValidateGrantTypes(ctx context.Context, requester fosite.AccessRequester) error
// AccessRequestValidator handles various validations in the access request handling.
type AccessRequestValidator interface {
// ValidateRequest validates if the access request should be handled.
ValidateRequest(requester fosite.AccessRequester) bool

// ValidateCode validates the code used in the authorization flow.
ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error
// ValidateClientAuth validates if the client authentication is required.
ValidateClientAuth(requester fosite.AccessRequester) bool

// ValidateGrantTypes validates the grant types in the access request.
ValidateGrantTypes(requester fosite.AccessRequester) error

// GetCodeAndSession retrieves the code, the code signature, and the request session.
GetCodeAndSession(ctx context.Context, requester fosite.AccessRequester) (code string, signature string, authorizeRequest fosite.Requester, err error)
// ValidateRedirectURI validates the redirect uri in the access request.
ValidateRedirectURI(accessRequester fosite.AccessRequester, authorizeRequester fosite.Requester) error
}

// InvalidateSession invalidates the code once the code is used.
InvalidateSession(ctx context.Context, signature string) error
// CodeHandler handles authorization/device code related operations.
type CodeHandler interface {
// Code fetches the code and code signature.
Code(ctx context.Context, requester fosite.AccessRequester) (code string, signature string, err error)

// ValidateCode validates the code.
ValidateCode(ctx context.Context, requester fosite.AccessRequester, code string) error
}

// CanSkipClientAuth indicates if client authentication can be skipped. By default, it MUST be false, unless you are
// implementing extension grant type, which allows unauthenticated client. CanSkipClientAuth must be called
// before HandleTokenEndpointRequest to decide, if AccessRequester will contain authenticated client.
CanSkipClientAuth(ctx context.Context, requester fosite.AccessRequester) bool
// SessionHandler handles session-related operations.
type SessionHandler interface {
// Session fetches the authorized request.
Session(ctx context.Context, requester fosite.AccessRequester, codeSignature string) (fosite.Requester, error)

// CanHandleTokenEndpointRequest indicates if GenericCodeTokenEndpointHandler can handle this request or not.
CanHandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) bool
// InvalidateSession invalidates the code and session.
InvalidateSession(ctx context.Context, codeSignature string) error
}

// GenericCodeTokenEndpointHandler is a token response handler for
// - the Authorize Code grant using the explicit grant type as defined in https://tools.ietf.org/html/rfc6749#section-4.1
// - the Device Authorization Grant as defined in https://www.rfc-editor.org/rfc/rfc8628
type GenericCodeTokenEndpointHandler struct {
CodeTokenEndpointHandler
AccessRequestValidator
CodeHandler
SessionHandler

AccessTokenStrategy AccessTokenStrategy
RefreshTokenStrategy RefreshTokenStrategy
Expand All @@ -60,7 +75,12 @@ func (c *GenericCodeTokenEndpointHandler) PopulateTokenEndpointResponse(ctx cont
return errorsx.WithStack(fosite.ErrUnknownRequest)
}

code, signature, ar, err := c.GetCodeAndSession(ctx, requester)
code, signature, err := c.Code(ctx, requester)
if err != nil {
return err
}

ar, err := c.Session(ctx, requester, signature)
if err != nil {
return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
}
Expand Down Expand Up @@ -137,30 +157,21 @@ func (c *GenericCodeTokenEndpointHandler) HandleTokenEndpointRequest(ctx context
return errorsx.WithStack(errorsx.WithStack(fosite.ErrUnknownRequest))
}

if err := c.ValidateGrantTypes(ctx, requester); err != nil {
if err := c.ValidateGrantTypes(requester); err != nil {
return err
}

code, _, ar, err := c.GetCodeAndSession(ctx, requester)
code, signature, err := c.Code(ctx, requester)
if err != nil {
switch {
case errors.Is(err, fosite.ErrInvalidatedAuthorizeCode), errors.Is(err, fosite.ErrInvalidatedDeviceCode):
if ar == nil {
return fosite.ErrServerError.
WithHint("Misconfigured code lead to an error that prohibited the OAuth 2.0 Framework from processing this request.").
WithDebug("getCodeSession must return a value for \"fosite.Requester\" when returning \"ErrInvalidatedAuthorizeCode\" or \"ErrInvalidatedDeviceCode\".")
}
return err
}

return c.revokeTokens(ctx, requester.GetID())
case errors.Is(err, fosite.ErrAuthorizationPending):
return err
case errors.Is(err, fosite.ErrPollingRateLimited):
return errorsx.WithStack(err)
case errors.Is(err, fosite.ErrNotFound):
return errorsx.WithStack(fosite.ErrInvalidGrant.WithWrap(err).WithDebug(err.Error()))
default:
return errorsx.WithStack(fosite.ErrServerError.WithWrap(err).WithDebug(err.Error()))
}
ar, err := c.Session(ctx, requester, signature)
if ar != nil && err != nil && (errors.Is(err, fosite.ErrInvalidatedAuthorizeCode) || errors.Is(err, fosite.ErrInvalidatedDeviceCode)) {
return c.revokeTokens(ctx, requester.GetID())
}
if err != nil {
return err
}

if err = c.ValidateCode(ctx, requester, code); err != nil {
Expand All @@ -180,10 +191,8 @@ func (c *GenericCodeTokenEndpointHandler) HandleTokenEndpointRequest(ctx context
return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The OAuth 2.0 Client ID from this request does not match the one from the authorize request."))
}

forcedRedirectURI := ar.GetRequestForm().Get("redirect_uri")
requestedRedirectURI := requester.GetRequestForm().Get("redirect_uri")
if forcedRedirectURI != "" && forcedRedirectURI != requestedRedirectURI {
return errorsx.WithStack(fosite.ErrInvalidGrant.WithHint("The \"redirect_uri\" from this request does not match the one from the authorize request."))
if err = c.ValidateRedirectURI(requester, ar); err != nil {
return err
}

// Checking of POST client_id skipped, because
Expand All @@ -204,11 +213,11 @@ func (c *GenericCodeTokenEndpointHandler) HandleTokenEndpointRequest(ctx context
}

func (c *GenericCodeTokenEndpointHandler) CanSkipClientAuth(ctx context.Context, requester fosite.AccessRequester) bool {
return c.CodeTokenEndpointHandler.CanSkipClientAuth(ctx, requester)
return c.ValidateClientAuth(requester)
}

func (c *GenericCodeTokenEndpointHandler) CanHandleTokenEndpointRequest(ctx context.Context, requester fosite.AccessRequester) bool {
return c.CodeTokenEndpointHandler.CanHandleTokenEndpointRequest(ctx, requester)
return c.ValidateRequest(requester)
}

func (c *GenericCodeTokenEndpointHandler) canIssueRefreshToken(ctx context.Context, requester fosite.Requester) bool {
Expand Down
Loading

0 comments on commit e2aff7a

Please sign in to comment.