Mono repo -> Master (#22553)
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
Этот коммит содержится в:
849
server/channels/web/oauth_test.go
Обычный файл
849
server/channels/web/oauth_test.go
Обычный файл
@@ -0,0 +1,849 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package web
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/model"
|
||||
"github.com/mattermost/mattermost-server/v6/server/channels/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/v6/server/channels/utils"
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/i18n"
|
||||
"github.com/mattermost/mattermost-server/v6/server/platform/shared/mlog"
|
||||
)
|
||||
|
||||
func TestOAuthComplete_AccessDenied(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
Params: &Params{
|
||||
Service: "TestService",
|
||||
},
|
||||
}
|
||||
responseWriter := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/signup/TestService/complete?error=access_denied", nil)
|
||||
|
||||
completeOAuth(c, responseWriter, request)
|
||||
|
||||
response := responseWriter.Result()
|
||||
|
||||
assert.Equal(t, http.StatusTemporaryRedirect, response.StatusCode)
|
||||
|
||||
location, _ := url.Parse(response.Header.Get("Location"))
|
||||
assert.Equal(t, "oauth_access_denied", location.Query().Get("type"))
|
||||
assert.Equal(t, "TestService", location.Query().Get("service"))
|
||||
}
|
||||
|
||||
func TestAuthorizeOAuthApp(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
th.Login(apiClient, th.SystemAdminUser)
|
||||
defer th.TearDown()
|
||||
|
||||
enableOAuth := *th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
oapp := &model.OAuthApp{
|
||||
Name: GenerateTestAppName(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{"https://nowhere.com"},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
}
|
||||
|
||||
rapp, appErr := th.App.CreateOAuthApp(oapp)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AuthCodeResponseType,
|
||||
ClientId: rapp.Id,
|
||||
RedirectURI: rapp.CallbackUrls[0],
|
||||
Scope: "",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
// Test auth code flow
|
||||
ruri, _, err := apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotEmpty(t, ruri, "redirect url should be set")
|
||||
|
||||
ru, _ := url.Parse(ruri)
|
||||
require.NotNil(t, ru, "redirect url unparseable")
|
||||
require.NotEmpty(t, ru.Query().Get("code"), "authorization code not returned")
|
||||
require.Equal(t, ru.Query().Get("state"), authRequest.State, "returned state doesn't match")
|
||||
|
||||
// Test implicit flow
|
||||
authRequest.ResponseType = model.ImplicitResponseType
|
||||
ruri, _, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
require.False(t, ruri == "", "redirect url should be set")
|
||||
|
||||
ru, _ = url.Parse(ruri)
|
||||
require.NotNil(t, ru, "redirect url unparseable")
|
||||
values, err := url.ParseQuery(ru.Fragment)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, values.Get("access_token") == "", "access_token not returned")
|
||||
assert.Equal(t, authRequest.State, values.Get("state"), "returned state doesn't match")
|
||||
|
||||
oldToken := apiClient.AuthToken
|
||||
apiClient.AuthToken = values.Get("access_token")
|
||||
_, resp, err := apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.Error(t, err)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
apiClient.AuthToken = oldToken
|
||||
|
||||
authRequest.RedirectURI = ""
|
||||
_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.RedirectURI = "http://somewhereelse.com"
|
||||
_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.RedirectURI = rapp.CallbackUrls[0]
|
||||
authRequest.ResponseType = ""
|
||||
_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.ResponseType = model.AuthCodeResponseType
|
||||
authRequest.ClientId = ""
|
||||
_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
authRequest.ClientId = model.NewId()
|
||||
_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.Error(t, err)
|
||||
CheckNotFoundStatus(t, resp)
|
||||
|
||||
// test callback URI doesn't have malformed query parameters
|
||||
oappWithQueryParamInCallback := &model.OAuthApp{
|
||||
Name: GenerateTestAppName(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{"https://nowhere.com?simply=lovely"},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
}
|
||||
|
||||
rapp, appErr = th.App.CreateOAuthApp(oappWithQueryParamInCallback)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
authRequest = &model.AuthorizeRequest{
|
||||
ResponseType: model.AuthCodeResponseType,
|
||||
ClientId: rapp.Id,
|
||||
RedirectURI: rapp.CallbackUrls[0],
|
||||
Scope: "",
|
||||
State: "123",
|
||||
}
|
||||
uriResponse, _, err := apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
ru, _ = url.Parse(uriResponse)
|
||||
require.NotEmpty(t, uriResponse, "redirect url should be set")
|
||||
require.NotNil(t, ru, "redirect url unparseable")
|
||||
// require no query parameter to have "?"
|
||||
require.False(t, strings.Contains(ru.RawQuery, "?"), "should not malform query parameters")
|
||||
require.NotEmpty(t, ru.Query().Get("code"), "authorization code not returned")
|
||||
require.Equal(t, ru.Query().Get("state"), authRequest.State, "returned state doesn't match")
|
||||
}
|
||||
|
||||
func TestDeauthorizeOAuthApp(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
th.Login(apiClient, th.SystemAdminUser)
|
||||
defer th.TearDown()
|
||||
|
||||
enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
oapp := &model.OAuthApp{
|
||||
Name: GenerateTestAppName(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{"https://nowhere.com"},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
}
|
||||
|
||||
rapp, appErr := th.App.CreateOAuthApp(oapp)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AuthCodeResponseType,
|
||||
ClientId: rapp.Id,
|
||||
RedirectURI: rapp.CallbackUrls[0],
|
||||
Scope: "",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
_, _, err := apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = apiClient.DeauthorizeOAuthApp(rapp.Id)
|
||||
require.NoError(t, err)
|
||||
|
||||
resp, err := apiClient.DeauthorizeOAuthApp("junk")
|
||||
require.Error(t, err)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
_, err = apiClient.DeauthorizeOAuthApp(model.NewId())
|
||||
require.NoError(t, err)
|
||||
|
||||
th.Logout(apiClient)
|
||||
resp, err = apiClient.DeauthorizeOAuthApp(rapp.Id)
|
||||
require.Error(t, err)
|
||||
CheckUnauthorizedStatus(t, resp)
|
||||
}
|
||||
|
||||
func TestOAuthAccessToken(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := Setup(t).InitBasic()
|
||||
th.Login(apiClient, th.SystemAdminUser)
|
||||
defer th.TearDown()
|
||||
|
||||
enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
|
||||
}()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageOAuth.Id, model.TeamUserRoleId)
|
||||
th.AddPermissionToRole(model.PermissionManageOAuth.Id, model.SystemUserRoleId)
|
||||
|
||||
oauthApp := &model.OAuthApp{
|
||||
Name: "TestApp5" + model.NewId(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{"https://nowhere.com"},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
}
|
||||
oauthApp, appErr := th.App.CreateOAuthApp(oauthApp)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
|
||||
data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
|
||||
|
||||
_, _, err := apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - oauth providing turned off")
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AuthCodeResponseType,
|
||||
ClientId: oauthApp.Id,
|
||||
RedirectURI: oauthApp.CallbackUrls[0],
|
||||
Scope: "all",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
redirect, _, err := apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
rurl, _ := url.Parse(redirect)
|
||||
|
||||
apiClient.Logout()
|
||||
|
||||
data = url.Values{"grant_type": []string{"junk"}, "client_id": []string{oauthApp.Id}, "client_secret": []string{oauthApp.ClientSecret}, "code": []string{rurl.Query().Get("code")}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
|
||||
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - bad grant type")
|
||||
|
||||
data.Set("grant_type", model.AccessTokenGrantType)
|
||||
data.Set("client_id", "")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - missing client id")
|
||||
|
||||
data.Set("client_id", "junk")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - bad client id")
|
||||
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", "")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - missing client secret")
|
||||
|
||||
data.Set("client_secret", "junk")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - bad client secret")
|
||||
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("code", "")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - missing code")
|
||||
|
||||
data.Set("code", "junk")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - bad code")
|
||||
|
||||
data.Set("code", rurl.Query().Get("code"))
|
||||
data.Set("redirect_uri", "junk")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - non-matching redirect uri")
|
||||
|
||||
// reset data for successful request
|
||||
data.Set("grant_type", model.AccessTokenGrantType)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("code", rurl.Query().Get("code"))
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
|
||||
token := ""
|
||||
refreshToken := ""
|
||||
rsp, _, err := apiClient.GetOAuthAccessToken(data)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, rsp.AccessToken, "access token not returned")
|
||||
require.NotEmpty(t, rsp.RefreshToken, "refresh token not returned")
|
||||
token, refreshToken = rsp.AccessToken, rsp.RefreshToken
|
||||
require.Equal(t, rsp.TokenType, model.AccessTokenType, "access token type incorrect")
|
||||
|
||||
_, err = apiClient.DoAPIGet("/oauth_test", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
apiClient.SetOAuthToken("")
|
||||
_, err = apiClient.DoAPIGet("/oauth_test", "")
|
||||
require.Error(t, err, "should have failed - no access token provided")
|
||||
|
||||
apiClient.SetOAuthToken("badtoken")
|
||||
_, err = apiClient.DoAPIGet("/oauth_test", "")
|
||||
require.Error(t, err, "should have failed - bad token provided")
|
||||
|
||||
apiClient.SetOAuthToken(token)
|
||||
_, err = apiClient.DoAPIGet("/oauth_test", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "should have failed - tried to reuse auth code")
|
||||
|
||||
data.Set("grant_type", model.RefreshTokenGrantType)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("refresh_token", "")
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
data.Del("code")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "Should have failed - refresh token empty")
|
||||
|
||||
data.Set("refresh_token", refreshToken)
|
||||
rsp, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, rsp.AccessToken, "access token not returned")
|
||||
require.NotEmpty(t, rsp.RefreshToken, "refresh token not returned")
|
||||
require.NotEqual(t, rsp.RefreshToken, refreshToken, "refresh token did not update")
|
||||
require.Equal(t, rsp.TokenType, model.AccessTokenType, "access token type incorrect")
|
||||
|
||||
apiClient.SetOAuthToken(rsp.AccessToken)
|
||||
_, err = apiClient.DoAPIGet("/oauth_test", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
data.Set("refresh_token", rsp.RefreshToken)
|
||||
rsp, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, rsp.AccessToken, "access token not returned")
|
||||
require.NotEmpty(t, rsp.RefreshToken, "refresh token not returned")
|
||||
require.NotEqual(t, rsp.RefreshToken, refreshToken, "refresh token did not update")
|
||||
require.Equal(t, rsp.TokenType, model.AccessTokenType, "access token type incorrect")
|
||||
|
||||
apiClient.SetOAuthToken(rsp.AccessToken)
|
||||
_, err = apiClient.DoAPIGet("/oauth_test", "")
|
||||
require.NoError(t, err)
|
||||
|
||||
authData := &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: -1}
|
||||
_, err = th.App.Srv().Store().OAuth().SaveAuthData(authData)
|
||||
require.NoError(t, err)
|
||||
|
||||
data.Set("grant_type", model.AccessTokenGrantType)
|
||||
data.Set("client_id", oauthApp.Id)
|
||||
data.Set("client_secret", oauthApp.ClientSecret)
|
||||
data.Set("redirect_uri", oauthApp.CallbackUrls[0])
|
||||
data.Set("code", authData.Code)
|
||||
data.Del("refresh_token")
|
||||
_, _, err = apiClient.GetOAuthAccessToken(data)
|
||||
require.Error(t, err, "Should have failed - code is expired")
|
||||
|
||||
apiClient.ClearOAuthToken()
|
||||
}
|
||||
|
||||
func TestMobileLoginWithOAuth(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
AppContext: th.Context,
|
||||
Params: &Params{
|
||||
Service: "gitlab",
|
||||
},
|
||||
}
|
||||
|
||||
var siteURL = "http://localhost:8065"
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = siteURL })
|
||||
|
||||
translationFunc := i18n.GetUserTranslations("en")
|
||||
c.AppContext.SetT(translationFunc)
|
||||
c.Logger = th.TestLogger
|
||||
provider := &MattermostTestProvider{}
|
||||
einterfaces.RegisterOAuthProvider(model.ServiceGitlab, provider)
|
||||
|
||||
t.Run("Should include redirect URL in the output when valid URL Scheme is passed", func(t *testing.T) {
|
||||
responseWriter := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/oauth/gitlab/mobile_login?redirect_to="+url.QueryEscape("randomScheme://"), nil)
|
||||
mobileLoginWithOAuth(c, responseWriter, request)
|
||||
assert.Contains(t, responseWriter.Body.String(), "randomScheme://")
|
||||
assert.NotContains(t, responseWriter.Body.String(), siteURL)
|
||||
})
|
||||
|
||||
t.Run("Should not include the redirect URL consisting of javascript protocol", func(t *testing.T) {
|
||||
responseWriter := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/oauth/gitlab/mobile_login?redirect_to="+url.QueryEscape("javascript:alert('hello')"), nil)
|
||||
mobileLoginWithOAuth(c, responseWriter, request)
|
||||
assert.NotContains(t, responseWriter.Body.String(), "javascript:alert('hello')")
|
||||
assert.Contains(t, responseWriter.Body.String(), siteURL)
|
||||
})
|
||||
|
||||
t.Run("Should not include the redirect URL consisting of javascript protocol in mixed case", func(t *testing.T) {
|
||||
responseWriter := httptest.NewRecorder()
|
||||
request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/oauth/gitlab/mobile_login?redirect_to="+url.QueryEscape("JaVasCript:alert('hello')"), nil)
|
||||
mobileLoginWithOAuth(c, responseWriter, request)
|
||||
assert.NotContains(t, responseWriter.Body.String(), "JaVasCript:alert('hello')")
|
||||
assert.Contains(t, responseWriter.Body.String(), siteURL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestOAuthComplete(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.SkipNow()
|
||||
}
|
||||
|
||||
th := Setup(t).InitBasic()
|
||||
th.Login(apiClient, th.SystemAdminUser)
|
||||
defer th.TearDown()
|
||||
|
||||
gitLabSettingsEnable := th.App.Config().GitLabSettings.Enable
|
||||
gitLabSettingsAuthEndpoint := th.App.Config().GitLabSettings.AuthEndpoint
|
||||
gitLabSettingsId := th.App.Config().GitLabSettings.Id
|
||||
gitLabSettingsSecret := th.App.Config().GitLabSettings.Secret
|
||||
gitLabSettingsTokenEndpoint := th.App.Config().GitLabSettings.TokenEndpoint
|
||||
gitLabSettingsUserAPIEndpoint := th.App.Config().GitLabSettings.UserAPIEndpoint
|
||||
enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
|
||||
defer func() {
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Enable = gitLabSettingsEnable })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = gitLabSettingsAuthEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = gitLabSettingsId })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Secret = gitLabSettingsSecret })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.TokenEndpoint = gitLabSettingsTokenEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.UserAPIEndpoint = gitLabSettingsUserAPIEndpoint })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
|
||||
}()
|
||||
|
||||
r, err := HTTPGet(apiClient.URL+"/login/gitlab/complete?code=123", apiClient.HTTPClient, "", true)
|
||||
assert.Error(t, err)
|
||||
closeBody(r)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Enable = true })
|
||||
r, err = HTTPGet(apiClient.URL+"/login/gitlab/complete?code=123&state=!#$#F@#Yˆ&~ñ", apiClient.HTTPClient, "", true)
|
||||
assert.Error(t, err)
|
||||
closeBody(r)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.AuthEndpoint = apiClient.URL + "/oauth/authorize" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Id = model.NewId() })
|
||||
|
||||
stateProps := map[string]string{}
|
||||
stateProps["action"] = model.OAuthActionLogin
|
||||
stateProps["team_id"] = th.BasicTeam.Id
|
||||
stateProps["redirect_to"] = *th.App.Config().GitLabSettings.AuthEndpoint
|
||||
|
||||
state := base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
|
||||
r, err = HTTPGet(apiClient.URL+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), apiClient.HTTPClient, "", true)
|
||||
assert.Error(t, err)
|
||||
closeBody(r)
|
||||
|
||||
stateProps["hash"] = utils.HashSha256(*th.App.Config().GitLabSettings.Id)
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
|
||||
r, err = HTTPGet(apiClient.URL+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), apiClient.HTTPClient, "", true)
|
||||
assert.Error(t, err)
|
||||
closeBody(r)
|
||||
|
||||
// We are going to use mattermost as the provider emulating gitlab
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
|
||||
defaultRolePermissions := th.SaveDefaultRolePermissions()
|
||||
defer func() {
|
||||
th.RestoreDefaultRolePermissions(defaultRolePermissions)
|
||||
}()
|
||||
th.AddPermissionToRole(model.PermissionManageOAuth.Id, model.TeamUserRoleId)
|
||||
th.AddPermissionToRole(model.PermissionManageOAuth.Id, model.SystemUserRoleId)
|
||||
|
||||
oauthApp := &model.OAuthApp{
|
||||
Name: "TestApp5" + model.NewId(),
|
||||
Homepage: "https://nowhere.com",
|
||||
Description: "test",
|
||||
CallbackUrls: []string{
|
||||
apiClient.URL + "/signup/" + model.ServiceGitlab + "/complete",
|
||||
apiClient.URL + "/login/" + model.ServiceGitlab + "/complete",
|
||||
},
|
||||
CreatorId: th.SystemAdminUser.Id,
|
||||
IsTrusted: true,
|
||||
}
|
||||
oauthApp, appErr := th.App.CreateOAuthApp(oauthApp)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Id = oauthApp.Id })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Secret = oauthApp.ClientSecret })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.AuthEndpoint = apiClient.URL + "/oauth/authorize" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.TokenEndpoint = apiClient.URL + "/oauth/access_token" })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.UserAPIEndpoint = apiClient.APIURL + "/users/me" })
|
||||
|
||||
provider := &MattermostTestProvider{}
|
||||
|
||||
authRequest := &model.AuthorizeRequest{
|
||||
ResponseType: model.AuthCodeResponseType,
|
||||
ClientId: oauthApp.Id,
|
||||
RedirectURI: oauthApp.CallbackUrls[0],
|
||||
Scope: "all",
|
||||
State: "123",
|
||||
}
|
||||
|
||||
redirect, _, err := apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
rurl, _ := url.Parse(redirect)
|
||||
|
||||
code := rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAuthActionEmailToSSO
|
||||
delete(stateProps, "team_id")
|
||||
stateProps["redirect_to"] = *th.App.Config().GitLabSettings.AuthEndpoint
|
||||
stateProps["hash"] = utils.HashSha256(*th.App.Config().GitLabSettings.Id)
|
||||
stateProps["redirect_to"] = "/oauth/authorize"
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
|
||||
r, err = HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false)
|
||||
if err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
einterfaces.RegisterOAuthProvider(model.ServiceGitlab, provider)
|
||||
|
||||
redirect, _, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
r, err = HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false)
|
||||
if err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
_, nErr := th.App.Srv().Store().User().UpdateAuthData(
|
||||
th.BasicUser.Id, model.ServiceGitlab, &th.BasicUser.Email, th.BasicUser.Email, true)
|
||||
require.NoError(t, nErr)
|
||||
|
||||
redirect, _, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAuthActionLogin
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
|
||||
if r, err = HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
redirect, _, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
delete(stateProps, "action")
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
|
||||
if r, err = HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
|
||||
redirect, _, err = apiClient.AuthorizeOAuthApp(authRequest)
|
||||
require.NoError(t, err)
|
||||
rurl, _ = url.Parse(redirect)
|
||||
|
||||
code = rurl.Query().Get("code")
|
||||
stateProps["action"] = model.OAuthActionSignup
|
||||
state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
|
||||
if r, err := HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false); err == nil {
|
||||
closeBody(r)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOAuthComplete_ErrorMessages(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
c := &Context{
|
||||
App: th.App,
|
||||
AppContext: th.Context,
|
||||
Params: &Params{
|
||||
Service: "gitlab",
|
||||
},
|
||||
}
|
||||
|
||||
translationFunc := i18n.GetUserTranslations("en")
|
||||
c.AppContext.SetT(translationFunc)
|
||||
c.Logger = mlog.CreateConsoleTestLogger(true, mlog.LvlDebug)
|
||||
defer c.Logger.Shutdown()
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Enable = true })
|
||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
|
||||
provider := &MattermostTestProvider{}
|
||||
einterfaces.RegisterOAuthProvider(model.ServiceGitlab, provider)
|
||||
|
||||
responseWriter := httptest.NewRecorder()
|
||||
|
||||
// Renders for web & mobile app with webview
|
||||
request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/signup/gitlab/complete?code=1234", nil)
|
||||
|
||||
completeOAuth(c, responseWriter, request)
|
||||
assert.Contains(t, responseWriter.Body.String(), "<!-- web error message -->")
|
||||
|
||||
// Renders for mobile app with redirect url
|
||||
stateProps := map[string]string{}
|
||||
stateProps["action"] = model.OAuthActionMobile
|
||||
stateProps["redirect_to"] = th.App.Config().NativeAppSettings.AppCustomURLSchemes[0]
|
||||
state := base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
|
||||
request2, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/signup/gitlab/complete?code=1234&state="+url.QueryEscape(state), nil)
|
||||
|
||||
completeOAuth(c, responseWriter, request2)
|
||||
assert.Contains(t, responseWriter.Body.String(), "<!-- mobile app message -->")
|
||||
}
|
||||
|
||||
func HTTPGet(url string, httpClient *http.Client, authToken string, followRedirect bool) (*http.Response, error) {
|
||||
rq, _ := http.NewRequest("GET", url, nil)
|
||||
rq.Close = true
|
||||
|
||||
if authToken != "" {
|
||||
rq.Header.Set(model.HeaderAuth, authToken)
|
||||
}
|
||||
|
||||
if !followRedirect {
|
||||
httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
}
|
||||
}
|
||||
|
||||
if rp, err := httpClient.Do(rq); err != nil {
|
||||
return nil, err
|
||||
} else if rp.StatusCode == 304 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode == 307 {
|
||||
return rp, nil
|
||||
} else if rp.StatusCode >= 300 {
|
||||
defer closeBody(rp)
|
||||
return rp, model.AppErrorFromJSON(rp.Body)
|
||||
} else {
|
||||
return rp, nil
|
||||
}
|
||||
}
|
||||
|
||||
func closeBody(r *http.Response) {
|
||||
if r != nil && r.Body != nil {
|
||||
io.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
}
|
||||
}
|
||||
|
||||
type MattermostTestProvider struct {
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) GetUserFromJSON(data io.Reader, tokenUser *model.User) (*model.User, error) {
|
||||
var user model.User
|
||||
if err := json.NewDecoder(data).Decode(&user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.AuthData = &user.Email
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) GetSSOSettings(config *model.Config, service string) (*model.SSOSettings, error) {
|
||||
return &config.GitLabSettings, nil
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) GetUserFromIdToken(token string) (*model.User, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *MattermostTestProvider) IsSameUser(dbUser, oauthUser *model.User) bool {
|
||||
return dbUser.AuthData == oauthUser.AuthData
|
||||
}
|
||||
|
||||
func GenerateTestAppName() string {
|
||||
return "fakeoauthapp" + model.NewRandomString(10)
|
||||
}
|
||||
|
||||
func checkHTTPStatus(t *testing.T, resp *model.Response, expectedStatus int) {
|
||||
t.Helper()
|
||||
|
||||
require.NotNilf(t, resp, "Unexpected nil response, expected http status:%v", expectedStatus)
|
||||
|
||||
require.Equalf(t, expectedStatus, resp.StatusCode, "Expected http status:%v, got %v", expectedStatus, resp.StatusCode)
|
||||
}
|
||||
|
||||
func CheckForbiddenStatus(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
checkHTTPStatus(t, resp, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func CheckUnauthorizedStatus(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
checkHTTPStatus(t, resp, http.StatusUnauthorized)
|
||||
}
|
||||
|
||||
func CheckNotFoundStatus(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
checkHTTPStatus(t, resp, http.StatusNotFound)
|
||||
}
|
||||
|
||||
func CheckBadRequestStatus(t *testing.T, resp *model.Response) {
|
||||
t.Helper()
|
||||
checkHTTPStatus(t, resp, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
func (th *TestHelper) Login(client *model.Client4, user *model.User) {
|
||||
session := &model.Session{
|
||||
UserId: user.Id,
|
||||
Roles: user.GetRawRoles(),
|
||||
IsOAuth: false,
|
||||
}
|
||||
session, _ = th.App.CreateSession(session)
|
||||
client.AuthToken = session.Token
|
||||
client.AuthType = model.HeaderBearer
|
||||
}
|
||||
|
||||
func (th *TestHelper) Logout(client *model.Client4) {
|
||||
client.AuthToken = ""
|
||||
}
|
||||
|
||||
func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string {
|
||||
results := make(map[string][]string)
|
||||
|
||||
for _, roleName := range []string{
|
||||
"system_user",
|
||||
"system_admin",
|
||||
"team_user",
|
||||
"team_admin",
|
||||
"channel_user",
|
||||
"channel_admin",
|
||||
} {
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
results[roleName] = role.Permissions
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) {
|
||||
for roleName, permissions := range data {
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
if strings.Join(role.Permissions, " ") == strings.Join(permissions, " ") {
|
||||
continue
|
||||
}
|
||||
|
||||
role.Permissions = permissions
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) {
|
||||
// utils.DisableDebugLogForTest()
|
||||
|
||||
// role, err1 := th.App.GetRoleByName(roleName)
|
||||
// if err1 != nil {
|
||||
// utils.EnableDebugLogForTest()
|
||||
// panic(err1)
|
||||
// }
|
||||
|
||||
// var newPermissions []string
|
||||
// for _, p := range role.Permissions {
|
||||
// if p != permission {
|
||||
// newPermissions = append(newPermissions, p)
|
||||
// }
|
||||
// }
|
||||
|
||||
// if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
|
||||
// utils.EnableDebugLogForTest()
|
||||
// return
|
||||
// }
|
||||
|
||||
// role.Permissions = newPermissions
|
||||
|
||||
// _, err2 := th.App.UpdateRole(role)
|
||||
// if err2 != nil {
|
||||
// utils.EnableDebugLogForTest()
|
||||
// panic(err2)
|
||||
// }
|
||||
|
||||
// utils.EnableDebugLogForTest()
|
||||
// }
|
||||
|
||||
func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
|
||||
role, err1 := th.App.GetRoleByName(context.Background(), roleName)
|
||||
if err1 != nil {
|
||||
panic(err1)
|
||||
}
|
||||
|
||||
for _, existingPermission := range role.Permissions {
|
||||
if existingPermission == permission {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
role.Permissions = append(role.Permissions, permission)
|
||||
|
||||
_, err2 := th.App.UpdateRole(role)
|
||||
if err2 != nil {
|
||||
panic(err2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFullyQualifiedRedirectURL(t *testing.T) {
|
||||
const siteURL = "https://xxx.yyy/mm"
|
||||
for target, expected := range map[string]string{
|
||||
"": "https://xxx.yyy/mm",
|
||||
"/": "https://xxx.yyy/mm/",
|
||||
"some-path": "https://xxx.yyy/mm/some-path",
|
||||
"/some-path": "https://xxx.yyy/mm/some-path",
|
||||
"/some-path/": "https://xxx.yyy/mm/some-path/",
|
||||
} {
|
||||
t.Run(target, func(t *testing.T) {
|
||||
require.Equal(t, expected, fullyQualifiedRedirectURL(siteURL, target))
|
||||
})
|
||||
}
|
||||
}
|
||||
Ссылка в новой задаче
Block a user