forked from NYANDEV/forgejo
9fbbe61364
Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/1757 Co-authored-by: Antonin Delpeuch <antonin@delpeuch.eu> Co-committed-by: Antonin Delpeuch <antonin@delpeuch.eu> (cherry picked from commit 0f6e0f90359b4b669d297a533de18b41e3293df2) (cherry picked from commit 779168a572c521507a35ba624dbd032ec28f272e) (cherry picked from commit 29a2457321e4587f55b333d0c5698925e403f026) (cherry picked from commit a1edc2314d2687c9320d884f8a584d8b539eec96) (cherry picked from commit cd015946109d39c6e30091de2fff47eba01eb937) (cherry picked from commit 74db46b0f50a5b465269688ef83e170b7584e2be) (cherry picked from commit fd98f55204f1cec66c3941d85b45dc84f8ab9ecd) (cherry picked from commit 3099d0e2818d1de763a686b6a23dcf5d55ba75ef)
498 lines
20 KiB
Go
498 lines
20 KiB
Go
// Copyright 2019 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package integration
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
|
|
auth_model "code.gitea.io/gitea/models/auth"
|
|
"code.gitea.io/gitea/models/unittest"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/json"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/test"
|
|
"code.gitea.io/gitea/routers/web/auth"
|
|
"code.gitea.io/gitea/tests"
|
|
|
|
"github.com/markbates/goth"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestAuthorizeNoClientID(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequest(t, "GET", "/login/oauth/authorize")
|
|
ctx := loginUser(t, "user2")
|
|
resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
|
|
assert.Contains(t, resp.Body.String(), "Client ID not registered")
|
|
}
|
|
|
|
func TestAuthorizeUnregisteredRedirect(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=UNREGISTERED&response_type=code&state=thestate")
|
|
ctx := loginUser(t, "user1")
|
|
resp := ctx.MakeRequest(t, req, http.StatusBadRequest)
|
|
assert.Contains(t, resp.Body.String(), "Unregistered Redirect URI")
|
|
}
|
|
|
|
func TestAuthorizeUnsupportedResponseType(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=UNEXPECTED&state=thestate")
|
|
ctx := loginUser(t, "user1")
|
|
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
|
u, err := resp.Result().Location()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "unsupported_response_type", u.Query().Get("error"))
|
|
assert.Equal(t, "Only code response type is supported.", u.Query().Get("error_description"))
|
|
}
|
|
|
|
func TestAuthorizeUnsupportedCodeChallengeMethod(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
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")
|
|
ctx := loginUser(t, "user1")
|
|
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
|
u, err := resp.Result().Location()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "invalid_request", u.Query().Get("error"))
|
|
assert.Equal(t, "unsupported code challenge method", u.Query().Get("error_description"))
|
|
}
|
|
|
|
func TestAuthorizeLoginRedirect(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequest(t, "GET", "/login/oauth/authorize")
|
|
assert.Contains(t, MakeRequest(t, req, http.StatusSeeOther).Body.String(), "/user/login")
|
|
}
|
|
|
|
func TestAuthorizeShow(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequest(t, "GET", "/login/oauth/authorize?client_id=da7da3ba-9a13-4167-856f-3899de0b0138&redirect_uri=a&response_type=code&state=thestate")
|
|
ctx := loginUser(t, "user4")
|
|
resp := ctx.MakeRequest(t, req, http.StatusOK)
|
|
|
|
htmlDoc := NewHTMLParser(t, resp.Body)
|
|
htmlDoc.AssertElement(t, "#authorize-app", true)
|
|
htmlDoc.GetCSRF()
|
|
}
|
|
|
|
func TestAuthorizeRedirectWithExistingGrant(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
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")
|
|
ctx := loginUser(t, "user1")
|
|
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
|
u, err := resp.Result().Location()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "thestate", u.Query().Get("state"))
|
|
assert.Truef(t, len(u.Query().Get("code")) > 30, "authorization code '%s' should be longer then 30", u.Query().Get("code"))
|
|
u.RawQuery = ""
|
|
assert.Equal(t, "https://example.com/xyzzy", u.String())
|
|
}
|
|
|
|
func TestAuthorizePKCERequiredForPublicClient(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
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")
|
|
ctx := loginUser(t, "user1")
|
|
resp := ctx.MakeRequest(t, req, http.StatusSeeOther)
|
|
u, err := resp.Result().Location()
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, "invalid_request", u.Query().Get("error"))
|
|
assert.Equal(t, "PKCE is required for public clients", u.Query().Get("error_description"))
|
|
}
|
|
|
|
func TestAccessTokenExchange(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
type response struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int64 `json:"expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
}
|
|
parsed := new(response)
|
|
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
|
|
assert.True(t, len(parsed.AccessToken) > 10)
|
|
assert.True(t, len(parsed.RefreshToken) > 10)
|
|
}
|
|
|
|
func TestAccessTokenExchangeWithPublicClient(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "ce5a1322-42a7-11ed-b878-0242ac120002",
|
|
"redirect_uri": "http://127.0.0.1",
|
|
"code": "authcodepublic",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
type response struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int64 `json:"expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
}
|
|
parsed := new(response)
|
|
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
|
|
assert.True(t, len(parsed.AccessToken) > 10)
|
|
assert.True(t, len(parsed.RefreshToken) > 10)
|
|
}
|
|
|
|
func TestAccessTokenExchangeJSON(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
type response struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int64 `json:"expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
}
|
|
parsed := new(response)
|
|
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
|
|
assert.True(t, len(parsed.AccessToken) > 10)
|
|
assert.True(t, len(parsed.RefreshToken) > 10)
|
|
}
|
|
|
|
func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError := new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "failed PKCE code challenge", parsedError.ErrorDescription)
|
|
}
|
|
|
|
func TestAccessTokenExchangeWithInvalidCredentials(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
// invalid client id
|
|
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "???",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError := new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "cannot load client with client id: '???'", parsedError.ErrorDescription)
|
|
|
|
// invalid client secret
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "???",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "invalid client secret", parsedError.ErrorDescription)
|
|
|
|
// invalid redirect uri
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "???",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "unexpected redirect URI", parsedError.ErrorDescription)
|
|
|
|
// invalid authorization code
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"code": "???",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "client is not authorized", parsedError.ErrorDescription)
|
|
|
|
// invalid grant_type
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "???",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "unsupported_grant_type", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "Only refresh_token or authorization_code grant type is supported", parsedError.ErrorDescription)
|
|
}
|
|
|
|
func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
type response struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int64 `json:"expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
}
|
|
parsed := new(response)
|
|
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
|
|
assert.True(t, len(parsed.AccessToken) > 10)
|
|
assert.True(t, len(parsed.RefreshToken) > 10)
|
|
|
|
// use wrong client_secret
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OmJsYWJsYQ==")
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError := new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "invalid client secret", parsedError.ErrorDescription)
|
|
|
|
// missing header
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "cannot load client with client id: ''", parsedError.ErrorDescription)
|
|
|
|
// client_id inconsistent with Authorization header
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"client_id": "inconsistent",
|
|
})
|
|
req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "invalid_request", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "client_id in request body inconsistent with Authorization header", parsedError.ErrorDescription)
|
|
|
|
// client_secret inconsistent with Authorization header
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"client_secret": "inconsistent",
|
|
})
|
|
req.Header.Add("Authorization", "Basic ZGE3ZGEzYmEtOWExMy00MTY3LTg1NmYtMzg5OWRlMGIwMTM4OjRNSzhOYTZSNTVzbWRDWTBXdUNDdW1aNmhqUlBuR1k1c2FXVlJISGpKaUE9")
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "invalid_request", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "client_secret in request body inconsistent with Authorization header", parsedError.ErrorDescription)
|
|
}
|
|
|
|
func TestRefreshTokenInvalidation(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "authorization_code",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"code": "authcode",
|
|
"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt",
|
|
})
|
|
resp := MakeRequest(t, req, http.StatusOK)
|
|
type response struct {
|
|
AccessToken string `json:"access_token"`
|
|
TokenType string `json:"token_type"`
|
|
ExpiresIn int64 `json:"expires_in"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
}
|
|
parsed := new(response)
|
|
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
|
|
|
|
// test without invalidation
|
|
setting.OAuth2.InvalidateRefreshTokens = false
|
|
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "refresh_token",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
// omit secret
|
|
"redirect_uri": "a",
|
|
"refresh_token": parsed.RefreshToken,
|
|
})
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError := new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "invalid_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "invalid empty client secret", parsedError.ErrorDescription)
|
|
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "refresh_token",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"refresh_token": "UNEXPECTED",
|
|
})
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "unable to parse refresh token", parsedError.ErrorDescription)
|
|
|
|
req = NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
|
|
"grant_type": "refresh_token",
|
|
"client_id": "da7da3ba-9a13-4167-856f-3899de0b0138",
|
|
"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
|
|
"redirect_uri": "a",
|
|
"refresh_token": parsed.RefreshToken,
|
|
})
|
|
|
|
bs, err := io.ReadAll(req.Body)
|
|
assert.NoError(t, err)
|
|
|
|
req.Body = io.NopCloser(bytes.NewReader(bs))
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
req.Body = io.NopCloser(bytes.NewReader(bs))
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// test with invalidation
|
|
setting.OAuth2.InvalidateRefreshTokens = true
|
|
req.Body = io.NopCloser(bytes.NewReader(bs))
|
|
MakeRequest(t, req, http.StatusOK)
|
|
|
|
// repeat request should fail
|
|
req.Body = io.NopCloser(bytes.NewReader(bs))
|
|
resp = MakeRequest(t, req, http.StatusBadRequest)
|
|
parsedError = new(auth.AccessTokenError)
|
|
assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsedError))
|
|
assert.Equal(t, "unauthorized_client", string(parsedError.ErrorCode))
|
|
assert.Equal(t, "token was already used", parsedError.ErrorDescription)
|
|
}
|
|
|
|
func TestSignInOAuthCallbackSignIn(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
|
|
//
|
|
// OAuth2 authentication source GitLab
|
|
//
|
|
gitlabName := "gitlab"
|
|
gitlab := addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
|
|
|
//
|
|
// Create a user as if it had been previously been created by the GitLab
|
|
// authentication source.
|
|
//
|
|
userGitLabUserID := "5678"
|
|
userGitLab := &user_model.User{
|
|
Name: "gitlabuser",
|
|
Email: "gitlabuser@example.com",
|
|
Passwd: "gitlabuserpassword",
|
|
Type: user_model.UserTypeIndividual,
|
|
LoginType: auth_model.OAuth2,
|
|
LoginSource: gitlab.ID,
|
|
LoginName: userGitLabUserID,
|
|
}
|
|
defer createUser(context.Background(), t, userGitLab)()
|
|
|
|
//
|
|
// A request for user information sent to Goth will return a
|
|
// goth.User exactly matching the user created above.
|
|
//
|
|
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
|
return goth.User{
|
|
Provider: gitlabName,
|
|
UserID: userGitLabUserID,
|
|
Email: userGitLab.Email,
|
|
}, nil
|
|
})()
|
|
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
|
resp := MakeRequest(t, req, http.StatusSeeOther)
|
|
assert.Equal(t, test.RedirectURL(resp), "/")
|
|
userAfterLogin := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: userGitLab.ID})
|
|
assert.Greater(t, userAfterLogin.LastLoginUnix, userGitLab.LastLoginUnix)
|
|
}
|
|
|
|
func TestSignUpViaOAuthWithMissingFields(t *testing.T) {
|
|
defer tests.PrepareTestEnv(t)()
|
|
// enable auto-creation of accounts via OAuth2
|
|
enableAutoRegistration := setting.OAuth2Client.EnableAutoRegistration
|
|
setting.OAuth2Client.EnableAutoRegistration = true
|
|
defer func() {
|
|
setting.OAuth2Client.EnableAutoRegistration = enableAutoRegistration
|
|
}()
|
|
|
|
// OAuth2 authentication source GitLab
|
|
gitlabName := "gitlab"
|
|
addAuthSource(t, authSourcePayloadGitLabCustom(gitlabName))
|
|
userGitLabUserID := "5678"
|
|
|
|
// The Goth User returned by the oauth2 integration is missing
|
|
// an email address, so we won't be able to automatically create a local account for it.
|
|
defer mockCompleteUserAuth(func(res http.ResponseWriter, req *http.Request) (goth.User, error) {
|
|
return goth.User{
|
|
Provider: gitlabName,
|
|
UserID: userGitLabUserID,
|
|
}, nil
|
|
})()
|
|
req := NewRequest(t, "GET", fmt.Sprintf("/user/oauth2/%s/callback?code=XYZ&state=XYZ", gitlabName))
|
|
resp := MakeRequest(t, req, http.StatusSeeOther)
|
|
assert.Equal(t, test.RedirectURL(resp), "/user/link_account")
|
|
}
|