Add more parameter validations and refactor internal/oidc/token_exchange.go.

Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
Matt Moyer 2020-12-09 10:04:58 -06:00
parent e25d090ca9
commit 1db2ae3a45
No known key found for this signature in database
GPG Key ID: EAE88AD172C5AE2D

View File

@ -5,22 +5,30 @@ package oidc
import ( import (
"context" "context"
"net/url"
"github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"github.com/pkg/errors"
"github.com/ory/fosite" "github.com/ory/fosite"
"github.com/ory/fosite/compose" "github.com/ory/fosite/compose"
"github.com/ory/fosite/handler/oauth2"
"github.com/ory/fosite/handler/openid"
"github.com/pkg/errors"
) )
const (
tokenTypeAccessToken = "urn:ietf:params:oauth:token-type:access_token"
tokenTypeJWT = "urn:ietf:params:oauth:token-type:jwt"
)
type stsParams struct {
subjectAccessToken string
requestedAudience string
}
func TokenExchangeFactory(config *compose.Config, storage interface{}, strategy interface{}) interface{} { func TokenExchangeFactory(config *compose.Config, storage interface{}, strategy interface{}) interface{} {
return &TokenExchangeHandler{ return &TokenExchangeHandler{
strategy.(openid.OpenIDConnectTokenStrategy), idTokenStrategy: strategy.(openid.OpenIDConnectTokenStrategy),
strategy.(oauth2.AccessTokenStrategy), accessTokenStrategy: strategy.(oauth2.AccessTokenStrategy),
storage.(oauth2.AccessTokenStorage), accessTokenStorage: storage.(oauth2.AccessTokenStorage),
} }
} }
@ -38,31 +46,86 @@ func (t *TokenExchangeHandler) HandleTokenEndpointRequest(ctx context.Context, r
} }
func (t *TokenExchangeHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error { func (t *TokenExchangeHandler) PopulateTokenEndpointResponse(ctx context.Context, requester fosite.AccessRequester, responder fosite.AccessResponder) error {
if !(requester.GetGrantTypes().ExactOne("urn:ietf:params:oauth:grant-type:token-exchange")) { // Skip this request if it's for a different grant type.
return errors.WithStack(fosite.ErrUnknownRequest) if err := t.HandleTokenEndpointRequest(ctx, requester); err != nil {
}
params := requester.GetRequestForm()
accessToken := params.Get("subject_token")
if err := t.accessTokenStrategy.ValidateAccessToken(ctx, requester, accessToken); err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
signature := t.accessTokenStrategy.AccessTokenSignature(accessToken)
accessTokenSession, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession()) // Validate the basic RFC8693 parameters we support.
params, err := t.validateParams(requester.GetRequestForm())
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
if !accessTokenSession.GetGrantedScopes().Has("pinniped.sts.unrestricted") {
return errors.WithStack(fosite.ErrScopeNotGranted) // Validate the incoming access token and lookup the information about the original authorize request.
} originalRequester, err := t.validateAccessToken(ctx, requester, params.subjectAccessToken)
// TODO check the other requester fields
scopedDownRequester := fosite.NewAccessRequest(accessTokenSession.GetSession())
scopedDownRequester.GrantedAudience = []string{params.Get("audience")}
newToken, err := t.idTokenStrategy.GenerateIDToken(ctx, scopedDownRequester)
if err != nil { if err != nil {
return errors.WithStack(err) return errors.WithStack(err)
} }
responder.SetAccessToken(newToken)
// Use the original authorize request information, along with the requested audience, to mint a new JWT.
responseToken, err := t.mintJWT(ctx, originalRequester, params.requestedAudience)
if err != nil {
return errors.WithStack(err)
}
// Format the response parameters according to RFC8693.
responder.SetAccessToken(responseToken)
responder.SetTokenType("N_A") responder.SetTokenType("N_A")
responder.SetExtra("issued_token_type", "urn:ietf:params:oauth:token-type:jwt") responder.SetExtra("issued_token_type", "urn:ietf:params:oauth:token-type:jwt")
return nil return nil
} }
func (t *TokenExchangeHandler) mintJWT(ctx context.Context, requester fosite.Requester, audience string) (string, error) {
downscoped := fosite.NewAccessRequest(requester.GetSession())
downscoped.Client.(*fosite.DefaultClient).ID = audience
return t.idTokenStrategy.GenerateIDToken(ctx, downscoped)
}
func (t *TokenExchangeHandler) validateParams(params url.Values) (*stsParams, error) {
var result stsParams
// Validate some required parameters.
result.requestedAudience = params.Get("audience")
if result.requestedAudience == "" {
return nil, errors.WithMessagef(fosite.ErrInvalidRequest, "missing audience parameter")
}
result.subjectAccessToken = params.Get("subject_token")
if result.subjectAccessToken == "" {
return nil, errors.WithMessagef(fosite.ErrInvalidRequest, "missing subject_token parameter")
}
// Validate some parameters with hardcoded values we support.
if params.Get("subject_token_type") != tokenTypeAccessToken {
return nil, errors.WithMessagef(fosite.ErrInvalidRequest, "unsupported subject_token_type parameter value, must be %q", tokenTypeAccessToken)
}
if params.Get("requested_token_type") != tokenTypeJWT {
return nil, errors.WithMessagef(fosite.ErrInvalidRequest, "unsupported requested_token_type parameter value, must be %q", tokenTypeJWT)
}
// Validate that none of these unsupported parameters were sent. These are optional and we do not currently support them.
for _, param := range []string{
"resource",
"scope",
"actor_token",
"actor_token_type",
} {
if params.Get(param) != "" {
return nil, errors.WithMessagef(fosite.ErrInvalidRequest, "unsupported parameter %s", param)
}
}
return &result, nil
}
func (t *TokenExchangeHandler) validateAccessToken(ctx context.Context, requester fosite.AccessRequester, accessToken string) (fosite.Requester, error) {
if err := t.accessTokenStrategy.ValidateAccessToken(ctx, requester, accessToken); err != nil {
return nil, errors.WithStack(err)
}
signature := t.accessTokenStrategy.AccessTokenSignature(accessToken)
originalRequester, err := t.accessTokenStorage.GetAccessTokenSession(ctx, signature, requester.GetSession())
if err != nil {
return nil, errors.WithStack(err)
}
return originalRequester, nil
}