1
0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2024-12-22 12:54:53 -05:00
forgejo/services/user/user_test.go

265 lines
9.8 KiB
Go
Raw Permalink Normal View History

// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package user
import (
"fmt"
Do not rewrite ssh keys files when deleting a user without one (#6097) ### Problem Big instances can have huge authorized_keys files when using OpenSSH instead of the internal ssh server. Forgejo always re-generates the contents of that file when a user is deleted, even if he does not even have a public key uploaded. In case of codeberg.org, a 15MB file gets rewritten. If we batch delete 100 Spam users without ssh keys, we rewrite 1.5GB, this takes time and wears the SSD. In addition, there is a high chance of hitting a race contidion bug, when deleting users in parallel. ### Solution / Mitigation This patch prevents rewriting authorized_keys files, when not necessary. It greatly speeds up deleting malicious users, saves IO bandwidth and SSD wear. It also greatly reduces the chance of hitting a race condition bug. Fixing the race condition is not the scope of this patch though. ## 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... - [x] in their respective `*_test.go` for unit tests. - [ ] 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 - [x] I do not want this change to show in the release notes. - [ ] 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. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6097 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org> Co-committed-by: Andreas Shimokawa <shimokawa@fsfe.org> (cherry picked from commit 3c9b3ddf5c727d5d79bfb790454791551ffdd63a)
2024-12-05 16:32:09 -05:00
"os"
"path/filepath"
"strings"
"testing"
"time"
"code.gitea.io/gitea/models"
Do not rewrite ssh keys files when deleting a user without one (#6097) ### Problem Big instances can have huge authorized_keys files when using OpenSSH instead of the internal ssh server. Forgejo always re-generates the contents of that file when a user is deleted, even if he does not even have a public key uploaded. In case of codeberg.org, a 15MB file gets rewritten. If we batch delete 100 Spam users without ssh keys, we rewrite 1.5GB, this takes time and wears the SSD. In addition, there is a high chance of hitting a race contidion bug, when deleting users in parallel. ### Solution / Mitigation This patch prevents rewriting authorized_keys files, when not necessary. It greatly speeds up deleting malicious users, saves IO bandwidth and SSD wear. It also greatly reduces the chance of hitting a race condition bug. Fixing the race condition is not the scope of this patch though. ## 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... - [x] in their respective `*_test.go` for unit tests. - [ ] 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 - [x] I do not want this change to show in the release notes. - [ ] 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. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6097 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org> Co-committed-by: Andreas Shimokawa <shimokawa@fsfe.org> (cherry picked from commit 3c9b3ddf5c727d5d79bfb790454791551ffdd63a)
2024-12-05 16:32:09 -05:00
asymkey_model "code.gitea.io/gitea/models/asymkey"
"code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/organization"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/setting"
Do not rewrite ssh keys files when deleting a user without one (#6097) ### Problem Big instances can have huge authorized_keys files when using OpenSSH instead of the internal ssh server. Forgejo always re-generates the contents of that file when a user is deleted, even if he does not even have a public key uploaded. In case of codeberg.org, a 15MB file gets rewritten. If we batch delete 100 Spam users without ssh keys, we rewrite 1.5GB, this takes time and wears the SSD. In addition, there is a high chance of hitting a race contidion bug, when deleting users in parallel. ### Solution / Mitigation This patch prevents rewriting authorized_keys files, when not necessary. It greatly speeds up deleting malicious users, saves IO bandwidth and SSD wear. It also greatly reduces the chance of hitting a race condition bug. Fixing the race condition is not the scope of this patch though. ## 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... - [x] in their respective `*_test.go` for unit tests. - [ ] 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 - [x] I do not want this change to show in the release notes. - [ ] 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. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6097 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org> Co-committed-by: Andreas Shimokawa <shimokawa@fsfe.org> (cherry picked from commit 3c9b3ddf5c727d5d79bfb790454791551ffdd63a)
2024-12-05 16:32:09 -05:00
"code.gitea.io/gitea/modules/test"
"code.gitea.io/gitea/modules/timeutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestMain(m *testing.M) {
unittest.MainTest(m)
}
func TestDeleteUser(t *testing.T) {
test := func(userID int64) {
require.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
ownedRepos := make([]*repo_model.Repository, 0, 10)
require.NoError(t, db.GetEngine(db.DefaultContext).Find(&ownedRepos, &repo_model.Repository{OwnerID: userID}))
if len(ownedRepos) > 0 {
err := DeleteUser(db.DefaultContext, user, false)
require.Error(t, err)
assert.True(t, models.IsErrUserOwnRepos(err))
return
}
orgUsers := make([]*organization.OrgUser, 0, 10)
require.NoError(t, db.GetEngine(db.DefaultContext).Find(&orgUsers, &organization.OrgUser{UID: userID}))
for _, orgUser := range orgUsers {
if err := models.RemoveOrgUser(db.DefaultContext, orgUser.OrgID, orgUser.UID); err != nil {
assert.True(t, organization.IsErrLastOrgOwner(err))
return
}
}
require.NoError(t, DeleteUser(db.DefaultContext, user, false))
unittest.AssertNotExistsBean(t, &user_model.User{ID: userID})
unittest.CheckConsistencyFor(t, &user_model.User{}, &repo_model.Repository{})
}
test(2)
test(4)
test(8)
test(11)
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
require.Error(t, DeleteUser(db.DefaultContext, org, false))
}
func TestPurgeUser(t *testing.T) {
Do not rewrite ssh keys files when deleting a user without one (#6097) ### Problem Big instances can have huge authorized_keys files when using OpenSSH instead of the internal ssh server. Forgejo always re-generates the contents of that file when a user is deleted, even if he does not even have a public key uploaded. In case of codeberg.org, a 15MB file gets rewritten. If we batch delete 100 Spam users without ssh keys, we rewrite 1.5GB, this takes time and wears the SSD. In addition, there is a high chance of hitting a race contidion bug, when deleting users in parallel. ### Solution / Mitigation This patch prevents rewriting authorized_keys files, when not necessary. It greatly speeds up deleting malicious users, saves IO bandwidth and SSD wear. It also greatly reduces the chance of hitting a race condition bug. Fixing the race condition is not the scope of this patch though. ## 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... - [x] in their respective `*_test.go` for unit tests. - [ ] 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 - [x] I do not want this change to show in the release notes. - [ ] 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. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6097 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org> Co-committed-by: Andreas Shimokawa <shimokawa@fsfe.org> (cherry picked from commit 3c9b3ddf5c727d5d79bfb790454791551ffdd63a)
2024-12-05 16:32:09 -05:00
defer unittest.OverrideFixtures(
unittest.FixturesOptions{
Dir: filepath.Join(setting.AppWorkPath, "models/fixtures/"),
Base: setting.AppWorkPath,
Dirs: []string{"services/user/TestPurgeUser/"},
},
)()
require.NoError(t, unittest.PrepareTestDatabase())
defer test.MockVariableValue(&setting.SSH.RootPath, t.TempDir())()
defer test.MockVariableValue(&setting.SSH.CreateAuthorizedKeysFile, true)()
defer test.MockVariableValue(&setting.SSH.CreateAuthorizedPrincipalsFile, true)()
defer test.MockVariableValue(&setting.SSH.StartBuiltinServer, false)()
require.NoError(t, asymkey_model.RewriteAllPublicKeys(db.DefaultContext))
require.NoError(t, asymkey_model.RewriteAllPrincipalKeys(db.DefaultContext))
test := func(userID int64, modifySSHKey bool) {
require.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
Do not rewrite ssh keys files when deleting a user without one (#6097) ### Problem Big instances can have huge authorized_keys files when using OpenSSH instead of the internal ssh server. Forgejo always re-generates the contents of that file when a user is deleted, even if he does not even have a public key uploaded. In case of codeberg.org, a 15MB file gets rewritten. If we batch delete 100 Spam users without ssh keys, we rewrite 1.5GB, this takes time and wears the SSD. In addition, there is a high chance of hitting a race contidion bug, when deleting users in parallel. ### Solution / Mitigation This patch prevents rewriting authorized_keys files, when not necessary. It greatly speeds up deleting malicious users, saves IO bandwidth and SSD wear. It also greatly reduces the chance of hitting a race condition bug. Fixing the race condition is not the scope of this patch though. ## 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... - [x] in their respective `*_test.go` for unit tests. - [ ] 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 - [x] I do not want this change to show in the release notes. - [ ] 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. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6097 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org> Co-committed-by: Andreas Shimokawa <shimokawa@fsfe.org> (cherry picked from commit 3c9b3ddf5c727d5d79bfb790454791551ffdd63a)
2024-12-05 16:32:09 -05:00
fAuthorizedKeys, err := os.Open(filepath.Join(setting.SSH.RootPath, "authorized_keys"))
require.NoError(t, err)
authorizedKeysStatBefore, err := fAuthorizedKeys.Stat()
require.NoError(t, err)
fAuthorizedPrincipals, err := os.Open(filepath.Join(setting.SSH.RootPath, "authorized_principals"))
require.NoError(t, err)
authorizedPrincipalsBefore, err := fAuthorizedPrincipals.Stat()
require.NoError(t, err)
Do not rewrite ssh keys files when deleting a user without one (#6097) ### Problem Big instances can have huge authorized_keys files when using OpenSSH instead of the internal ssh server. Forgejo always re-generates the contents of that file when a user is deleted, even if he does not even have a public key uploaded. In case of codeberg.org, a 15MB file gets rewritten. If we batch delete 100 Spam users without ssh keys, we rewrite 1.5GB, this takes time and wears the SSD. In addition, there is a high chance of hitting a race contidion bug, when deleting users in parallel. ### Solution / Mitigation This patch prevents rewriting authorized_keys files, when not necessary. It greatly speeds up deleting malicious users, saves IO bandwidth and SSD wear. It also greatly reduces the chance of hitting a race condition bug. Fixing the race condition is not the scope of this patch though. ## 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... - [x] in their respective `*_test.go` for unit tests. - [ ] 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 - [x] I do not want this change to show in the release notes. - [ ] 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. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6097 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org> Co-committed-by: Andreas Shimokawa <shimokawa@fsfe.org> (cherry picked from commit 3c9b3ddf5c727d5d79bfb790454791551ffdd63a)
2024-12-05 16:32:09 -05:00
require.NoError(t, DeleteUser(db.DefaultContext, user, true))
unittest.AssertNotExistsBean(t, &user_model.User{ID: userID})
unittest.CheckConsistencyFor(t, &user_model.User{}, &repo_model.Repository{})
Do not rewrite ssh keys files when deleting a user without one (#6097) ### Problem Big instances can have huge authorized_keys files when using OpenSSH instead of the internal ssh server. Forgejo always re-generates the contents of that file when a user is deleted, even if he does not even have a public key uploaded. In case of codeberg.org, a 15MB file gets rewritten. If we batch delete 100 Spam users without ssh keys, we rewrite 1.5GB, this takes time and wears the SSD. In addition, there is a high chance of hitting a race contidion bug, when deleting users in parallel. ### Solution / Mitigation This patch prevents rewriting authorized_keys files, when not necessary. It greatly speeds up deleting malicious users, saves IO bandwidth and SSD wear. It also greatly reduces the chance of hitting a race condition bug. Fixing the race condition is not the scope of this patch though. ## 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... - [x] in their respective `*_test.go` for unit tests. - [ ] 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 - [x] I do not want this change to show in the release notes. - [ ] 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. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6097 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org> Co-committed-by: Andreas Shimokawa <shimokawa@fsfe.org> (cherry picked from commit 3c9b3ddf5c727d5d79bfb790454791551ffdd63a)
2024-12-05 16:32:09 -05:00
fAuthorizedKeys, err = os.Open(filepath.Join(setting.SSH.RootPath, "authorized_keys"))
require.NoError(t, err)
fAuthorizedPrincipals, err = os.Open(filepath.Join(setting.SSH.RootPath, "authorized_principals"))
require.NoError(t, err)
authorizedKeysStatAfter, err := fAuthorizedKeys.Stat()
require.NoError(t, err)
authorizedPrincipalsAfter, err := fAuthorizedPrincipals.Stat()
require.NoError(t, err)
if modifySSHKey {
assert.Greater(t, authorizedKeysStatAfter.ModTime(), authorizedKeysStatBefore.ModTime())
assert.Greater(t, authorizedPrincipalsAfter.ModTime(), authorizedPrincipalsBefore.ModTime())
} else {
assert.Equal(t, authorizedKeysStatAfter.ModTime(), authorizedKeysStatBefore.ModTime())
assert.Equal(t, authorizedPrincipalsAfter.ModTime(), authorizedPrincipalsBefore.ModTime())
}
}
Do not rewrite ssh keys files when deleting a user without one (#6097) ### Problem Big instances can have huge authorized_keys files when using OpenSSH instead of the internal ssh server. Forgejo always re-generates the contents of that file when a user is deleted, even if he does not even have a public key uploaded. In case of codeberg.org, a 15MB file gets rewritten. If we batch delete 100 Spam users without ssh keys, we rewrite 1.5GB, this takes time and wears the SSD. In addition, there is a high chance of hitting a race contidion bug, when deleting users in parallel. ### Solution / Mitigation This patch prevents rewriting authorized_keys files, when not necessary. It greatly speeds up deleting malicious users, saves IO bandwidth and SSD wear. It also greatly reduces the chance of hitting a race condition bug. Fixing the race condition is not the scope of this patch though. ## 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... - [x] in their respective `*_test.go` for unit tests. - [ ] 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 - [x] I do not want this change to show in the release notes. - [ ] 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. Co-authored-by: Gusted <postmaster@gusted.xyz> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/6097 Reviewed-by: Gusted <gusted@noreply.codeberg.org> Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Co-authored-by: Andreas Shimokawa <shimokawa@fsfe.org> Co-committed-by: Andreas Shimokawa <shimokawa@fsfe.org> (cherry picked from commit 3c9b3ddf5c727d5d79bfb790454791551ffdd63a)
2024-12-05 16:32:09 -05:00
test(2, true)
test(4, false)
test(8, false)
test(11, false)
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3})
require.Error(t, DeleteUser(db.DefaultContext, org, false))
}
func TestCreateUser(t *testing.T) {
user := &user_model.User{
Name: "GiteaBot",
Email: "GiteaBot@gitea.io",
Passwd: ";p['////..-++']",
IsAdmin: false,
Theme: setting.UI.DefaultTheme,
MustChangePassword: false,
}
require.NoError(t, user_model.CreateUser(db.DefaultContext, user))
require.NoError(t, DeleteUser(db.DefaultContext, user, false))
}
func TestRenameUser(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 21})
t.Run("Non-Local", func(t *testing.T) {
u := &user_model.User{
Type: user_model.UserTypeIndividual,
LoginType: auth.OAuth2,
}
require.ErrorIs(t, RenameUser(db.DefaultContext, u, "user_rename"), user_model.ErrUserIsNotLocal{})
})
t.Run("Same username", func(t *testing.T) {
require.NoError(t, RenameUser(db.DefaultContext, user, user.Name))
})
t.Run("Non usable username", func(t *testing.T) {
usernames := []string{"--diff", "aa.png", ".well-known", "search", "aaa.atom"}
for _, username := range usernames {
t.Run(username, func(t *testing.T) {
require.Error(t, user_model.IsUsableUsername(username))
require.Error(t, RenameUser(db.DefaultContext, user, username))
})
}
})
t.Run("Only capitalization", func(t *testing.T) {
caps := strings.ToUpper(user.Name)
unittest.AssertNotExistsBean(t, &user_model.User{ID: user.ID, Name: caps})
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name})
require.NoError(t, RenameUser(db.DefaultContext, user, caps))
unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID, Name: caps})
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: caps})
})
t.Run("Already exists", func(t *testing.T) {
existUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
require.ErrorIs(t, RenameUser(db.DefaultContext, user, existUser.Name), user_model.ErrUserAlreadyExist{Name: existUser.Name})
require.ErrorIs(t, RenameUser(db.DefaultContext, user, existUser.LowerName), user_model.ErrUserAlreadyExist{Name: existUser.LowerName})
newUsername := fmt.Sprintf("uSEr%d", existUser.ID)
require.ErrorIs(t, RenameUser(db.DefaultContext, user, newUsername), user_model.ErrUserAlreadyExist{Name: newUsername})
})
t.Run("Normal", func(t *testing.T) {
oldUsername := user.Name
newUsername := "User_Rename"
require.NoError(t, RenameUser(db.DefaultContext, user, newUsername))
unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: user.ID, Name: newUsername, LowerName: strings.ToLower(newUsername)})
redirectUID, err := user_model.LookupUserRedirect(db.DefaultContext, oldUsername)
require.NoError(t, err)
assert.EqualValues(t, user.ID, redirectUID)
unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{OwnerID: user.ID, OwnerName: user.Name})
})
}
func TestCreateUser_Issue5882(t *testing.T) {
// Init settings
_ = setting.Admin
passwd := ".//.;1;;//.,-=_"
tt := []struct {
user *user_model.User
disableOrgCreation bool
}{
{&user_model.User{Name: "GiteaBot", Email: "GiteaBot@gitea.io", Passwd: passwd, MustChangePassword: false}, false},
{&user_model.User{Name: "GiteaBot2", Email: "GiteaBot2@gitea.io", Passwd: passwd, MustChangePassword: false}, true},
}
setting.Service.DefaultAllowCreateOrganization = true
for _, v := range tt {
setting.Admin.DisableRegularOrgCreation = v.disableOrgCreation
require.NoError(t, user_model.CreateUser(db.DefaultContext, v.user))
Add context cache as a request level cache (#22294) To avoid duplicated load of the same data in an HTTP request, we can set a context cache to do that. i.e. Some pages may load a user from a database with the same id in different areas on the same page. But the code is hidden in two different deep logic. How should we share the user? As a result of this PR, now if both entry functions accept `context.Context` as the first parameter and we just need to refactor `GetUserByID` to reuse the user from the context cache. Then it will not be loaded twice on an HTTP request. But of course, sometimes we would like to reload an object from the database, that's why `RemoveContextData` is also exposed. The core context cache is here. It defines a new context ```go type cacheContext struct { ctx context.Context data map[any]map[any]any lock sync.RWMutex } var cacheContextKey = struct{}{} func WithCacheContext(ctx context.Context) context.Context { return context.WithValue(ctx, cacheContextKey, &cacheContext{ ctx: ctx, data: make(map[any]map[any]any), }) } ``` Then you can use the below 4 methods to read/write/del the data within the same context. ```go func GetContextData(ctx context.Context, tp, key any) any func SetContextData(ctx context.Context, tp, key, value any) func RemoveContextData(ctx context.Context, tp, key any) func GetWithContextCache[T any](ctx context.Context, cacheGroupKey string, cacheTargetID any, f func() (T, error)) (T, error) ``` Then let's take a look at how `system.GetString` implement it. ```go func GetSetting(ctx context.Context, key string) (string, error) { return cache.GetWithContextCache(ctx, contextCacheKey, key, func() (string, error) { return cache.GetString(genSettingCacheKey(key), func() (string, error) { res, err := GetSettingNoCache(ctx, key) if err != nil { return "", err } return res.SettingValue, nil }) }) } ``` First, it will check if context data include the setting object with the key. If not, it will query from the global cache which may be memory or a Redis cache. If not, it will get the object from the database. In the end, if the object gets from the global cache or database, it will be set into the context cache. An object stored in the context cache will only be destroyed after the context disappeared.
2023-02-15 08:37:34 -05:00
u, err := user_model.GetUserByEmail(db.DefaultContext, v.user.Email)
require.NoError(t, err)
assert.Equal(t, !u.AllowCreateOrganization, v.disableOrgCreation)
require.NoError(t, DeleteUser(db.DefaultContext, v.user, false))
}
}
func TestDeleteInactiveUsers(t *testing.T) {
require.NoError(t, unittest.PrepareTestDatabase())
// Add an inactive user older than a minute, with an associated email_address record.
oldUser := &user_model.User{Name: "OldInactive", LowerName: "oldinactive", Email: "old@example.com", CreatedUnix: timeutil.TimeStampNow().Add(-120)}
_, err := db.GetEngine(db.DefaultContext).NoAutoTime().Insert(oldUser)
require.NoError(t, err)
oldEmail := &user_model.EmailAddress{UID: oldUser.ID, IsPrimary: true, Email: "old@example.com", LowerEmail: "old@example.com"}
err = db.Insert(db.DefaultContext, oldEmail)
require.NoError(t, err)
// Add an inactive user that's not older than a minute, with an associated email_address record.
newUser := &user_model.User{Name: "NewInactive", LowerName: "newinactive", Email: "new@example.com"}
err = db.Insert(db.DefaultContext, newUser)
require.NoError(t, err)
newEmail := &user_model.EmailAddress{UID: newUser.ID, IsPrimary: true, Email: "new@example.com", LowerEmail: "new@example.com"}
err = db.Insert(db.DefaultContext, newEmail)
require.NoError(t, err)
err = DeleteInactiveUsers(db.DefaultContext, time.Minute)
require.NoError(t, err)
// User older than a minute should be deleted along with their email address.
unittest.AssertExistsIf(t, false, oldUser)
unittest.AssertExistsIf(t, false, oldEmail)
// User not older than a minute shouldn't be deleted and their emaill address should still exist.
unittest.AssertExistsIf(t, true, newUser)
unittest.AssertExistsIf(t, true, newEmail)
}