mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-12 22:10:25 +00:00
fix: endless redirection loop between /user/settings/change_password and /user/settings/security (#10002)
Fixes forgejo/forgejo#9980 ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests - I added test coverage for Go changes... - [ ] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [ ] I do not want this change to show in the release notes. - [x] I want the title to show in the release notes with a link to this pull request. - [ ] I want the content of the `release-notes/<pull request number>.md` to be be used for the release notes instead of the title. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Bug fixes - [PR](https://codeberg.org/forgejo/forgejo/pulls/10002): <!--number 10002 --><!--line 0 --><!--description ZW5kbGVzcyByZWRpcmVjdGlvbiBsb29wIGJldHdlZW4gL3VzZXIvc2V0dGluZ3MvY2hhbmdlX3Bhc3N3b3JkIGFuZCAvdXNlci9zZXR0aW5ncy9zZWN1cml0eQ==-->endless redirection loop between /user/settings/change_password and /user/settings/security<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/10002 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org> Co-authored-by: zokki <zokki.softwareschmiede@gmail.com> Co-committed-by: zokki <zokki.softwareschmiede@gmail.com>
This commit is contained in:
parent
fb9839f16d
commit
dc0a63efe2
3 changed files with 104 additions and 3 deletions
37
models/fixtures/TestTwoFactorWithPasswordChange/user.yml
Normal file
37
models/fixtures/TestTwoFactorWithPasswordChange/user.yml
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
-
|
||||||
|
id: 2001
|
||||||
|
lower_name: user2001
|
||||||
|
name: user2001
|
||||||
|
full_name: "user2001"
|
||||||
|
email: user2001@example.com
|
||||||
|
keep_email_private: false
|
||||||
|
email_notifications_preference: onmention
|
||||||
|
passwd: ZogKvWdyEx:password
|
||||||
|
passwd_hash_algo: dummy
|
||||||
|
must_change_password: true
|
||||||
|
login_source: 0
|
||||||
|
login_name: user2001
|
||||||
|
type: 0
|
||||||
|
salt: ZogKvWdyEx
|
||||||
|
max_repo_creation: -1
|
||||||
|
is_active: true
|
||||||
|
is_admin: false
|
||||||
|
is_restricted: false
|
||||||
|
allow_git_hook: false
|
||||||
|
allow_import_local: false
|
||||||
|
allow_create_organization: true
|
||||||
|
prohibit_login: false
|
||||||
|
avatar: ""
|
||||||
|
avatar_email: user2001@example.com
|
||||||
|
use_custom_avatar: true
|
||||||
|
num_followers: 0
|
||||||
|
num_following: 0
|
||||||
|
num_stars: 0
|
||||||
|
num_repos: 0
|
||||||
|
num_teams: 0
|
||||||
|
num_members: 0
|
||||||
|
visibility: 0
|
||||||
|
repo_admin_change_team_access: false
|
||||||
|
theme: ""
|
||||||
|
keep_activity_private: false
|
||||||
|
created_unix: 1759086716
|
||||||
|
|
@ -167,12 +167,14 @@ func verifyAuthWithOptions(options *common.VerifyOptions) func(ctx *context.Cont
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if ctx.Req.URL.Path == "/user/settings/change_password" {
|
} else if ctx.Req.URL.Path == "/user/settings/change_password" {
|
||||||
|
if ctx.Doer.MustHaveTwoFactor() {
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||||
|
return
|
||||||
|
}
|
||||||
// make sure that the form cannot be accessed by users who don't need this
|
// make sure that the form cannot be accessed by users who don't need this
|
||||||
ctx.Redirect(setting.AppSubURL + "/")
|
ctx.Redirect(setting.AppSubURL + "/")
|
||||||
return
|
return
|
||||||
}
|
} else if ctx.Doer.MustHaveTwoFactor() && !strings.HasPrefix(ctx.Req.URL.Path, "/user/settings/security") {
|
||||||
|
|
||||||
if ctx.Doer.MustHaveTwoFactor() && !strings.HasPrefix(ctx.Req.URL.Path, "/user/settings/security") {
|
|
||||||
hasTwoFactor, err := auth_model.HasTwoFactorByUID(ctx, ctx.Doer.ID)
|
hasTwoFactor, err := auth_model.HasTwoFactorByUID(ctx, ctx.Doer.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error("Error getting 2fa: %s", err)
|
log.Error("Error getting 2fa: %s", err)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"forgejo.org/modules/setting"
|
"forgejo.org/modules/setting"
|
||||||
"forgejo.org/modules/test"
|
"forgejo.org/modules/test"
|
||||||
"forgejo.org/modules/translation"
|
"forgejo.org/modules/translation"
|
||||||
|
"forgejo.org/services/forms"
|
||||||
"forgejo.org/tests"
|
"forgejo.org/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
@ -274,3 +275,64 @@ func TestGlobalTwoFactorRequirement(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTwoFactorWithPasswordChange(t *testing.T) {
|
||||||
|
defer unittest.OverrideFixtures("models/fixtures/TestTwoFactorWithPasswordChange")()
|
||||||
|
|
||||||
|
normalUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
|
||||||
|
changePasswordUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{MustChangePassword: true})
|
||||||
|
|
||||||
|
runTest := func(t *testing.T, user *user_model.User, requireTOTP bool) {
|
||||||
|
t.Helper()
|
||||||
|
defer unittest.AssertSuccessfulDelete(t, &auth.TwoFactor{UID: user.ID})
|
||||||
|
|
||||||
|
session := loginUser(t, user.Name)
|
||||||
|
|
||||||
|
if user.MustChangePassword {
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s", user.Name))
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
assert.Equal(t, "/user/settings/change_password", resp.Header().Get("Location"))
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/user/settings/security")
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
assert.Equal(t, "/user/settings/change_password", resp.Header().Get("Location"))
|
||||||
|
|
||||||
|
req = NewRequestWithJSON(t, "POST", "/user/settings/change_password", forms.MustChangePasswordForm{
|
||||||
|
Password: "password",
|
||||||
|
Retype: "password",
|
||||||
|
})
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
assert.Equal(t, "/user/settings/security", resp.Header().Get("Location"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if requireTOTP {
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s", user.Name))
|
||||||
|
resp := session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
assert.Equal(t, "/user/settings/security", resp.Header().Get("Location"))
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/user/settings/change_password")
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusSeeOther)
|
||||||
|
assert.Equal(t, "/user/settings/security", resp.Header().Get("Location"))
|
||||||
|
|
||||||
|
session.EnrollTOTP(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", fmt.Sprintf("/%s", user.Name))
|
||||||
|
session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("Don't require TwoFactor", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
runTest(t, normalUser, false)
|
||||||
|
runTest(t, changePasswordUser, false)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Require TwoFactor", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
defer test.MockVariableValue(&setting.GlobalTwoFactorRequirement, setting.AllTwoFactorRequirement)()
|
||||||
|
|
||||||
|
runTest(t, normalUser, true)
|
||||||
|
runTest(t, changePasswordUser, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue