mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-04-22 17:36:57 -04:00
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
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>
269 lines
8.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|