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.

oauth_test.go 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package integration
  4. import (
  5. "bytes"
  6. "io"
  7. "net/http"
  8. "testing"
  9. "code.gitea.io/gitea/modules/json"
  10. "code.gitea.io/gitea/modules/setting"
  11. "code.gitea.io/gitea/routers/web/auth"
  12. "code.gitea.io/gitea/tests"
  13. "github.com/stretchr/testify/assert"
  14. )
  15. func TestAuthorizeNoClientID(t *testing.T) {
  16. defer tests.PrepareTestEnv(t)()
  17. req := NewRequest(t, "GET", "/login/oauth/authorize")
  18. ctx := loginUser(t, "user2")
  19. resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
  20. assert.Contains(t, resp.Body.String(), "Client ID not registered")
  21. }
  22. func TestAuthorizeUnregisteredRedirect(t *testing.T) {
  23. defer tests.PrepareTestEnv(t)()
  24. req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=UNREGISTERED&response_type=code&state=thestate")
  25. ctx := loginUser(t, "user1")
  26. resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
  27. assert.Contains(t, resp.Body.String(), "Unregistered Redirect URI")
  28. }
  29. func TestAuthorizeUnsupportedResponseType(t *testing.T) {
  30. defer tests.PrepareTestEnv(t)()
  31. req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=UNEXPECTED&state=thestate")
  32. ctx := loginUser(t, "user1")
  33. resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
  34. u, err := resp.Result().Location()
  35. assert.NoError(t, err)
  36. assert.Equal(t, "unsupported_response_type", u.Query().Get("error"))
  37. assert.Equal(t, "Only code response type is supported.", u.Query().Get("error_description"))
  38. }
  39. func TestAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
  40. defer tests.PrepareTestEnv(t)()
  41. req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate&code_challenge_method=UNEXPECTED")
  42. ctx := loginUser(t, "user1")
  43. resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
  44. u, err := resp.Result().Location()
  45. assert.NoError(t, err)
  46. assert.Equal(t, "invalid_request", u.Query().Get("error"))
  47. assert.Equal(t, "unsupported code challenge method", u.Query().Get("error_description"))
  48. }
  49. func TestAuthorizeLoginRedirect(t *testing.T) {
  50. defer tests.PrepareTestEnv(t)()
  51. req := NewRequest(t, "GET", "/login/oauth/authorize")
  52. assert.Contains(t, MakeRequest(t, req, http.StatusSeeOther).Body.String(), "/user/login")
  53. }
  54. func TestAuthorizeShow(t *testing.T) {
  55. defer tests.PrepareTestEnv(t)()
  56. req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate")
  57. ctx := loginUser(t, "user4")
  58. resp := ctx.MakeRequest(t, req, http.StatusOK)
  59. htmlDoc := NewHTMLParser(t, resp.Body)
  60. htmlDoc.AssertElement(t, "#authorize-app", true)
  61. htmlDoc.GetCSRF()
  62. }
  63. func TestAuthorizeRedirectWithExistingGrant(t *testing.T) {
  64. defer tests.PrepareTestEnv(t)()
  65. req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=https%3A%2F%2Fexample.com%2Fxyzzy&response_type=code&state=thestate")
  66. ctx := loginUser(t, "user1")
  67. resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
  68. u, err := resp.Result().Location()
  69. assert.NoError(t, err)
  70. assert.Equal(t, "thestate", u.Query().Get("state"))
  71. assert.Truef(t, len(u.Query().Get("code")) > 30, "authorization code '%s' should be longer then 30", u.Query().Get("code"))
  72. u.RawQuery = ""
  73. assert.Equal(t, "https://example.com/xyzzy", u.String())
  74. }
  75. func TestAuthorizePKCERequiredForPublicClient(t *testing.T) {
  76. defer tests.PrepareTestEnv(t)()
  77. req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=ce5a1322-42a7-11ed-b878-0242ac120002&redirect_uri=http%3A%2F%2F127.0.0.1&response_type=code&state=thestate")
  78. ctx := loginUser(t, "user1")
  79. resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
  80. u, err := resp.Result().Location()
  81. assert.NoError(t, err)
  82. assert.Equal(t, "invalid_request", u.Query().Get("error"))
  83. assert.Equal(t, "PKCE is required for public clients", u.Query().Get("error_description"))
  84. }
  85. func TestAccessTokenExchange(t *testing.T) {
  86. defer tests.PrepareTestEnv(t)()
  87. req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  88. "grant_type": "authorization_code",
  89. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  90. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  91. "redirect_uri": "a",
  92. "code": "authcode",
  93. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  94. })
  95. resp := MakeRequest(t, req, http.StatusOK)
  96. type response struct {
  97. AccessToken string `json:"access_token"`
  98. TokenType string `json:"token_type"`
  99. ExpiresIn int64 `json:"expires_in"`
  100. RefreshToken string `json:"refresh_token"`
  101. }
  102. parsed := new(response)
  103. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
  104. assert.True(t, len(parsed.AccessToken) > 10)
  105. assert.True(t, len(parsed.RefreshToken) > 10)
  106. }
  107. func TestAccessTokenExchangeJSON(t *testing.T) {
  108. defer tests.PrepareTestEnv(t)()
  109. req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
  110. "grant_type": "authorization_code",
  111. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  112. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  113. "redirect_uri": "a",
  114. "code": "authcode",
  115. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  116. })
  117. resp := MakeRequest(t, req, http.StatusOK)
  118. type response struct {
  119. AccessToken string `json:"access_token"`
  120. TokenType string `json:"token_type"`
  121. ExpiresIn int64 `json:"expires_in"`
  122. RefreshToken string `json:"refresh_token"`
  123. }
  124. parsed := new(response)
  125. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
  126. assert.True(t, len(parsed.AccessToken) > 10)
  127. assert.True(t, len(parsed.RefreshToken) > 10)
  128. }
  129. func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
  130. defer tests.PrepareTestEnv(t)()
  131. req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  132. "grant_type": "authorization_code",
  133. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  134. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  135. "redirect_uri": "a",
  136. "code": "authcode",
  137. })
  138. resp := MakeRequest(t, req, http.StatusBadRequest)
  139. parsedError := new(auth.AccessTokenError)
  140. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  141. assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
  142. assert.Equal(t, "failed PKCE code challenge", parsedError.ErrorDescription)
  143. }
  144. func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
  145. defer tests.PrepareTestEnv(t)()
  146. // invalid client id
  147. req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  148. "grant_type": "authorization_code",
  149. "client_id": "???",
  150. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  151. "redirect_uri": "a",
  152. "code": "authcode",
  153. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  154. })
  155. resp := MakeRequest(t, req, http.StatusBadRequest)
  156. parsedError := new(auth.AccessTokenError)
  157. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  158. assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
  159. assert.Equal(t, "cannot load client with client id: '???'", parsedError.ErrorDescription)
  160. // invalid client secret
  161. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  162. "grant_type": "authorization_code",
  163. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  164. "client_secret": "???",
  165. "redirect_uri": "a",
  166. "code": "authcode",
  167. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  168. })
  169. resp = MakeRequest(t, req, http.StatusBadRequest)
  170. parsedError = new(auth.AccessTokenError)
  171. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  172. assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
  173. assert.Equal(t, "invalid client secret", parsedError.ErrorDescription)
  174. // invalid redirect uri
  175. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  176. "grant_type": "authorization_code",
  177. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  178. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  179. "redirect_uri": "???",
  180. "code": "authcode",
  181. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  182. })
  183. resp = MakeRequest(t, req, http.StatusBadRequest)
  184. parsedError = new(auth.AccessTokenError)
  185. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  186. assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
  187. assert.Equal(t, "unexpected redirect URI", parsedError.ErrorDescription)
  188. // invalid authorization code
  189. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  190. "grant_type": "authorization_code",
  191. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  192. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  193. "redirect_uri": "a",
  194. "code": "???",
  195. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  196. })
  197. resp = MakeRequest(t, req, http.StatusBadRequest)
  198. parsedError = new(auth.AccessTokenError)
  199. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  200. assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
  201. assert.Equal(t, "client is not authorized", parsedError.ErrorDescription)
  202. // invalid grant_type
  203. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  204. "grant_type": "???",
  205. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  206. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  207. "redirect_uri": "a",
  208. "code": "authcode",
  209. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  210. })
  211. resp = MakeRequest(t, req, http.StatusBadRequest)
  212. parsedError = new(auth.AccessTokenError)
  213. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  214. assert.Equal(t, "unsupported_grant_type", string(parsedError.ErrorCode))
  215. assert.Equal(t, "Only refresh_token or authorization_code grant type is supported", parsedError.ErrorDescription)
  216. }
  217. func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
  218. defer tests.PrepareTestEnv(t)()
  219. req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  220. "grant_type": "authorization_code",
  221. "redirect_uri": "a",
  222. "code": "authcode",
  223. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  224. })
  225. req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
  226. resp := MakeRequest(t, req, http.StatusOK)
  227. type response struct {
  228. AccessToken string `json:"access_token"`
  229. TokenType string `json:"token_type"`
  230. ExpiresIn int64 `json:"expires_in"`
  231. RefreshToken string `json:"refresh_token"`
  232. }
  233. parsed := new(response)
  234. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
  235. assert.True(t, len(parsed.AccessToken) > 10)
  236. assert.True(t, len(parsed.RefreshToken) > 10)
  237. // use wrong client_secret
  238. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  239. "grant_type": "authorization_code",
  240. "redirect_uri": "a",
  241. "code": "authcode",
  242. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  243. })
  244. req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==")
  245. resp = MakeRequest(t, req, http.StatusBadRequest)
  246. parsedError := new(auth.AccessTokenError)
  247. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  248. assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
  249. assert.Equal(t, "invalid client secret", parsedError.ErrorDescription)
  250. // missing header
  251. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  252. "grant_type": "authorization_code",
  253. "redirect_uri": "a",
  254. "code": "authcode",
  255. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  256. })
  257. resp = MakeRequest(t, req, http.StatusBadRequest)
  258. parsedError = new(auth.AccessTokenError)
  259. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  260. assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
  261. assert.Equal(t, "cannot load client with client id: ''", parsedError.ErrorDescription)
  262. // client_id inconsistent with Authorization header
  263. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  264. "grant_type": "authorization_code",
  265. "redirect_uri": "a",
  266. "code": "authcode",
  267. "client_id": "inconsistent",
  268. })
  269. req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
  270. resp = MakeRequest(t, req, http.StatusBadRequest)
  271. parsedError = new(auth.AccessTokenError)
  272. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  273. assert.Equal(t, "invalid_request", string(parsedError.ErrorCode))
  274. assert.Equal(t, "client_id in request body inconsistent with Authorization header", parsedError.ErrorDescription)
  275. // client_secret inconsistent with Authorization header
  276. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  277. "grant_type": "authorization_code",
  278. "redirect_uri": "a",
  279. "code": "authcode",
  280. "client_secret": "inconsistent",
  281. })
  282. req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
  283. resp = MakeRequest(t, req, http.StatusBadRequest)
  284. parsedError = new(auth.AccessTokenError)
  285. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  286. assert.Equal(t, "invalid_request", string(parsedError.ErrorCode))
  287. assert.Equal(t, "client_secret in request body inconsistent with Authorization header", parsedError.ErrorDescription)
  288. }
  289. func TestRefreshTokenInvalidation(t *testing.T) {
  290. defer tests.PrepareTestEnv(t)()
  291. req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  292. "grant_type": "authorization_code",
  293. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  294. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  295. "redirect_uri": "a",
  296. "code": "authcode",
  297. "code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
  298. })
  299. resp := MakeRequest(t, req, http.StatusOK)
  300. type response struct {
  301. AccessToken string `json:"access_token"`
  302. TokenType string `json:"token_type"`
  303. ExpiresIn int64 `json:"expires_in"`
  304. RefreshToken string `json:"refresh_token"`
  305. }
  306. parsed := new(response)
  307. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
  308. // test without invalidation
  309. setting.OAuth2.InvalidateRefreshTokens = false
  310. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  311. "grant_type": "refresh_token",
  312. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  313. // omit secret
  314. "redirect_uri": "a",
  315. "refresh_token": parsed.RefreshToken,
  316. })
  317. resp = MakeRequest(t, req, http.StatusBadRequest)
  318. parsedError := new(auth.AccessTokenError)
  319. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  320. assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
  321. assert.Equal(t, "invalid empty client secret", parsedError.ErrorDescription)
  322. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  323. "grant_type": "refresh_token",
  324. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  325. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  326. "redirect_uri": "a",
  327. "refresh_token": "UNEXPECTED",
  328. })
  329. resp = MakeRequest(t, req, http.StatusBadRequest)
  330. parsedError = new(auth.AccessTokenError)
  331. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  332. assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
  333. assert.Equal(t, "unable to parse refresh token", parsedError.ErrorDescription)
  334. req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
  335. "grant_type": "refresh_token",
  336. "client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
  337. "client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
  338. "redirect_uri": "a",
  339. "refresh_token": parsed.RefreshToken,
  340. })
  341. bs, err := io.ReadAll(req.Body)
  342. assert.NoError(t, err)
  343. req.Body = io.NopCloser(bytes.NewReader(bs))
  344. MakeRequest(t, req, http.StatusOK)
  345. req.Body = io.NopCloser(bytes.NewReader(bs))
  346. MakeRequest(t, req, http.StatusOK)
  347. // test with invalidation
  348. setting.OAuth2.InvalidateRefreshTokens = true
  349. req.Body = io.NopCloser(bytes.NewReader(bs))
  350. MakeRequest(t, req, http.StatusOK)
  351. // repeat request should fail
  352. req.Body = io.NopCloser(bytes.NewReader(bs))
  353. resp = MakeRequest(t, req, http.StatusBadRequest)
  354. parsedError = new(auth.AccessTokenError)
  355. assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
  356. assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
  357. assert.Equal(t, "token was already used", parsedError.ErrorDescription)
  358. }