From 08cde0e60e3a76f2fc3b0b1f5ed09b96a9923d9d Mon Sep 17 00:00:00 2001 From: James Elliott Date: Wed, 16 Nov 2022 10:17:13 +1100 Subject: [PATCH] fix: retain refresh_token original scopes --- access_request.go | 33 ++++++++++++++++++++++++++++++++ handler/oauth2/flow_refresh.go | 35 +++++++++++++++++++++++++++++----- oauth2.go | 30 +++++++++++++++++++++++++---- 3 files changed, 89 insertions(+), 9 deletions(-) diff --git a/access_request.go b/access_request.go index 6efb9df10..c421238bb 100644 --- a/access_request.go +++ b/access_request.go @@ -25,6 +25,11 @@ type AccessRequest struct { GrantTypes Arguments `json:"grantTypes" gorethink:"grantTypes"` HandledGrantType Arguments `json:"handledGrantType" gorethink:"handledGrantType"` + RefreshTokenRequestedScope Arguments + RefreshTokenGrantedScope Arguments + + OriginalRequester Requester + Request } @@ -41,3 +46,31 @@ func NewAccessRequest(session Session) *AccessRequest { func (a *AccessRequest) GetGrantTypes() Arguments { return a.GrantTypes } + +func (a *AccessRequest) GetRefreshTokenRequestedScopes() (scopes Arguments) { + if a.RefreshTokenRequestedScope == nil { + return a.RequestedScope + } + + return a.RefreshTokenRequestedScope +} + +func (a *AccessRequest) SetRefreshTokenRequestedScopes(scopes Arguments) { + a.RefreshTokenRequestedScope = scopes +} + +func (a *AccessRequest) GetRefreshTokenGrantedScopes() (scopes Arguments) { + if a.RefreshTokenGrantedScope == nil { + return a.GrantedScope + } + + return a.RefreshTokenGrantedScope +} + +func (a *AccessRequest) SetRefreshTokenGrantedScopes(scopes Arguments) { + a.RefreshTokenGrantedScope = scopes +} + +func (a *AccessRequest) SetGrantedScopes(scopes Arguments) { + a.GrantedScope = scopes +} diff --git a/handler/oauth2/flow_refresh.go b/handler/oauth2/flow_refresh.go index 6625826e9..006c19481 100644 --- a/handler/oauth2/flow_refresh.go +++ b/handler/oauth2/flow_refresh.go @@ -86,7 +86,6 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex scopeNames := strings.Join(c.Config.GetRefreshTokenScopes(ctx), " or ") hint := fmt.Sprintf("The OAuth 2.0 Client was not granted scope %s and may thus not perform the 'refresh_token' authorization grant.", scopeNames) return errorsx.WithStack(fosite.ErrScopeNotGranted.WithHint(hint)) - } // The authorization server MUST ... and ensure that the refresh token was issued to the authenticated client @@ -116,15 +115,25 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex request.SetRequestedScopes(fosite.RemoveEmpty(strings.Split(scope, " "))) } + // If a new refresh token is issued, the refresh token scope MUST be identical to that of the refresh token included + // by the client in the request. + if rtRequest, ok := request.(fosite.RefreshTokenAccessRequester); ok { + rtRequest.SetRefreshTokenRequestedScopes(originalRequest.GetRequestedScopes()) + rtRequest.SetRefreshTokenGrantedScopes(originalRequest.GetGrantedScopes()) + } + request.SetRequestedAudience(originalRequest.GetRequestedAudience()) + strategy := c.Config.GetScopeStrategy(ctx) + originalScopes := originalRequest.GetGrantedScopes() + for _, scope := range request.GetRequestedScopes() { // Addresses point 2 of the text in RFC6749 Section 6. - if !originalRequest.GetGrantedScopes().Has(scope) { + if !strategy(originalScopes, scope) { return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The requested scope '%s' was not originally granted by the resource owner.", scope)) } - if !c.Config.GetScopeStrategy(ctx)(request.GetClient().GetScopes(), scope) { + if !strategy(request.GetClient().GetScopes(), scope) { return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The OAuth 2.0 Client is not allowed to request scope '%s'.", scope)) } @@ -194,8 +203,24 @@ func (c *RefreshTokenGrantHandler) PopulateTokenEndpointResponse(ctx context.Con return err } - if err = c.TokenRevocationStorage.CreateRefreshTokenSession(ctx, refreshSignature, storeReq); err != nil { - return err + if rtRequest, ok := requester.(fosite.RefreshTokenAccessRequester); ok { + r := requester.Sanitize([]string{}).(*fosite.Request) + r.SetID(ts.GetID()) + + rtStoreReq := &fosite.AccessRequest{ + Request: *r, + } + + rtStoreReq.SetRequestedScopes(rtRequest.GetRefreshTokenRequestedScopes()) + rtStoreReq.SetGrantedScopes(rtRequest.GetRefreshTokenGrantedScopes()) + + if err = c.TokenRevocationStorage.CreateRefreshTokenSession(ctx, refreshSignature, rtStoreReq); err != nil { + return err + } + } else { + if err = c.TokenRevocationStorage.CreateRefreshTokenSession(ctx, refreshSignature, storeReq); err != nil { + return err + } } responder.SetAccessToken(accessToken) diff --git a/oauth2.go b/oauth2.go index 66a539b06..faeb601f0 100644 --- a/oauth2.go +++ b/oauth2.go @@ -194,7 +194,7 @@ type IntrospectionResponder interface { // IsActive returns true if the introspected token is active and false otherwise. IsActive() bool - // AccessRequester returns nil when IsActive() is false and the original access request object otherwise. + // GetAccessRequester returns nil when IsActive() is false and the original access request object otherwise. GetAccessRequester() AccessRequester // GetTokenUse optionally returns the type of the token that was introspected. This could be "access_token", "refresh_token", @@ -235,7 +235,7 @@ type Requester interface { // AppendRequestedScope appends a scope to the request. AppendRequestedScope(scope string) - // GetGrantScopes returns all granted scopes. + // GetGrantedScopes returns all granted scopes. GetGrantedScopes() (grantedScopes Arguments) // GetGrantedAudience returns all granted audiences. @@ -263,9 +263,31 @@ type Requester interface { Sanitize(allowedParameters []string) Requester } +// RefreshTokenAccessRequester is an extended AccessRequester implementation that allows preserving +// the original Requester. +type RefreshTokenAccessRequester interface { + // GetRefreshTokenRequestedScopes returns the request's scopes specifically for the refresh token. + GetRefreshTokenRequestedScopes() (scopes Arguments) + + // SetRefreshTokenRequestedScopes sets the request's scopes specifically for the refresh token. + SetRefreshTokenRequestedScopes(scopes Arguments) + + // GetRefreshTokenGrantedScopes returns all granted scopes specifically for the refresh token. + GetRefreshTokenGrantedScopes() (scopes Arguments) + + // SetRefreshTokenGrantedScopes sets all granted scopes specifically for the refresh token. + SetRefreshTokenGrantedScopes(scopes Arguments) + + // SetGrantedScopes sets all granted scopes. This is specifically used in the refresh flow to restore the originally + // granted scopes to the session. + SetGrantedScopes(scopes Arguments) + + AccessRequester +} + // AccessRequester is a token endpoint's request context. type AccessRequester interface { - // GetGrantType returns the requests grant type. + // GetGrantTypes returns the requests grant type. GetGrantTypes() (grantTypes Arguments) Requester @@ -323,7 +345,7 @@ type AccessResponder interface { // SetTokenType set's the responses mandatory token type SetTokenType(tokenType string) - // SetAccessToken returns the responses access token. + // GetAccessToken returns the responses access token. GetAccessToken() (token string) // GetTokenType returns the responses token type.