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