diff --git a/models/auth/oauth2.go b/models/auth/oauth2.go index e2bb72b722..d0d5d1e52f 100644 --- a/models/auth/oauth2.go +++ b/models/auth/oauth2.go @@ -537,6 +537,16 @@ func (grant *OAuth2Grant) SetNonce(ctx context.Context, nonce string) error { return nil } +// SetScope updates the scope of a grant +func (grant *OAuth2Grant) SetScope(ctx context.Context, scope string) error { + grant.Scope = scope + _, err := db.GetEngine(ctx).ID(grant.ID).Cols("scope").Update(grant) + if err != nil { + return err + } + return nil +} + // GetOAuth2GrantByID returns the grant with the given ID func GetOAuth2GrantByID(ctx context.Context, id int64) (grant *OAuth2Grant, err error) { grant = new(OAuth2Grant) diff --git a/routers/web/auth/oauth2_provider.go b/routers/web/auth/oauth2_provider.go index 05931d8f59..f75186cf3c 100644 --- a/routers/web/auth/oauth2_provider.go +++ b/routers/web/auth/oauth2_provider.go @@ -286,6 +286,14 @@ func AuthorizeOAuth(ctx *context.Context) { // Redirect if user already granted access and the application is confidential or trusted otherwise // I.e. always require authorization for untrusted public clients as recommended by RFC 6749 Section 10.2 if (app.ConfidentialClient || app.SkipSecondaryAuthorization) && grant != nil { + // Update the grant scope if the requested scope has changed, so that + // confidential/trusted clients always get a token with the current scope. + if grant.Scope != form.Scope { + if err := grant.SetScope(ctx, form.Scope); err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + return + } + } code, err := grant.GenerateNewAuthorizationCode(ctx, form.RedirectURI, form.CodeChallenge, form.CodeChallengeMethod) if err != nil { handleServerError(ctx, form.State, form.RedirectURI) @@ -388,12 +396,14 @@ func GrantApplicationOAuth(ctx *context.Context) { return } } else if grant.Scope != form.Scope { - handleAuthorizeError(ctx, AuthorizeError{ - State: form.State, - ErrorDescription: "a grant exists with different scope", - ErrorCode: ErrorCodeServerError, - }, form.RedirectURI) - return + // The user has re-authorized with a different scope. Update the + // existing grant to match the newly consented scope. This avoids + // an infinite redirect loop when the OAuth2 client changes its + // requested scope after the initial authorization. + if err := grant.SetScope(ctx, form.Scope); err != nil { + handleServerError(ctx, form.State, form.RedirectURI) + return + } } if len(form.Nonce) > 0 {