forgejo/tests/integration/admin_user_test.go
Gusted a4642af51a
Some checks are pending
/ release (push) Waiting to run
testing-integration / test-unit (push) Waiting to run
testing-integration / test-sqlite (push) Waiting to run
testing-integration / test-mariadb (v10.6) (push) Waiting to run
testing-integration / test-mariadb (v11.8) (push) Waiting to run
testing / backend-checks (push) Waiting to run
testing / frontend-checks (push) Waiting to run
testing / test-unit (push) Blocked by required conditions
testing / test-e2e (push) Blocked by required conditions
testing / test-remote-cacher (redis) (push) Blocked by required conditions
testing / test-remote-cacher (valkey) (push) Blocked by required conditions
testing / test-remote-cacher (garnet) (push) Blocked by required conditions
testing / test-remote-cacher (redict) (push) Blocked by required conditions
testing / test-mysql (push) Blocked by required conditions
testing / test-pgsql (push) Blocked by required conditions
testing / test-sqlite (push) Blocked by required conditions
testing / security-check (push) Blocked by required conditions
feat: replace cross origin protection (#9830)
Replace the anti-CSRF token with a [cross origin protection by Go](https://go.dev/doc/go1.25#nethttppkgnethttp) that uses a stateless way of verifying if a request was cross origin or not. This allows is to remove al lot of code and replace it with a few lines of code and we no longer have to hand roll this protection. The new protection uses indicators by the browser itself that indicate if the request is cross-origin, thus we no longer have to take care of ensuring the generated CSRF token is passed back to the server any request by the the browser will have send this indicator.

Resolves forgejo/forgejo#3538

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/9830
Reviewed-by: oliverpool <oliverpool@noreply.codeberg.org>
Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
Co-authored-by: Gusted <postmaster@gusted.xyz>
Co-committed-by: Gusted <postmaster@gusted.xyz>
2025-10-29 22:43:22 +01:00

269 lines
8.7 KiB
Go

// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"strconv"
"testing"
"time"
auth_model "forgejo.org/models/auth"
"forgejo.org/models/db"
issues_model "forgejo.org/models/issues"
"forgejo.org/models/unittest"
user_model "forgejo.org/models/user"
api "forgejo.org/modules/structs"
"forgejo.org/modules/timeutil"
"forgejo.org/tests"
"github.com/stretchr/testify/assert"
)
func TestAdminViewUsers(t *testing.T) {
defer tests.PrepareTestEnv(t)()
t.Run("Admin user", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user1")
req := NewRequest(t, "GET", "/admin/users")
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/admin/users?status_filter[is_2fa_enabled]=1")
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
// 6th column is the 2FA column.
// One user that has TOTP and another user that has WebAuthn.
assert.Equal(t, 2, htmlDoc.Find(".admin-setting-content table tbody tr td:nth-child(6) .octicon-check").Length())
// account type 5 is for remote users (eg. users from the federation)
req = NewRequest(t, "GET", "/admin/users?status_filter[account_type]=5")
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
// Only one user (id 42) is a remote user
assert.Equal(t, 1, htmlDoc.Find("table tbody tr").Length())
})
t.Run("Normal user", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
session := loginUser(t, "user2")
req := NewRequest(t, "GET", "/admin/users")
session.MakeRequest(t, req, http.StatusForbidden)
})
t.Run("Anonymous user", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()
req := NewRequest(t, "GET", "/admin/users")
MakeRequest(t, req, http.StatusSeeOther)
})
}
func TestAdminViewUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
req := NewRequest(t, "GET", "/admin/users/1")
session.MakeRequest(t, req, http.StatusOK)
session = loginUser(t, "user2")
req = NewRequest(t, "GET", "/admin/users/1")
session.MakeRequest(t, req, http.StatusForbidden)
}
func TestAdminEditUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
testSuccessfullEdit(t, user_model.User{ID: 2, Name: "newusername", LoginName: "otherlogin", Email: "new@e-mail.gitea"})
}
func TestAdminEditUserHideEmail(t *testing.T) {
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
userID := int64(2) // user2 from fixtures
// Test setting hide_email to false
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/admin/users/%d/edit", userID), map[string]string{
"user_name": "user2",
"login_name": "user2",
"login_type": "0-0",
"email": "user2@example.com",
"hide_email": "false",
})
session.MakeRequest(t, req, http.StatusSeeOther)
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
assert.False(t, user.KeepEmailPrivate)
// Verify the form now loads with hide_email not checked
req = NewRequest(t, "GET", fmt.Sprintf("/admin/users/%d/edit", userID))
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
htmlDoc.AssertElement(t, `input[name="hide_email"]:not([checked])`, true)
// Test setting hide_email to true
req = NewRequestWithValues(t, "POST", fmt.Sprintf("/admin/users/%d/edit", userID), map[string]string{
"user_name": "user2",
"login_name": "user2",
"login_type": "0-0",
"email": "user2@example.com",
"hide_email": "true",
})
session.MakeRequest(t, req, http.StatusSeeOther)
user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userID})
assert.True(t, user.KeepEmailPrivate)
// Verify the form loads with hide_email checked
req = NewRequest(t, "GET", fmt.Sprintf("/admin/users/%d/edit", userID))
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
htmlDoc.AssertElement(t, `input[name="hide_email"][checked]`, true)
}
func testSuccessfullEdit(t *testing.T, formData user_model.User) {
makeRequest(t, formData, http.StatusSeeOther)
}
func makeRequest(t *testing.T, formData user_model.User, headerCode int) {
session := loginUser(t, "user1")
req := NewRequestWithValues(t, "POST", "/admin/users/"+strconv.Itoa(int(formData.ID))+"/edit", map[string]string{
"user_name": formData.Name,
"login_name": formData.LoginName,
"login_type": "0-0",
"email": formData.Email,
})
session.MakeRequest(t, req, headerCode)
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: formData.ID})
assert.Equal(t, formData.Name, user.Name)
assert.Equal(t, formData.LoginName, user.LoginName)
assert.Equal(t, formData.Email, user.Email)
}
func TestAdminDeleteUser(t *testing.T) {
defer unittest.OverrideFixtures("tests/integration/fixtures/TestAdminDeleteUser")()
defer tests.PrepareTestEnv(t)()
session := loginUser(t, "user1")
userID := int64(1000)
unittest.AssertExistsAndLoadBean(t, &issues_model.Issue{PosterID: userID})
req := NewRequestWithValues(t, "POST", fmt.Sprintf("/admin/users/%d/delete", userID), map[string]string{
"purge": "true",
})
session.MakeRequest(t, req, http.StatusSeeOther)
assertUserDeleted(t, userID, true)
unittest.CheckConsistencyFor(t, &user_model.User{})
}
func TestSourceId(t *testing.T) {
defer tests.PrepareTestEnv(t)()
testUser23 := &user_model.User{
Name: "ausersourceid23",
LoginName: "ausersourceid23",
Email: "ausersourceid23@example.com",
Passwd: "ausersourceid23password",
Type: user_model.UserTypeIndividual,
LoginType: auth_model.Plain,
LoginSource: 23,
}
defer createUser(t.Context(), t, testUser23)()
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin)
// Our new user start with 'a' so it should be the first one
req := NewRequest(t, "GET", "/api/v1/admin/users?limit=1").AddTokenAuth(token)
resp := session.MakeRequest(t, req, http.StatusOK)
var users []api.User
DecodeJSON(t, resp, &users)
assert.Len(t, users, 1)
assert.Equal(t, "ausersourceid23", users[0].UserName)
// Now our new user should not be in the list, because we filter by source_id 0
req = NewRequest(t, "GET", "/api/v1/admin/users?limit=1&source_id=0").AddTokenAuth(token)
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &users)
assert.Len(t, users, 1)
assert.Equal(t, "federated-example.net", users[0].UserName)
// Now our new user should be in the list, because we filter by source_id 23
req = NewRequest(t, "GET", "/api/v1/admin/users?limit=1&source_id=23").AddTokenAuth(token)
resp = session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &users)
assert.Len(t, users, 1)
assert.Equal(t, "ausersourceid23", users[0].UserName)
}
func TestAdminViewUsersSorted(t *testing.T) {
defer tests.PrepareTestEnv(t)()
createTimestamp := time.Now().Unix() - 1000
updateTimestamp := time.Now().Unix() - 500
sess := db.GetEngine(t.Context())
// Create 10 users with login source 44
for i := int64(1); i <= 10; i++ {
name := "sorttest" + strconv.Itoa(int(i))
user := &user_model.User{
Name: name,
LowerName: name,
LoginName: name,
Email: name + "@example.com",
Passwd: name + ".password",
Type: user_model.UserTypeIndividual,
LoginType: auth_model.OAuth2,
LoginSource: 44,
CreatedUnix: timeutil.TimeStamp(createTimestamp - i),
UpdatedUnix: timeutil.TimeStamp(updateTimestamp - i),
}
if _, err := sess.NoAutoTime().Insert(user); err != nil {
t.Fatalf("Failed to create user: %v", err)
}
}
session := loginUser(t, "user1")
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin)
testCases := []struct {
loginSource int64
sortType string
expectedUsers []string
}{
{0, "alphabetically", []string{"federated-example.net", "the_34-user.with.all.allowedChars", "user1", "user10"}},
{0, "reversealphabetically", []string{"user9", "user8", "user5", "user40"}},
{0, "newest", []string{"federated-example.net", "user40", "user39", "user38"}},
{0, "oldest", []string{"user1", "user2", "user4", "user5"}},
{44, "recentupdate", []string{"sorttest1", "sorttest2", "sorttest3", "sorttest4"}},
{44, "leastupdate", []string{"sorttest10", "sorttest9", "sorttest8", "sorttest7"}},
}
for _, testCase := range testCases {
req := NewRequest(
t,
"GET",
fmt.Sprintf("/api/v1/admin/users?sort=%s&limit=4&source_id=%d",
testCase.sortType,
testCase.loginSource),
).AddTokenAuth(token)
resp := session.MakeRequest(t, req, http.StatusOK)
var users []api.User
DecodeJSON(t, resp, &users)
assert.Len(t, users, 4)
for i, user := range users {
assert.Equalf(t, testCase.expectedUsers[i], user.UserName, "Sort type: %s, index %d", testCase.sortType, i)
}
}
}