You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

api_admin_test.go 14KB

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