code.gitea.io/gitea@v1.22.3/tests/integration/api_admin_test.go (about) 1 // Copyright 2017 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package integration 5 6 import ( 7 "fmt" 8 "net/http" 9 "testing" 10 "time" 11 12 asymkey_model "code.gitea.io/gitea/models/asymkey" 13 auth_model "code.gitea.io/gitea/models/auth" 14 "code.gitea.io/gitea/models/unittest" 15 user_model "code.gitea.io/gitea/models/user" 16 "code.gitea.io/gitea/modules/json" 17 "code.gitea.io/gitea/modules/setting" 18 api "code.gitea.io/gitea/modules/structs" 19 "code.gitea.io/gitea/tests" 20 21 "github.com/gobwas/glob" 22 "github.com/stretchr/testify/assert" 23 ) 24 25 func 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 54 func 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 64 func 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 85 func 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 100 func 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 111 func 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 133 func 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 139 func 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 148 func 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 165 func 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 192 func 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 // required 201 "login_name": "user2", 202 "source_id": "0", 203 // to change 204 "full_name": fullNameToChange, 205 }).AddTokenAuth(token) 206 MakeRequest(t, req, http.StatusOK) 207 user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"}) 208 assert.Equal(t, fullNameToChange, user2.FullName) 209 210 empty := "" 211 req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ 212 LoginName: "user2", 213 SourceID: 0, 214 Email: &empty, 215 }).AddTokenAuth(token) 216 resp := MakeRequest(t, req, http.StatusBadRequest) 217 218 errMap := make(map[string]any) 219 json.Unmarshal(resp.Body.Bytes(), &errMap) 220 assert.EqualValues(t, "e-mail invalid [email: ]", errMap["message"].(string)) 221 222 user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"}) 223 assert.False(t, user2.IsRestricted) 224 bTrue := true 225 req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ 226 // required 227 LoginName: "user2", 228 SourceID: 0, 229 // to change 230 Restricted: &bTrue, 231 }).AddTokenAuth(token) 232 MakeRequest(t, req, http.StatusOK) 233 user2 = unittest.AssertExistsAndLoadBean(t, &user_model.User{LoginName: "user2"}) 234 assert.True(t, user2.IsRestricted) 235 } 236 237 func TestAPICreateRepoForUser(t *testing.T) { 238 defer tests.PrepareTestEnv(t)() 239 adminUsername := "user1" 240 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) 241 242 req := NewRequestWithJSON( 243 t, 244 "POST", 245 fmt.Sprintf("/api/v1/admin/users/%s/repos", adminUsername), 246 &api.CreateRepoOption{ 247 Name: "admincreatedrepo", 248 }, 249 ).AddTokenAuth(token) 250 MakeRequest(t, req, http.StatusCreated) 251 } 252 253 func TestAPIRenameUser(t *testing.T) { 254 defer tests.PrepareTestEnv(t)() 255 adminUsername := "user1" 256 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) 257 urlStr := fmt.Sprintf("/api/v1/admin/users/%s/rename", "user2") 258 req := NewRequestWithValues(t, "POST", urlStr, map[string]string{ 259 // required 260 "new_name": "User2", 261 }).AddTokenAuth(token) 262 MakeRequest(t, req, http.StatusNoContent) 263 264 urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2") 265 req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ 266 // required 267 "new_name": "User2-2-2", 268 }).AddTokenAuth(token) 269 MakeRequest(t, req, http.StatusNoContent) 270 271 req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ 272 // required 273 "new_name": "user1", 274 }).AddTokenAuth(token) 275 // the old user name still be used by with a redirect 276 MakeRequest(t, req, http.StatusTemporaryRedirect) 277 278 urlStr = fmt.Sprintf("/api/v1/admin/users/%s/rename", "User2-2-2") 279 req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ 280 // required 281 "new_name": "user1", 282 }).AddTokenAuth(token) 283 MakeRequest(t, req, http.StatusUnprocessableEntity) 284 285 req = NewRequestWithValues(t, "POST", urlStr, map[string]string{ 286 // required 287 "new_name": "user2", 288 }).AddTokenAuth(token) 289 MakeRequest(t, req, http.StatusNoContent) 290 } 291 292 func TestAPICron(t *testing.T) { 293 defer tests.PrepareTestEnv(t)() 294 295 // user1 is an admin user 296 session := loginUser(t, "user1") 297 298 t.Run("List", func(t *testing.T) { 299 defer tests.PrintCurrentTest(t)() 300 301 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadAdmin) 302 303 req := NewRequest(t, "GET", "/api/v1/admin/cron"). 304 AddTokenAuth(token) 305 resp := MakeRequest(t, req, http.StatusOK) 306 307 assert.Equal(t, "28", resp.Header().Get("X-Total-Count")) 308 309 var crons []api.Cron 310 DecodeJSON(t, resp, &crons) 311 assert.Len(t, crons, 28) 312 }) 313 314 t.Run("Execute", func(t *testing.T) { 315 defer tests.PrintCurrentTest(t)() 316 317 now := time.Now() 318 token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteAdmin) 319 // Archive cleanup is harmless, because in the test environment there are none 320 // and is thus an NOOP operation and therefore doesn't interfere with any other 321 // tests. 322 req := NewRequest(t, "POST", "/api/v1/admin/cron/archive_cleanup"). 323 AddTokenAuth(token) 324 MakeRequest(t, req, http.StatusNoContent) 325 326 // Check for the latest run time for this cron, to ensure it has been run. 327 req = NewRequest(t, "GET", "/api/v1/admin/cron"). 328 AddTokenAuth(token) 329 resp := MakeRequest(t, req, http.StatusOK) 330 331 var crons []api.Cron 332 DecodeJSON(t, resp, &crons) 333 334 for _, cron := range crons { 335 if cron.Name == "archive_cleanup" { 336 assert.True(t, now.Before(cron.Prev)) 337 } 338 } 339 }) 340 } 341 342 func TestAPICreateUser_NotAllowedEmailDomain(t *testing.T) { 343 defer tests.PrepareTestEnv(t)() 344 345 setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")} 346 defer func() { 347 setting.Service.EmailDomainAllowList = []glob.Glob{} 348 }() 349 350 adminUsername := "user1" 351 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) 352 353 req := NewRequestWithValues(t, "POST", "/api/v1/admin/users", map[string]string{ 354 "email": "allowedUser1@example1.org", 355 "login_name": "allowedUser1", 356 "username": "allowedUser1", 357 "password": "allowedUser1_pass", 358 "must_change_password": "true", 359 }).AddTokenAuth(token) 360 resp := MakeRequest(t, req, http.StatusCreated) 361 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")) 362 363 req = NewRequest(t, "DELETE", "/api/v1/admin/users/allowedUser1").AddTokenAuth(token) 364 MakeRequest(t, req, http.StatusNoContent) 365 } 366 367 func TestAPIEditUser_NotAllowedEmailDomain(t *testing.T) { 368 defer tests.PrepareTestEnv(t)() 369 370 setting.Service.EmailDomainAllowList = []glob.Glob{glob.MustCompile("example.org")} 371 defer func() { 372 setting.Service.EmailDomainAllowList = []glob.Glob{} 373 }() 374 375 adminUsername := "user1" 376 token := getUserToken(t, adminUsername, auth_model.AccessTokenScopeWriteAdmin) 377 urlStr := fmt.Sprintf("/api/v1/admin/users/%s", "user2") 378 379 newEmail := "user2@example1.com" 380 req := NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ 381 LoginName: "user2", 382 SourceID: 0, 383 Email: &newEmail, 384 }).AddTokenAuth(token) 385 resp := MakeRequest(t, req, http.StatusOK) 386 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")) 387 388 originalEmail := "user2@example.com" 389 req = NewRequestWithJSON(t, "PATCH", urlStr, api.EditUserOption{ 390 LoginName: "user2", 391 SourceID: 0, 392 Email: &originalEmail, 393 }).AddTokenAuth(token) 394 MakeRequest(t, req, http.StatusOK) 395 }