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_token_test.go 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. // Copyright 2018 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "fmt"
  6. "net/http"
  7. "testing"
  8. auth_model "code.gitea.io/gitea/models/auth"
  9. "code.gitea.io/gitea/models/unittest"
  10. user_model "code.gitea.io/gitea/models/user"
  11. "code.gitea.io/gitea/modules/log"
  12. api "code.gitea.io/gitea/modules/structs"
  13. "code.gitea.io/gitea/tests"
  14. "github.com/stretchr/testify/assert"
  15. )
  16. // TestAPICreateAndDeleteToken tests that token that was just created can be deleted
  17. func TestAPICreateAndDeleteToken(t *testing.T) {
  18. defer tests.PrepareTestEnv(t)()
  19. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  20. newAccessToken := createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user, nil)
  21. deleteAPIAccessToken(t, newAccessToken, user)
  22. newAccessToken = createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user, nil)
  23. deleteAPIAccessToken(t, newAccessToken, user)
  24. }
  25. // TestAPIDeleteMissingToken ensures that error is thrown when token not found
  26. func TestAPIDeleteMissingToken(t *testing.T) {
  27. defer tests.PrepareTestEnv(t)()
  28. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  29. req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", unittest.NonexistentID)
  30. req = AddBasicAuthHeader(req, user.Name)
  31. MakeRequest(t, req, http.StatusNotFound)
  32. }
  33. // TestAPIGetTokensPermission ensures that only the admin can get tokens from other users
  34. func TestAPIGetTokensPermission(t *testing.T) {
  35. defer tests.PrepareTestEnv(t)()
  36. // admin can get tokens for other users
  37. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  38. req := NewRequestf(t, "GET", "/api/v1/users/user2/tokens")
  39. req = AddBasicAuthHeader(req, user.Name)
  40. MakeRequest(t, req, http.StatusOK)
  41. // non-admin can get tokens for himself
  42. user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  43. req = NewRequestf(t, "GET", "/api/v1/users/user2/tokens")
  44. req = AddBasicAuthHeader(req, user.Name)
  45. MakeRequest(t, req, http.StatusOK)
  46. // non-admin can't get tokens for other users
  47. user = unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
  48. req = NewRequestf(t, "GET", "/api/v1/users/user2/tokens")
  49. req = AddBasicAuthHeader(req, user.Name)
  50. MakeRequest(t, req, http.StatusForbidden)
  51. }
  52. // TestAPIDeleteTokensPermission ensures that only the admin can delete tokens from other users
  53. func TestAPIDeleteTokensPermission(t *testing.T) {
  54. defer tests.PrepareTestEnv(t)()
  55. admin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  56. user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
  57. user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
  58. // admin can delete tokens for other users
  59. createAPIAccessTokenWithoutCleanUp(t, "test-key-1", user2, nil)
  60. req := NewRequestf(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-1")
  61. req = AddBasicAuthHeader(req, admin.Name)
  62. MakeRequest(t, req, http.StatusNoContent)
  63. // non-admin can delete tokens for himself
  64. createAPIAccessTokenWithoutCleanUp(t, "test-key-2", user2, nil)
  65. req = NewRequestf(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-2")
  66. req = AddBasicAuthHeader(req, user2.Name)
  67. MakeRequest(t, req, http.StatusNoContent)
  68. // non-admin can't delete tokens for other users
  69. createAPIAccessTokenWithoutCleanUp(t, "test-key-3", user2, nil)
  70. req = NewRequestf(t, "DELETE", "/api/v1/users/"+user2.LoginName+"/tokens/test-key-3")
  71. req = AddBasicAuthHeader(req, user4.Name)
  72. MakeRequest(t, req, http.StatusForbidden)
  73. }
  74. type permission struct {
  75. category auth_model.AccessTokenScopeCategory
  76. level auth_model.AccessTokenScopeLevel
  77. }
  78. type requiredScopeTestCase struct {
  79. url string
  80. method string
  81. requiredPermissions []permission
  82. }
  83. func (c *requiredScopeTestCase) Name() string {
  84. return fmt.Sprintf("%v %v", c.method, c.url)
  85. }
  86. // TestAPIDeniesPermissionBasedOnTokenScope tests that API routes forbid access
  87. // when the correct token scope is not included.
  88. func TestAPIDeniesPermissionBasedOnTokenScope(t *testing.T) {
  89. defer tests.PrepareTestEnv(t)()
  90. // We'll assert that each endpoint, when fetched with a token with all
  91. // scopes *except* the ones specified, a forbidden status code is returned.
  92. //
  93. // This is to protect against endpoints having their access check copied
  94. // from other endpoints and not updated.
  95. //
  96. // Test cases are in alphabetical order by URL.
  97. //
  98. // Note: query parameters are not currently supported since the token is
  99. // appended with `?=token=<token>`.
  100. testCases := []requiredScopeTestCase{
  101. {
  102. "/api/v1/admin/emails",
  103. "GET",
  104. []permission{
  105. {
  106. auth_model.AccessTokenScopeCategoryAdmin,
  107. auth_model.Read,
  108. },
  109. },
  110. },
  111. {
  112. "/api/v1/admin/users",
  113. "GET",
  114. []permission{
  115. {
  116. auth_model.AccessTokenScopeCategoryAdmin,
  117. auth_model.Read,
  118. },
  119. },
  120. },
  121. {
  122. "/api/v1/admin/users",
  123. "POST",
  124. []permission{
  125. {
  126. auth_model.AccessTokenScopeCategoryAdmin,
  127. auth_model.Write,
  128. },
  129. },
  130. },
  131. {
  132. "/api/v1/admin/users/user2",
  133. "PATCH",
  134. []permission{
  135. {
  136. auth_model.AccessTokenScopeCategoryAdmin,
  137. auth_model.Write,
  138. },
  139. },
  140. },
  141. {
  142. "/api/v1/admin/users/user2/orgs",
  143. "GET",
  144. []permission{
  145. {
  146. auth_model.AccessTokenScopeCategoryAdmin,
  147. auth_model.Read,
  148. },
  149. },
  150. },
  151. {
  152. "/api/v1/admin/users/user2/orgs",
  153. "POST",
  154. []permission{
  155. {
  156. auth_model.AccessTokenScopeCategoryAdmin,
  157. auth_model.Write,
  158. },
  159. },
  160. },
  161. {
  162. "/api/v1/admin/orgs",
  163. "GET",
  164. []permission{
  165. {
  166. auth_model.AccessTokenScopeCategoryAdmin,
  167. auth_model.Read,
  168. },
  169. },
  170. },
  171. {
  172. "/api/v1/notifications",
  173. "GET",
  174. []permission{
  175. {
  176. auth_model.AccessTokenScopeCategoryNotification,
  177. auth_model.Read,
  178. },
  179. },
  180. },
  181. {
  182. "/api/v1/notifications",
  183. "PUT",
  184. []permission{
  185. {
  186. auth_model.AccessTokenScopeCategoryNotification,
  187. auth_model.Write,
  188. },
  189. },
  190. },
  191. {
  192. "/api/v1/org/org1/repos",
  193. "POST",
  194. []permission{
  195. {
  196. auth_model.AccessTokenScopeCategoryOrganization,
  197. auth_model.Write,
  198. },
  199. {
  200. auth_model.AccessTokenScopeCategoryRepository,
  201. auth_model.Write,
  202. },
  203. },
  204. },
  205. {
  206. "/api/v1/packages/user1/type/name/1",
  207. "GET",
  208. []permission{
  209. {
  210. auth_model.AccessTokenScopeCategoryPackage,
  211. auth_model.Read,
  212. },
  213. },
  214. },
  215. {
  216. "/api/v1/packages/user1/type/name/1",
  217. "DELETE",
  218. []permission{
  219. {
  220. auth_model.AccessTokenScopeCategoryPackage,
  221. auth_model.Write,
  222. },
  223. },
  224. },
  225. {
  226. "/api/v1/repos/user1/repo1",
  227. "GET",
  228. []permission{
  229. {
  230. auth_model.AccessTokenScopeCategoryRepository,
  231. auth_model.Read,
  232. },
  233. },
  234. },
  235. {
  236. "/api/v1/repos/user1/repo1",
  237. "PATCH",
  238. []permission{
  239. {
  240. auth_model.AccessTokenScopeCategoryRepository,
  241. auth_model.Write,
  242. },
  243. },
  244. },
  245. {
  246. "/api/v1/repos/user1/repo1",
  247. "DELETE",
  248. []permission{
  249. {
  250. auth_model.AccessTokenScopeCategoryRepository,
  251. auth_model.Write,
  252. },
  253. },
  254. },
  255. {
  256. "/api/v1/repos/user1/repo1/branches",
  257. "GET",
  258. []permission{
  259. {
  260. auth_model.AccessTokenScopeCategoryRepository,
  261. auth_model.Read,
  262. },
  263. },
  264. },
  265. {
  266. "/api/v1/repos/user1/repo1/archive/foo",
  267. "GET",
  268. []permission{
  269. {
  270. auth_model.AccessTokenScopeCategoryRepository,
  271. auth_model.Read,
  272. },
  273. },
  274. },
  275. {
  276. "/api/v1/repos/user1/repo1/issues",
  277. "GET",
  278. []permission{
  279. {
  280. auth_model.AccessTokenScopeCategoryIssue,
  281. auth_model.Read,
  282. },
  283. },
  284. },
  285. {
  286. "/api/v1/repos/user1/repo1/media/foo",
  287. "GET",
  288. []permission{
  289. {
  290. auth_model.AccessTokenScopeCategoryRepository,
  291. auth_model.Read,
  292. },
  293. },
  294. },
  295. {
  296. "/api/v1/repos/user1/repo1/raw/foo",
  297. "GET",
  298. []permission{
  299. {
  300. auth_model.AccessTokenScopeCategoryRepository,
  301. auth_model.Read,
  302. },
  303. },
  304. },
  305. {
  306. "/api/v1/repos/user1/repo1/teams",
  307. "GET",
  308. []permission{
  309. {
  310. auth_model.AccessTokenScopeCategoryRepository,
  311. auth_model.Read,
  312. },
  313. },
  314. },
  315. {
  316. "/api/v1/repos/user1/repo1/teams/team1",
  317. "PUT",
  318. []permission{
  319. {
  320. auth_model.AccessTokenScopeCategoryRepository,
  321. auth_model.Write,
  322. },
  323. },
  324. },
  325. {
  326. "/api/v1/repos/user1/repo1/transfer",
  327. "POST",
  328. []permission{
  329. {
  330. auth_model.AccessTokenScopeCategoryRepository,
  331. auth_model.Write,
  332. },
  333. },
  334. },
  335. // Private repo
  336. {
  337. "/api/v1/repos/user2/repo2",
  338. "GET",
  339. []permission{
  340. {
  341. auth_model.AccessTokenScopeCategoryRepository,
  342. auth_model.Read,
  343. },
  344. },
  345. },
  346. // Private repo
  347. {
  348. "/api/v1/repos/user2/repo2",
  349. "GET",
  350. []permission{
  351. {
  352. auth_model.AccessTokenScopeCategoryRepository,
  353. auth_model.Read,
  354. },
  355. },
  356. },
  357. {
  358. "/api/v1/user",
  359. "GET",
  360. []permission{
  361. {
  362. auth_model.AccessTokenScopeCategoryUser,
  363. auth_model.Read,
  364. },
  365. },
  366. },
  367. {
  368. "/api/v1/user/emails",
  369. "GET",
  370. []permission{
  371. {
  372. auth_model.AccessTokenScopeCategoryUser,
  373. auth_model.Read,
  374. },
  375. },
  376. },
  377. {
  378. "/api/v1/user/emails",
  379. "POST",
  380. []permission{
  381. {
  382. auth_model.AccessTokenScopeCategoryUser,
  383. auth_model.Write,
  384. },
  385. },
  386. },
  387. {
  388. "/api/v1/user/emails",
  389. "DELETE",
  390. []permission{
  391. {
  392. auth_model.AccessTokenScopeCategoryUser,
  393. auth_model.Write,
  394. },
  395. },
  396. },
  397. {
  398. "/api/v1/user/applications/oauth2",
  399. "GET",
  400. []permission{
  401. {
  402. auth_model.AccessTokenScopeCategoryUser,
  403. auth_model.Read,
  404. },
  405. },
  406. },
  407. {
  408. "/api/v1/user/applications/oauth2",
  409. "POST",
  410. []permission{
  411. {
  412. auth_model.AccessTokenScopeCategoryUser,
  413. auth_model.Write,
  414. },
  415. },
  416. },
  417. {
  418. "/api/v1/users/search",
  419. "GET",
  420. []permission{
  421. {
  422. auth_model.AccessTokenScopeCategoryUser,
  423. auth_model.Read,
  424. },
  425. },
  426. },
  427. // Private user
  428. {
  429. "/api/v1/users/user31",
  430. "GET",
  431. []permission{
  432. {
  433. auth_model.AccessTokenScopeCategoryUser,
  434. auth_model.Read,
  435. },
  436. },
  437. },
  438. // Private user
  439. {
  440. "/api/v1/users/user31/gpg_keys",
  441. "GET",
  442. []permission{
  443. {
  444. auth_model.AccessTokenScopeCategoryUser,
  445. auth_model.Read,
  446. },
  447. },
  448. },
  449. }
  450. // User needs to be admin so that we can verify that tokens without admin
  451. // scopes correctly deny access.
  452. user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
  453. assert.True(t, user.IsAdmin, "User needs to be admin")
  454. for _, testCase := range testCases {
  455. runTestCase(t, &testCase, user)
  456. }
  457. }
  458. // runTestCase Helper function to run a single test case.
  459. func runTestCase(t *testing.T, testCase *requiredScopeTestCase, user *user_model.User) {
  460. t.Run(testCase.Name(), func(t *testing.T) {
  461. defer tests.PrintCurrentTest(t)()
  462. // Create a token with all scopes NOT required by the endpoint.
  463. var unauthorizedScopes []auth_model.AccessTokenScope
  464. for _, category := range auth_model.AllAccessTokenScopeCategories {
  465. // For permissions, Write > Read > NoAccess. So we need to
  466. // find the minimum required, and only grant permission up to but
  467. // not including the minimum required.
  468. minRequiredLevel := auth_model.Write
  469. categoryIsRequired := false
  470. for _, requiredPermission := range testCase.requiredPermissions {
  471. if requiredPermission.category != category {
  472. continue
  473. }
  474. categoryIsRequired = true
  475. if requiredPermission.level < minRequiredLevel {
  476. minRequiredLevel = requiredPermission.level
  477. }
  478. }
  479. unauthorizedLevel := auth_model.Write
  480. if categoryIsRequired {
  481. if minRequiredLevel == auth_model.Read {
  482. unauthorizedLevel = auth_model.NoAccess
  483. } else if minRequiredLevel == auth_model.Write {
  484. unauthorizedLevel = auth_model.Read
  485. } else {
  486. assert.Failf(t, "Invalid test case", "Unknown access token scope level: %v", minRequiredLevel)
  487. return
  488. }
  489. }
  490. if unauthorizedLevel == auth_model.NoAccess {
  491. continue
  492. }
  493. cateogoryUnauthorizedScopes := auth_model.GetRequiredScopes(
  494. unauthorizedLevel,
  495. category)
  496. unauthorizedScopes = append(unauthorizedScopes, cateogoryUnauthorizedScopes...)
  497. }
  498. accessToken := createAPIAccessTokenWithoutCleanUp(t, "test-token", user, &unauthorizedScopes)
  499. defer deleteAPIAccessToken(t, accessToken, user)
  500. // Add API access token to the URL.
  501. url := fmt.Sprintf("%s?token=%s", testCase.url, accessToken.Token)
  502. // Request the endpoint. Verify that permission is denied.
  503. req := NewRequestf(t, testCase.method, url)
  504. MakeRequest(t, req, http.StatusForbidden)
  505. })
  506. }
  507. // createAPIAccessTokenWithoutCleanUp Create an API access token and assert that
  508. // creation succeeded. The caller is responsible for deleting the token.
  509. func createAPIAccessTokenWithoutCleanUp(t *testing.T, tokenName string, user *user_model.User, scopes *[]auth_model.AccessTokenScope) api.AccessToken {
  510. payload := map[string]any{
  511. "name": tokenName,
  512. }
  513. if scopes != nil {
  514. for _, scope := range *scopes {
  515. scopes, scopesExists := payload["scopes"].([]string)
  516. if !scopesExists {
  517. scopes = make([]string, 0)
  518. }
  519. scopes = append(scopes, string(scope))
  520. payload["scopes"] = scopes
  521. }
  522. }
  523. log.Debug("Requesting creation of token with scopes: %v", scopes)
  524. req := NewRequestWithJSON(t, "POST", "/api/v1/users/"+user.LoginName+"/tokens", payload)
  525. req = AddBasicAuthHeader(req, user.Name)
  526. resp := MakeRequest(t, req, http.StatusCreated)
  527. var newAccessToken api.AccessToken
  528. DecodeJSON(t, resp, &newAccessToken)
  529. unittest.AssertExistsAndLoadBean(t, &auth_model.AccessToken{
  530. ID: newAccessToken.ID,
  531. Name: newAccessToken.Name,
  532. Token: newAccessToken.Token,
  533. UID: user.ID,
  534. })
  535. return newAccessToken
  536. }
  537. // createAPIAccessTokenWithoutCleanUp Delete an API access token and assert that
  538. // deletion succeeded.
  539. func deleteAPIAccessToken(t *testing.T, accessToken api.AccessToken, user *user_model.User) {
  540. req := NewRequestf(t, "DELETE", "/api/v1/users/"+user.LoginName+"/tokens/%d", accessToken.ID)
  541. req = AddBasicAuthHeader(req, user.Name)
  542. MakeRequest(t, req, http.StatusNoContent)
  543. unittest.AssertNotExistsBean(t, &auth_model.AccessToken{ID: accessToken.ID})
  544. }