diff --git a/access_request.go b/access_request.go index c03e74a6d..4f1583757 100644 --- a/access_request.go +++ b/access_request.go @@ -23,3 +23,22 @@ func NewAccessRequest(session Session) *AccessRequest { func (a *AccessRequest) GetGrantTypes() Arguments { return a.GrantTypes } + +func (a *AccessRequest) SetGrantedScopes(scopes Arguments) { + a.GrantedScope = scopes +} + +func (a *AccessRequest) SanitizeRestoreRefreshTokenOriginalRequester(requester Requester) Requester { + r := a.Sanitize(nil).(*Request) + + ar := &AccessRequest{ + Request: *r, + } + + ar.SetID(requester.GetID()) + + ar.SetRequestedScopes(requester.GetRequestedScopes()) + ar.SetGrantedScopes(requester.GetGrantedScopes()) + + return ar +} diff --git a/handler/oauth2/flow_refresh.go b/handler/oauth2/flow_refresh.go index c3c5788ec..3b7f89939 100644 --- a/handler/oauth2/flow_refresh.go +++ b/handler/oauth2/flow_refresh.go @@ -30,6 +30,11 @@ type RefreshTokenGrantHandler struct { fosite.AudienceStrategyProvider fosite.RefreshTokenScopesProvider } + + // IgnoreRequestedScopeNotInOriginalGrant determines the action to take when the requested scopes in the refresh + // flow were not originally granted. If false which is the default the handler will automatically return an error. + // If true the handler will filter out / ignore the scopes which were not originally granted. + IgnoreRequestedScopeNotInOriginalGrant bool } // HandleTokenEndpointRequest implements https://tools.ietf.org/html/rfc6749#section-6 @@ -79,13 +84,43 @@ func (c *RefreshTokenGrantHandler) HandleTokenEndpointRequest(ctx context.Contex request.SetID(originalRequest.GetID()) request.SetSession(originalRequest.GetSession().Clone()) - request.SetRequestedScopes(originalRequest.GetRequestedScopes()) + /* + There are two key points in the following spec section this addresses: + 1. If omitted the scope param should be treated as the same as the scope originally granted by the resource owner. + 2. The REQUESTED scope MUST NOT include any scope not originally granted. + scope + OPTIONAL. The scope of the access request as described by Section 3.3. The requested scope MUST NOT + include any scope not originally granted by the resource owner, and if omitted is treated as equal to + the scope originally granted by the resource owner. + See https://www.rfc-editor.org/rfc/rfc6749#section-6 + */ + + // Addresses point 1 of the text in RFC6749 Section 6. + if len(request.GetRequestedScopes()) == 0 { + request.SetRequestedScopes(originalRequest.GetGrantedScopes()) + } + request.SetRequestedAudience(originalRequest.GetRequestedAudience()) - for _, scope := range originalRequest.GetGrantedScopes() { - if !c.Config.GetScopeStrategy(ctx)(request.GetClient().GetScopes(), scope) { + strategy := c.Config.GetScopeStrategy(ctx) + originalScopes := originalRequest.GetGrantedScopes() + + for _, scope := range request.GetRequestedScopes() { + if !strategy(originalScopes, scope) { + if c.IgnoreRequestedScopeNotInOriginalGrant { + // Skips addressing point 2 of the text in RFC6749 Section 6 and instead just prevents the scope + // requested from being granted. + continue + } else { + // Addresses point 2 of the text in RFC6749 Section 6. + return errorsx.WithStack(fosite.ErrInvalidScope.WithHintf("The requested scope '%s' was not originally granted by the resource owner.", 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)) } + request.GrantScope(scope) } @@ -134,26 +169,36 @@ func (c *RefreshTokenGrantHandler) PopulateTokenEndpointResponse(ctx context.Con err = c.handleRefreshTokenEndpointStorageError(ctx, err) }() - ts, err := c.TokenRevocationStorage.GetRefreshTokenSession(ctx, signature, nil) + originalRequest, err := c.TokenRevocationStorage.GetRefreshTokenSession(ctx, signature, nil) if err != nil { return err - } else if err := c.TokenRevocationStorage.RevokeAccessToken(ctx, ts.GetID()); err != nil { + } else if err := c.TokenRevocationStorage.RevokeAccessToken(ctx, originalRequest.GetID()); err != nil { return err } - if err := c.TokenRevocationStorage.RevokeRefreshTokenMaybeGracePeriod(ctx, ts.GetID(), signature); err != nil { + if err := c.TokenRevocationStorage.RevokeRefreshTokenMaybeGracePeriod(ctx, originalRequest.GetID(), signature); err != nil { return err } storeReq := requester.Sanitize([]string{}) - storeReq.SetID(ts.GetID()) + storeReq.SetID(originalRequest.GetID()) if err = c.TokenRevocationStorage.CreateAccessTokenSession(ctx, accessSignature, storeReq); err != nil { return err } - if err = c.TokenRevocationStorage.CreateRefreshTokenSession(ctx, refreshSignature, storeReq); err != nil { - return err + if rtRequest, ok := requester.(fosite.RefreshTokenAccessRequester); ok { + rtStoreReq := rtRequest.SanitizeRestoreRefreshTokenOriginalRequester(originalRequest) + + rtStoreReq.SetSession(requester.GetSession().Clone()) + + 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/handler/oauth2/flow_refresh_test.go b/handler/oauth2/flow_refresh_test.go index bed4ba581..0f4f390b4 100644 --- a/handler/oauth2/flow_refresh_test.go +++ b/handler/oauth2/flow_refresh_test.go @@ -173,12 +173,103 @@ func TestRefreshFlow_HandleTokenEndpointRequest(t *testing.T) { assert.NotEqual(t, sess, areq.Session) assert.NotEqual(t, time.Now().UTC().Add(-time.Hour).Round(time.Hour), areq.RequestedAt) assert.Equal(t, fosite.Arguments{"foo", "offline"}, areq.GrantedScope) - assert.Equal(t, fosite.Arguments{"foo", "bar", "offline"}, areq.RequestedScope) + assert.Equal(t, fosite.Arguments{"foo", "offline"}, areq.RequestedScope) assert.NotEqual(t, url.Values{"foo": []string{"bar"}}, areq.Form) assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AccessToken)) assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.RefreshToken)) }, }, + { + description: "should pass with scope in form", + setup: func(config *fosite.Config) { + areq.GrantTypes = fosite.Arguments{"refresh_token"} + areq.Client = &fosite.DefaultClient{ + ID: "foo", + GrantTypes: fosite.Arguments{"refresh_token"}, + Scopes: []string{"foo", "bar", "baz", "offline"}, + } + + token, sig, err := strategy.GenerateRefreshToken(nil, nil) + require.NoError(t, err) + + areq.Form.Add("refresh_token", token) + areq.Form.Add("scope", "foo bar baz offline") + err = store.CreateRefreshTokenSession(nil, sig, &fosite.Request{ + Client: areq.Client, + GrantedScope: fosite.Arguments{"foo", "bar", "baz", "offline"}, + RequestedScope: fosite.Arguments{"foo", "bar", "baz", "offline"}, + Session: sess, + Form: url.Values{"foo": []string{"bar"}}, + RequestedAt: time.Now().UTC().Add(-time.Hour).Round(time.Hour), + }) + require.NoError(t, err) + }, + expect: func(t *testing.T) { + assert.Equal(t, fosite.Arguments{"foo", "bar", "baz", "offline"}, areq.GrantedScope) + assert.Equal(t, fosite.Arguments{"foo", "bar", "baz", "offline"}, areq.RequestedScope) + }, + }, + { + description: "should pass with scope in form and should narrow scopes", + setup: func(config *fosite.Config) { + areq.GrantTypes = fosite.Arguments{"refresh_token"} + areq.Client = &fosite.DefaultClient{ + ID: "foo", + GrantTypes: fosite.Arguments{"refresh_token"}, + Scopes: []string{"foo", "bar", "baz", "offline"}, + } + + token, sig, err := strategy.GenerateRefreshToken(nil, nil) + require.NoError(t, err) + + areq.Form.Add("refresh_token", token) + areq.Form.Add("scope", "foo bar offline") + areq.SetRequestedScopes(fosite.Arguments{"foo", "bar", "offline"}) + + err = store.CreateRefreshTokenSession(nil, sig, &fosite.Request{ + Client: areq.Client, + GrantedScope: fosite.Arguments{"foo", "bar", "baz", "offline"}, + RequestedScope: fosite.Arguments{"foo", "bar", "baz", "offline"}, + Session: sess, + Form: url.Values{"foo": []string{"bar"}}, + RequestedAt: time.Now().UTC().Add(-time.Hour).Round(time.Hour), + }) + require.NoError(t, err) + }, + expect: func(t *testing.T) { + assert.Equal(t, fosite.Arguments{"foo", "bar", "offline"}, areq.GrantedScope) + assert.Equal(t, fosite.Arguments{"foo", "bar", "offline"}, areq.RequestedScope) + }, + }, + { + description: "should fail with broadened scopes even if the client can request it", + setup: func(config *fosite.Config) { + areq.GrantTypes = fosite.Arguments{"refresh_token"} + areq.Client = &fosite.DefaultClient{ + ID: "foo", + GrantTypes: fosite.Arguments{"refresh_token"}, + Scopes: []string{"foo", "bar", "baz", "offline"}, + } + + token, sig, err := strategy.GenerateRefreshToken(nil, nil) + require.NoError(t, err) + + areq.Form.Add("refresh_token", token) + areq.Form.Add("scope", "foo bar offline") + areq.SetRequestedScopes(fosite.Arguments{"foo", "bar", "offline"}) + + err = store.CreateRefreshTokenSession(nil, sig, &fosite.Request{ + Client: areq.Client, + GrantedScope: fosite.Arguments{"foo", "baz", "offline"}, + RequestedScope: fosite.Arguments{"foo", "baz", "offline"}, + Session: sess, + Form: url.Values{"foo": []string{"bar"}}, + RequestedAt: time.Now().UTC().Add(-time.Hour).Round(time.Hour), + }) + require.NoError(t, err) + }, + expectErr: fosite.ErrInvalidScope, + }, { description: "should pass with custom client lifespans", setup: func(config *fosite.Config) { @@ -211,7 +302,7 @@ func TestRefreshFlow_HandleTokenEndpointRequest(t *testing.T) { assert.NotEqual(t, sess, areq.Session) assert.NotEqual(t, time.Now().UTC().Add(-time.Hour).Round(time.Hour), areq.RequestedAt) assert.Equal(t, fosite.Arguments{"foo", "offline"}, areq.GrantedScope) - assert.Equal(t, fosite.Arguments{"foo", "bar", "offline"}, areq.RequestedScope) + assert.Equal(t, fosite.Arguments{"foo", "offline"}, areq.RequestedScope) assert.NotEqual(t, url.Values{"foo": []string{"bar"}}, areq.Form) internal.RequireEqualTime(t, time.Now().Add(*internal.TestLifespans.RefreshTokenGrantAccessTokenLifespan).UTC(), areq.GetSession().GetExpiresAt(fosite.AccessToken), time.Minute) internal.RequireEqualTime(t, time.Now().Add(*internal.TestLifespans.RefreshTokenGrantRefreshTokenLifespan).UTC(), areq.GetSession().GetExpiresAt(fosite.RefreshToken), time.Minute) @@ -272,7 +363,7 @@ func TestRefreshFlow_HandleTokenEndpointRequest(t *testing.T) { assert.NotEqual(t, sess, areq.Session) assert.NotEqual(t, time.Now().UTC().Add(-time.Hour).Round(time.Hour), areq.RequestedAt) assert.Equal(t, fosite.Arguments{"foo"}, areq.GrantedScope) - assert.Equal(t, fosite.Arguments{"foo", "bar"}, areq.RequestedScope) + assert.Equal(t, fosite.Arguments{"foo"}, areq.RequestedScope) assert.NotEqual(t, url.Values{"foo": []string{"bar"}}, areq.Form) assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.AccessToken)) assert.Equal(t, time.Now().Add(time.Hour).UTC().Round(time.Second), areq.GetSession().GetExpiresAt(fosite.RefreshToken)) diff --git a/integration/helper_endpoints_test.go b/integration/helper_endpoints_test.go index 76e4b5201..b9a4656e6 100644 --- a/integration/helper_endpoints_test.go +++ b/integration/helper_endpoints_test.go @@ -77,8 +77,22 @@ func authEndpointHandler(t *testing.T, oauth2 fosite.OAuth2Provider, session fos return } - if ar.GetRequestedScopes().Has("fosite") { - ar.GrantScope("fosite") + if ar.GetClient().GetID() == "grant-all-requested-scopes-client" { + for _, scope := range ar.GetRequestedScopes() { + ar.GrantScope(scope) + } + } else { + if ar.GetRequestedScopes().Has("fosite") { + ar.GrantScope("fosite") + } + + if ar.GetRequestedScopes().Has("offline") { + ar.GrantScope("offline") + } + + if ar.GetRequestedScopes().Has("openid") { + ar.GrantScope("openid") + } } if ar.GetRequestedScopes().Has("offline") { @@ -146,7 +160,7 @@ func tokenEndpointHandler(t *testing.T, provider fosite.OAuth2Provider) func(rw response, err := provider.NewAccessResponse(ctx, accessRequest) if err != nil { - t.Logf("Access request failed because: %+v", err) + t.Logf("Access request failed because: %+v", fosite.ErrorToRFC6749Error(err).WithExposeDebug(true).GetDescription()) t.Logf("Request: %+v", accessRequest) provider.WriteAccessError(req.Context(), rw, accessRequest, err) return diff --git a/integration/refresh_token_grant_test.go b/integration/refresh_token_grant_test.go index 89f73db39..1b7e959b0 100644 --- a/integration/refresh_token_grant_test.go +++ b/integration/refresh_token_grant_test.go @@ -4,6 +4,7 @@ package integration_test import ( + "context" "encoding/json" "net/http" "net/http/httptest" @@ -20,6 +21,7 @@ import ( "github.com/ory/fosite" "github.com/ory/fosite/compose" + hoauth2 "github.com/ory/fosite/handler/oauth2" "github.com/ory/fosite/handler/openid" "github.com/ory/fosite/token/jwt" ) @@ -265,3 +267,294 @@ func TestRefreshTokenFlow(t *testing.T) { }) } } + +func TestRefreshTokenFlowScopeParameter(t *testing.T) { + type testCase struct { + name string + scopes fosite.Arguments + expected fosite.Arguments + err string + } + + type step struct { + OAuth2 *oauth2.Token + SessionAT, SessionRT fosite.Requester + } + + originalScopes := fosite.Arguments{"openid", "offline", "offline_access", "foo", "bar"} + + scenarios := []struct { + name string + ignore bool + checkTime bool + testCases []testCase + }{ + { + "ShouldPassRFC", + false, + true, + []testCase{ + { + "ShouldGrantOriginalScopesWhenOmitted", + nil, + originalScopes, + "", + }, + { + "ShouldNarrowScopesWhenIncluded", + fosite.Arguments{"openid", "offline_access", "foo"}, + fosite.Arguments{"openid", "offline_access", "foo"}, + "", + }, + { + "ShouldGrantOriginalScopesWhenOmittedAfterNarrowing", + nil, + originalScopes, + "", + }, + { + "ShouldGrantOriginalScopesExplicitlyRequested", + originalScopes, + originalScopes, + "", + }, + { + "ShouldErrorWhenBroadeningScopesAllowedByClientButNotOriginallyGranted", + fosite.Arguments{"openid", "offline", "offline_access", "foo", "bar", "baz"}, + nil, + "The requested scope is invalid, unknown, or malformed. The requested scope 'baz' was not originally granted by the resource owner.", + }, + }, + }, + { + "ShouldPassIgnoreFilter", + true, + false, + []testCase{ + { + "ShouldGrantOriginalScopesWhenOmitted", + nil, + originalScopes, + "", + }, + { + "ShouldNarrowScopesWhenIncluded", + fosite.Arguments{"openid", "offline_access", "foo"}, + fosite.Arguments{"openid", "offline_access", "foo"}, + "", + }, + { + "ShouldGrantOriginalScopesWhenOmittedAfterNarrowing", + nil, + originalScopes, + "", + }, + { + "ShouldGrantOriginalScopesExplicitlyRequested", + originalScopes, + originalScopes, + "", + }, + { + "ShouldErrorWhenBroadeningScopesAllowedByClientButNotOriginallyGranted", + fosite.Arguments{"openid", "offline", "offline_access", "foo", "bar", "baz"}, + fosite.Arguments{"openid", "offline", "offline_access", "foo", "bar"}, + "", + }, + }, + }, + } + + state := "1234567890" + + for _, scenario := range scenarios { + t.Run(scenario.name, func(t *testing.T) { + ctx := context.Background() + + session := &defaultSession{ + DefaultSession: &openid.DefaultSession{ + Claims: &jwt.IDTokenClaims{ + Subject: "peter", + }, + Headers: &jwt.Headers{}, + Subject: "peter", + Username: "peteru", + }, + } + + fc := new(fosite.Config) + fc.GlobalSecret = []byte("some-secret-thats-random-some-secret-thats-random-") + fc.ScopeStrategy = fosite.ExactScopeStrategy + + s := compose.NewOAuth2HMACStrategy(fc) + + var f fosite.OAuth2Provider + + if scenario.ignore { + keyGetter := func(context.Context) (interface{}, error) { + return gen.MustRSAKey(), nil + } + + // OAuth2RefreshTokenGrantFactory creates an OAuth2 refresh grant handler and registers + // an access token, refresh token and authorize code validator.nmj + factoryRefresh := func(config fosite.Configurator, storage interface{}, strategy interface{}) interface{} { + return &hoauth2.RefreshTokenGrantHandler{ + AccessTokenStrategy: strategy.(hoauth2.AccessTokenStrategy), + RefreshTokenStrategy: strategy.(hoauth2.RefreshTokenStrategy), + TokenRevocationStorage: storage.(hoauth2.TokenRevocationStorage), + Config: config, + IgnoreRequestedScopeNotInOriginalGrant: true, + } + } + + f = compose.Compose( + fc, + fositeStore, + &compose.CommonStrategy{ + CoreStrategy: compose.NewOAuth2HMACStrategy(fc), + OpenIDConnectTokenStrategy: compose.NewOpenIDConnectStrategy(keyGetter, fc), + Signer: &jwt.DefaultSigner{GetPrivateKey: keyGetter}, + }, + compose.OAuth2AuthorizeExplicitFactory, + compose.OAuth2AuthorizeImplicitFactory, + compose.OAuth2ClientCredentialsGrantFactory, + factoryRefresh, + compose.OAuth2ResourceOwnerPasswordCredentialsFactory, + compose.RFC7523AssertionGrantFactory, + + compose.OpenIDConnectExplicitFactory, + compose.OpenIDConnectImplicitFactory, + compose.OpenIDConnectHybridFactory, + compose.OpenIDConnectRefreshFactory, + + compose.OAuth2TokenIntrospectionFactory, + compose.OAuth2TokenRevocationFactory, + + compose.OAuth2PKCEFactory, + compose.PushedAuthorizeHandlerFactory, + ) + } else { + f = compose.ComposeAllEnabled(fc, fositeStore, gen.MustRSAKey()) + } + + ts := mockServer(t, f, session) + defer ts.Close() + + client := newOAuth2Client(ts) + client.Scopes = []string{"openid", "offline", "offline_access", "foo", "bar"} + client.ClientID = "grant-all-requested-scopes-client" + + testRefreshingClient := &fosite.DefaultClient{ + ID: "grant-all-requested-scopes-client", + Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar" + RedirectURIs: []string{ts.URL + "/callback"}, + ResponseTypes: []string{"code"}, + GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"}, + Scopes: []string{"openid", "offline_access", "offline", "foo", "bar", "baz"}, + Audience: []string{"https://www.ory.sh/api"}, + } + + fositeStore.Clients["grant-all-requested-scopes-client"] = testRefreshingClient + + entries := make([]step, len(scenario.testCases)+1) + + resp, err := http.Get(client.AuthCodeURL(state)) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + + entries[0].OAuth2, err = client.Exchange(ctx, resp.Request.URL.Query().Get("code"), oauth2.SetAuthURLParam("client_id", client.ClientID)) + + require.NoError(t, err) + require.NotEmpty(t, entries[0].OAuth2.AccessToken) + require.NotEmpty(t, entries[0].OAuth2.RefreshToken) + + assert.Equal(t, strings.Join(originalScopes, " "), entries[0].OAuth2.Extra("scope")) + + entries[0].SessionAT, err = fositeStore.GetAccessTokenSession(ctx, s.AccessTokenSignature(ctx, entries[0].OAuth2.AccessToken), nil) + require.NoError(t, err) + + entries[0].SessionRT, err = fositeStore.GetRefreshTokenSession(ctx, s.RefreshTokenSignature(ctx, entries[0].OAuth2.RefreshToken), nil) + require.NoError(t, err) + + assert.ElementsMatch(t, entries[0].SessionAT.GetRequestedScopes(), originalScopes) + assert.ElementsMatch(t, entries[0].SessionRT.GetRequestedScopes(), originalScopes) + assert.ElementsMatch(t, entries[0].SessionAT.GetGrantedScopes(), originalScopes) + assert.ElementsMatch(t, entries[0].SessionRT.GetGrantedScopes(), originalScopes) + assert.Equal(t, strings.Join(originalScopes, " "), entries[0].OAuth2.Extra("scope")) + + for i, tc := range scenario.testCases { + t.Run(tc.name, func(t *testing.T) { + if scenario.checkTime { + time.Sleep(time.Second) + } + + idx := i + 1 + + opts := []oauth2.AuthCodeOption{ + oauth2.SetAuthURLParam("refresh_token", entries[i].OAuth2.RefreshToken), + oauth2.SetAuthURLParam("grant_type", "refresh_token"), + } + + if len(tc.scopes) != 0 { + opts = append(opts, oauth2.SetAuthURLParam("scope", strings.Join(tc.scopes, " ")), oauth2.SetAuthURLParam("client_id", client.ClientID)) + } + + entries[idx].OAuth2, err = client.Exchange(ctx, "", opts...) + if len(tc.err) != 0 { + require.Error(t, err) + require.Nil(t, entries[idx].OAuth2) + require.Contains(t, err.Error(), tc.err) + + return + } + + require.NoError(t, err) + require.NotEmpty(t, entries[idx].OAuth2.AccessToken) + require.NotEmpty(t, entries[idx].OAuth2.RefreshToken) + + entries[idx].SessionAT, err = fositeStore.GetAccessTokenSession(ctx, s.AccessTokenSignature(ctx, entries[idx].OAuth2.AccessToken), nil) + require.NoError(t, err) + + entries[idx].SessionRT, err = fositeStore.GetRefreshTokenSession(ctx, s.RefreshTokenSignature(ctx, entries[idx].OAuth2.RefreshToken), nil) + require.NoError(t, err) + + if len(tc.scopes) != 0 { + assert.ElementsMatch(t, entries[idx].SessionAT.GetRequestedScopes(), tc.scopes) + assert.Equal(t, strings.Join(tc.expected, " "), entries[idx].OAuth2.Extra("scope")) + } else { + assert.ElementsMatch(t, entries[idx].SessionAT.GetRequestedScopes(), originalScopes) + assert.Equal(t, strings.Join(originalScopes, " "), entries[idx].OAuth2.Extra("scope")) + } + assert.ElementsMatch(t, entries[idx].SessionAT.GetGrantedScopes(), tc.expected) + assert.ElementsMatch(t, entries[idx].SessionRT.GetRequestedScopes(), originalScopes) + assert.ElementsMatch(t, entries[idx].SessionRT.GetGrantedScopes(), originalScopes) + + var ( + j int + entry step + ) + + assert.Equal(t, entries[idx].SessionAT.GetID(), entries[idx].SessionRT.GetID()) + + for j, entry = range entries { + if j == idx { + break + } + + assert.Equal(t, entries[idx].SessionAT.GetID(), entry.SessionAT.GetID()) + assert.Equal(t, entries[idx].SessionAT.GetID(), entry.SessionRT.GetID()) + assert.Equal(t, entries[idx].SessionRT.GetID(), entry.SessionAT.GetID()) + assert.Equal(t, entries[idx].SessionRT.GetID(), entry.SessionRT.GetID()) + + if scenario.checkTime { + assert.Greater(t, entries[idx].SessionAT.GetSession().GetExpiresAt(fosite.AccessToken).Unix(), entry.SessionAT.GetSession().GetExpiresAt(fosite.AccessToken).Unix()) + assert.Greater(t, entries[idx].SessionRT.GetSession().GetExpiresAt(fosite.RefreshToken).Unix(), entry.SessionRT.GetSession().GetExpiresAt(fosite.RefreshToken).Unix()) + assert.Greater(t, entries[idx].SessionAT.GetRequestedAt().Unix(), entry.SessionAT.GetRequestedAt().Unix()) + assert.Greater(t, entries[idx].SessionRT.GetRequestedAt().Unix(), entry.SessionRT.GetRequestedAt().Unix()) + } + } + }) + } + }) + } +} diff --git a/oauth2.go b/oauth2.go index eb676d500..f3246cecb 100644 --- a/oauth2.go +++ b/oauth2.go @@ -245,6 +245,16 @@ type Requester interface { Sanitize(allowedParameters []string) Requester } +// RefreshTokenAccessRequester is an extended AccessRequester implementation that allows preserving +// the original Requester. +type RefreshTokenAccessRequester interface { + // SanitizeRestoreRefreshTokenOriginalRequester returns a sanitized copy of this Requester and mutates the relevant + // values from the provided Requester which is the original refresh token session Requester. + SanitizeRestoreRefreshTokenOriginalRequester(requester Requester) Requester + + AccessRequester +} + // AccessRequester is a token endpoint's request context. type AccessRequester interface { // GetGrantType returns the requests grant type.