loading up the forgejo repo on tangled to test page performance
1// Copyright 2017 The Gitea Authors. All rights reserved.
2// SPDX-License-Identifier: MIT
3
4package integration
5
6import (
7 "fmt"
8 "net/http"
9 "testing"
10 "time"
11
12 asymkey_model "forgejo.org/models/asymkey"
13 auth_model "forgejo.org/models/auth"
14 "forgejo.org/models/unittest"
15 user_model "forgejo.org/models/user"
16 "forgejo.org/modules/json"
17 "forgejo.org/modules/setting"
18 api "forgejo.org/modules/structs"
19 "forgejo.org/tests"
20
21 "github.com/gobwas/glob"
22 "github.com/stretchr/testify/assert"
23)
24
25func TestAPIAdminCreateAndDeleteSSHKey(t *testing.T) {
26 defer tests.PrepareTestEnv(t)()
27 // user1 is an admin user
28 session := loginUser(t, "user1")
29 keyOwner := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user2"})
30
31 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
32 urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", keyOwner.Name)
33 req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
34 "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
35 "title": "test-key",
36 }).AddTokenAuth(token)
37 resp := MakeRequest(t, req, http.StatusCreated)
38
39 var newPublicKey api.PublicKey
40 DecodeJSON(t, resp, &newPublicKey)
41 unittest.AssertExistsAndLoadBean(t, &asymkey_model.PublicKey{
42 ID: newPublicKey.ID,
43 Name: newPublicKey.Title,
44 Fingerprint: newPublicKey.Fingerprint,
45 OwnerID: keyOwner.ID,
46 })
47
48 req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", keyOwner.Name, newPublicKey.ID).
49 AddTokenAuth(token)
50 MakeRequest(t, req, http.StatusNoContent)
51 unittest.AssertNotExistsBean(t, &asymkey_model.PublicKey{ID: newPublicKey.ID})
52}
53
54func TestAPIAdminDeleteMissingSSHKey(t *testing.T) {
55 defer tests.PrepareTestEnv(t)()
56
57 // user1 is an admin user
58 token := getUserToken(t, "user1", auth_model.AccessTokenScopeWriteAdmin)
59 req := NewRequestf(t, "DELETE", "/api/v1/admin/users/user1/keys/%d", unittest.NonexistentID).
60 AddTokenAuth(token)
61 MakeRequest(t, req, http.StatusNotFound)
62}
63
64func TestAPIAdminDeleteUnauthorizedKey(t *testing.T) {
65 defer tests.PrepareTestEnv(t)()
66 adminUsername := "user1"
67 normalUsername := "user2"
68 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
69
70 urlStr := fmt.Sprintf("/api/v1/admin/users/%s/keys", adminUsername)
71 req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
72 "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment\n",
73 "title": "test-key",
74 }).AddTokenAuth(token)
75 resp := MakeRequest(t, req, http.StatusCreated)
76 var newPublicKey api.PublicKey
77 DecodeJSON(t, resp, &newPublicKey)
78
79 token = getUserToken(t, normalUsername)
80 req = NewRequestf(t, "DELETE", "/api/v1/admin/users/%s/keys/%d", adminUsername, newPublicKey.ID).
81 AddTokenAuth(token)
82 MakeRequest(t, req, http.StatusForbidden)
83}
84
85func TestAPISudoUser(t *testing.T) {
86 defer tests.PrepareTestEnv(t)()
87 adminUsername := "user1"
88 normalUsername := "user2"
89 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadUser)
90
91 req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user?sudo=%s", normalUsername)).
92 AddTokenAuth(token)
93 resp := MakeRequest(t, req, http.StatusOK)
94 var user api.User
95 DecodeJSON(t, resp, &user)
96
97 assert.Equal(t, normalUsername, user.UserName)
98}
99
100func TestAPISudoUserForbidden(t *testing.T) {
101 defer tests.PrepareTestEnv(t)()
102 adminUsername := "user1"
103 normalUsername := "user2"
104
105 token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadAdmin)
106 req := NewRequest(t, "GET", fmt.Sprintf("/api/v1/user?sudo=%s", adminUsername)).
107 AddTokenAuth(token)
108 MakeRequest(t, req, http.StatusForbidden)
109}
110
111func TestAPIListUsers(t *testing.T) {
112 defer tests.PrepareTestEnv(t)()
113 adminUsername := "user1"
114 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeReadAdmin)
115
116 req := NewRequest(t, "GET", "/api/v1/admin/users").
117 AddTokenAuth(token)
118 resp := MakeRequest(t, req, http.StatusOK)
119 var users []api.User
120 DecodeJSON(t, resp, &users)
121
122 found := false
123 for _, user := range users {
124 if user.UserName == adminUsername {
125 found = true
126 }
127 }
128 assert.True(t, found)
129 numberOfUsers := unittest.GetCount(t, &user_model.User{}, "type = 0")
130 assert.Len(t, users, numberOfUsers)
131}
132
133func TestAPIListUsersNotLoggedIn(t *testing.T) {
134 defer tests.PrepareTestEnv(t)()
135 req := NewRequest(t, "GET", "/api/v1/admin/users")
136 MakeRequest(t, req, http.StatusUnauthorized)
137}
138
139func TestAPIListUsersNonAdmin(t *testing.T) {
140 defer tests.PrepareTestEnv(t)()
141 nonAdminUsername := "user2"
142 token := getUserToken(t, nonAdminUsername)
143 req := NewRequest(t, "GET", "/api/v1/admin/users").
144 AddTokenAuth(token)
145 MakeRequest(t, req, http.StatusForbidden)
146}
147
148func TestAPICreateUserInvalidEmail(t *testing.T) {
149 defer tests.PrepareTestEnv(t)()
150 adminUsername := "user1"
151 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
152 req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{
153 "email": "invalid_email@domain.com\r\n",
154 "full_name": "invalid user",
155 "login_name": "invalidUser",
156 "must_change_password": "true",
157 "password": "password",
158 "send_notify": "true",
159 "source_id": "0",
160 "username": "invalidUser",
161 }).AddTokenAuth(token)
162 MakeRequest(t, req, http.StatusUnprocessableEntity)
163}
164
165func TestAPICreateAndDeleteUser(t *testing.T) {
166 defer tests.PrepareTestEnv(t)()
167 adminUsername := "user1"
168 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
169
170 req := NewRequestWithValues(
171 t,
172 "POST",
173 "/api/v1/admin/users",
174 map[string]string{
175 "email": "deleteme@domain.com",
176 "full_name": "delete me",
177 "login_name": "deleteme",
178 "must_change_password": "true",
179 "password": "password",
180 "send_notify": "true",
181 "source_id": "0",
182 "username": "deleteme",
183 },
184 ).AddTokenAuth(token)
185 MakeRequest(t, req, http.StatusCreated)
186
187 req = NewRequest(t, "DELETE", "/api/v1/admin/users/deleteme").
188 AddTokenAuth(token)
189 MakeRequest(t, req, http.StatusNoContent)
190}
191
192func TestAPIEditUser(t *testing.T) {
193 defer tests.PrepareTestEnv(t)()
194 adminUsername := "user1"
195 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
196 urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2")
197
198 fullNameToChange := "Full Name User 2"
199 req := NewRequestWithValues(t, "PATCH", urlStr, map[string]string{
200 "full_name": fullNameToChange,
201 }).AddTokenAuth(token)
202 MakeRequest(t, req, http.StatusOK)
203 user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"})
204 assert.Equal(t, fullNameToChange, user2.FullName)
205
206 empty := ""
207 req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
208 Email: &empty,
209 }).AddTokenAuth(token)
210 resp := MakeRequest(t, req, http.StatusBadRequest)
211
212 errMap := make(map[string]any)
213 json.Unmarshal(resp.Body.Bytes(), &errMap)
214 assert.Equal(t, "e-mail invalid [email: ]", errMap["message"].(string))
215
216 user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"})
217 assert.False(t, user2.IsRestricted)
218 bTrue := true
219 req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
220 Restricted: &bTrue,
221 }).AddTokenAuth(token)
222 MakeRequest(t, req, http.StatusOK)
223 user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"})
224 assert.True(t, user2.IsRestricted)
225}
226
227func TestAPIEditUserWithLoginName(t *testing.T) {
228 defer tests.PrepareTestEnv(t)()
229
230 adminUsername := "user1"
231 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
232 urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2")
233
234 loginName := "user2"
235 loginSource := int64(0)
236
237 t.Run("login_name only", func(t *testing.T) {
238 defer tests.PrintCurrentTest(t)()
239
240 req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
241 LoginName: &loginName,
242 }).AddTokenAuth(token)
243 MakeRequest(t, req, http.StatusUnprocessableEntity)
244 })
245
246 t.Run("source_id only", func(t *testing.T) {
247 defer tests.PrintCurrentTest(t)()
248
249 req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
250 SourceID: &loginSource,
251 }).AddTokenAuth(token)
252 MakeRequest(t, req, http.StatusUnprocessableEntity)
253 })
254
255 t.Run("login_name & source_id", func(t *testing.T) {
256 defer tests.PrintCurrentTest(t)()
257
258 req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
259 LoginName: &loginName,
260 SourceID: &loginSource,
261 }).AddTokenAuth(token)
262 MakeRequest(t, req, http.StatusOK)
263 })
264}
265
266func TestAPICreateRepoForUser(t *testing.T) {
267 defer tests.PrepareTestEnv(t)()
268 adminUsername := "user1"
269 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
270
271 req := NewRequestWithJSON(
272 t,
273 "POST",
274 fmt.Sprintf("/api/v1/admin/users/%s/repos", adminUsername),
275 &api.CreateRepoOption{
276 Name: "admincreatedrepo",
277 },
278 ).AddTokenAuth(token)
279 MakeRequest(t, req, http.StatusCreated)
280}
281
282func TestAPIRenameUser(t *testing.T) {
283 defer tests.PrepareTestEnv(t)()
284 adminUsername := "user1"
285 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
286 urlStr := fmt.Sprintf("/api/v1/admin/users/%s/rename", "user2")
287 req := NewRequestWithValues(t, "POST", urlStr, map[string]string{
288 // required
289 "new_name": "User2",
290 }).AddTokenAuth(token)
291 MakeRequest(t, req, http.StatusNoContent)
292
293 urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2")
294 req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
295 // required
296 "new_name": "User2-2-2",
297 }).AddTokenAuth(token)
298 MakeRequest(t, req, http.StatusNoContent)
299
300 req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
301 // required
302 "new_name": "user1",
303 }).AddTokenAuth(token)
304 // the old user name still be used by with a redirect
305 MakeRequest(t, req, http.StatusTemporaryRedirect)
306
307 urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2-2-2")
308 req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
309 // required
310 "new_name": "user1",
311 }).AddTokenAuth(token)
312 MakeRequest(t, req, http.StatusUnprocessableEntity)
313
314 req = NewRequestWithValues(t, "POST", urlStr, map[string]string{
315 // required
316 "new_name": "user2",
317 }).AddTokenAuth(token)
318 MakeRequest(t, req, http.StatusNoContent)
319}
320
321func TestAPICron(t *testing.T) {
322 defer tests.PrepareTestEnv(t)()
323
324 // user1 is an admin user
325 session := loginUser(t, "user1")
326
327 t.Run("List", func(t *testing.T) {
328 defer tests.PrintCurrentTest(t)()
329
330 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin)
331
332 req := NewRequest(t, "GET", "/api/v1/admin/cron").
333 AddTokenAuth(token)
334 resp := MakeRequest(t, req, http.StatusOK)
335
336 assert.Equal(t, "28", resp.Header().Get("X-Total-Count"))
337
338 var crons []api.Cron
339 DecodeJSON(t, resp, &crons)
340 assert.Len(t, crons, 28)
341 })
342
343 t.Run("Execute", func(t *testing.T) {
344 defer tests.PrintCurrentTest(t)()
345
346 now := time.Now()
347 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin)
348 // Archive cleanup is harmless, because in the test environment there are none
349 // and is thus an NOOP operation and therefore doesn't interfere with any other
350 // tests.
351 req := NewRequest(t, "POST", "/api/v1/admin/cron/archive_cleanup").
352 AddTokenAuth(token)
353 MakeRequest(t, req, http.StatusNoContent)
354
355 // Check for the latest run time for this cron, to ensure it has been run.
356 req = NewRequest(t, "GET", "/api/v1/admin/cron").
357 AddTokenAuth(token)
358 resp := MakeRequest(t, req, http.StatusOK)
359
360 var crons []api.Cron
361 DecodeJSON(t, resp, &crons)
362
363 for _, cron := range crons {
364 if cron.Name == "archive_cleanup" {
365 assert.True(t, now.Before(cron.Prev))
366 }
367 }
368 })
369}
370
371func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) {
372 defer tests.PrepareTestEnv(t)()
373
374 setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")}
375 defer func() {
376 setting.Service.EmailDomainAllowList = []glob.Glob{}
377 }()
378
379 adminUsername := "user1"
380 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
381
382 req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{
383 "email": "allowedUser1@example1.org",
384 "login_name": "allowedUser1",
385 "username": "allowedUser1",
386 "password": "allowedUser1_pass",
387 "must_change_password": "true",
388 }).AddTokenAuth(token)
389 resp := MakeRequest(t, req, http.StatusCreated)
390 assert.Equal(t, "the domain of user email allowedUser1@example1.org conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", resp.Header().Get("X-Gitea-Warning"))
391
392 req = NewRequest(t, "DELETE", "/api/v1/admin/users/allowedUser1").AddTokenAuth(token)
393 MakeRequest(t, req, http.StatusNoContent)
394}
395
396func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) {
397 defer tests.PrepareTestEnv(t)()
398
399 setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")}
400 defer func() {
401 setting.Service.EmailDomainAllowList = []glob.Glob{}
402 }()
403
404 adminUsername := "user1"
405 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin)
406 urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2")
407
408 newEmail := "user2@example1.com"
409 req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
410 Email: &newEmail,
411 }).AddTokenAuth(token)
412 resp := MakeRequest(t, req, http.StatusOK)
413 assert.Equal(t, "the domain of user email user2@example1.com conflicts with EMAIL_DOMAIN_ALLOWLIST or EMAIL_DOMAIN_BLOCKLIST", resp.Header().Get("X-Gitea-Warning"))
414
415 originalEmail := "user2@example.com"
416 req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{
417 Email: &originalEmail,
418 }).AddTokenAuth(token)
419 MakeRequest(t, req, http.StatusOK)
420}