1// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
2// See LICENSE.txt for license information.
3
4package web
5
6import (
7	"context"
8	"encoding/base64"
9	"encoding/json"
10	"io"
11	"io/ioutil"
12	"net/http"
13	"net/http/httptest"
14	"net/url"
15	"strings"
16	"testing"
17
18	"github.com/stretchr/testify/assert"
19	"github.com/stretchr/testify/require"
20
21	"github.com/mattermost/mattermost-server/v6/app/request"
22	"github.com/mattermost/mattermost-server/v6/einterfaces"
23	"github.com/mattermost/mattermost-server/v6/model"
24	"github.com/mattermost/mattermost-server/v6/shared/i18n"
25	"github.com/mattermost/mattermost-server/v6/shared/mlog"
26	"github.com/mattermost/mattermost-server/v6/utils"
27)
28
29func TestOAuthComplete_AccessDenied(t *testing.T) {
30	th := Setup(t).InitBasic()
31	defer th.TearDown()
32
33	c := &Context{
34		App: th.App,
35		Params: &Params{
36			Service: "TestService",
37		},
38	}
39	responseWriter := httptest.NewRecorder()
40	request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/signup/TestService/complete?error=access_denied", nil)
41
42	completeOAuth(c, responseWriter, request)
43
44	response := responseWriter.Result()
45
46	assert.Equal(t, http.StatusTemporaryRedirect, response.StatusCode)
47
48	location, _ := url.Parse(response.Header.Get("Location"))
49	assert.Equal(t, "oauth_access_denied", location.Query().Get("type"))
50	assert.Equal(t, "TestService", location.Query().Get("service"))
51}
52
53func TestAuthorizeOAuthApp(t *testing.T) {
54	th := Setup(t).InitBasic()
55	th.Login(apiClient, th.SystemAdminUser)
56	defer th.TearDown()
57
58	enableOAuth := *th.App.Config().ServiceSettings.EnableOAuthServiceProvider
59	defer func() {
60		th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
61	}()
62
63	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
64
65	oapp := &model.OAuthApp{
66		Name:         GenerateTestAppName(),
67		Homepage:     "https://nowhere.com",
68		Description:  "test",
69		CallbackUrls: []string{"https://nowhere.com"},
70		CreatorId:    th.SystemAdminUser.Id,
71	}
72
73	rapp, appErr := th.App.CreateOAuthApp(oapp)
74	require.Nil(t, appErr)
75
76	authRequest := &model.AuthorizeRequest{
77		ResponseType: model.AuthCodeResponseType,
78		ClientId:     rapp.Id,
79		RedirectURI:  rapp.CallbackUrls[0],
80		Scope:        "",
81		State:        "123",
82	}
83
84	// Test auth code flow
85	ruri, _, err := apiClient.AuthorizeOAuthApp(authRequest)
86	require.NoError(t, err)
87
88	require.NotEmpty(t, ruri, "redirect url should be set")
89
90	ru, _ := url.Parse(ruri)
91	require.NotNil(t, ru, "redirect url unparseable")
92	require.NotEmpty(t, ru.Query().Get("code"), "authorization code not returned")
93	require.Equal(t, ru.Query().Get("state"), authRequest.State, "returned state doesn't match")
94
95	// Test implicit flow
96	authRequest.ResponseType = model.ImplicitResponseType
97	ruri, _, err = apiClient.AuthorizeOAuthApp(authRequest)
98	require.NoError(t, err)
99	require.False(t, ruri == "", "redirect url should be set")
100
101	ru, _ = url.Parse(ruri)
102	require.NotNil(t, ru, "redirect url unparseable")
103	values, err := url.ParseQuery(ru.Fragment)
104	require.NoError(t, err)
105	assert.False(t, values.Get("access_token") == "", "access_token not returned")
106	assert.Equal(t, authRequest.State, values.Get("state"), "returned state doesn't match")
107
108	oldToken := apiClient.AuthToken
109	apiClient.AuthToken = values.Get("access_token")
110	_, resp, err := apiClient.AuthorizeOAuthApp(authRequest)
111	require.Error(t, err)
112	CheckForbiddenStatus(t, resp)
113
114	apiClient.AuthToken = oldToken
115
116	authRequest.RedirectURI = ""
117	_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
118	require.Error(t, err)
119	CheckBadRequestStatus(t, resp)
120
121	authRequest.RedirectURI = "http://somewhereelse.com"
122	_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
123	require.Error(t, err)
124	CheckBadRequestStatus(t, resp)
125
126	authRequest.RedirectURI = rapp.CallbackUrls[0]
127	authRequest.ResponseType = ""
128	_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
129	require.Error(t, err)
130	CheckBadRequestStatus(t, resp)
131
132	authRequest.ResponseType = model.AuthCodeResponseType
133	authRequest.ClientId = ""
134	_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
135	require.Error(t, err)
136	CheckBadRequestStatus(t, resp)
137
138	authRequest.ClientId = model.NewId()
139	_, resp, err = apiClient.AuthorizeOAuthApp(authRequest)
140	require.Error(t, err)
141	CheckNotFoundStatus(t, resp)
142}
143
144func TestDeauthorizeOAuthApp(t *testing.T) {
145	th := Setup(t).InitBasic()
146	th.Login(apiClient, th.SystemAdminUser)
147	defer th.TearDown()
148
149	enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
150	defer func() {
151		th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
152	}()
153	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
154
155	oapp := &model.OAuthApp{
156		Name:         GenerateTestAppName(),
157		Homepage:     "https://nowhere.com",
158		Description:  "test",
159		CallbackUrls: []string{"https://nowhere.com"},
160		CreatorId:    th.SystemAdminUser.Id,
161	}
162
163	rapp, appErr := th.App.CreateOAuthApp(oapp)
164	require.Nil(t, appErr)
165
166	authRequest := &model.AuthorizeRequest{
167		ResponseType: model.AuthCodeResponseType,
168		ClientId:     rapp.Id,
169		RedirectURI:  rapp.CallbackUrls[0],
170		Scope:        "",
171		State:        "123",
172	}
173
174	_, _, err := apiClient.AuthorizeOAuthApp(authRequest)
175	require.NoError(t, err)
176
177	_, err = apiClient.DeauthorizeOAuthApp(rapp.Id)
178	require.NoError(t, err)
179
180	resp, err := apiClient.DeauthorizeOAuthApp("junk")
181	require.Error(t, err)
182	CheckBadRequestStatus(t, resp)
183
184	_, err = apiClient.DeauthorizeOAuthApp(model.NewId())
185	require.NoError(t, err)
186
187	th.Logout(apiClient)
188	resp, err = apiClient.DeauthorizeOAuthApp(rapp.Id)
189	require.Error(t, err)
190	CheckUnauthorizedStatus(t, resp)
191}
192
193func TestOAuthAccessToken(t *testing.T) {
194	if testing.Short() {
195		t.SkipNow()
196	}
197
198	th := Setup(t).InitBasic()
199	th.Login(apiClient, th.SystemAdminUser)
200	defer th.TearDown()
201
202	enableOAuth := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
203	defer func() {
204		th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuth })
205	}()
206	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
207
208	defaultRolePermissions := th.SaveDefaultRolePermissions()
209	defer func() {
210		th.RestoreDefaultRolePermissions(defaultRolePermissions)
211	}()
212	th.AddPermissionToRole(model.PermissionManageOAuth.Id, model.TeamUserRoleId)
213	th.AddPermissionToRole(model.PermissionManageOAuth.Id, model.SystemUserRoleId)
214
215	oauthApp := &model.OAuthApp{
216		Name:         "TestApp5" + model.NewId(),
217		Homepage:     "https://nowhere.com",
218		Description:  "test",
219		CallbackUrls: []string{"https://nowhere.com"},
220		CreatorId:    th.SystemAdminUser.Id,
221	}
222	oauthApp, appErr := th.App.CreateOAuthApp(oauthApp)
223	require.Nil(t, appErr)
224
225	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false })
226	data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{oauthApp.CallbackUrls[0]}}
227
228	_, _, err := apiClient.GetOAuthAccessToken(data)
229	require.Error(t, err, "should have failed - oauth providing turned off")
230	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
231
232	authRequest := &model.AuthorizeRequest{
233		ResponseType: model.AuthCodeResponseType,
234		ClientId:     oauthApp.Id,
235		RedirectURI:  oauthApp.CallbackUrls[0],
236		Scope:        "all",
237		State:        "123",
238	}
239
240	redirect, _, err := apiClient.AuthorizeOAuthApp(authRequest)
241	require.NoError(t, err)
242	rurl, _ := url.Parse(redirect)
243
244	apiClient.Logout()
245
246	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]}}
247
248	_, _, err = apiClient.GetOAuthAccessToken(data)
249	require.Error(t, err, "should have failed - bad grant type")
250
251	data.Set("grant_type", model.AccessTokenGrantType)
252	data.Set("client_id", "")
253	_, _, err = apiClient.GetOAuthAccessToken(data)
254	require.Error(t, err, "should have failed - missing client id")
255
256	data.Set("client_id", "junk")
257	_, _, err = apiClient.GetOAuthAccessToken(data)
258	require.Error(t, err, "should have failed - bad client id")
259
260	data.Set("client_id", oauthApp.Id)
261	data.Set("client_secret", "")
262	_, _, err = apiClient.GetOAuthAccessToken(data)
263	require.Error(t, err, "should have failed - missing client secret")
264
265	data.Set("client_secret", "junk")
266	_, _, err = apiClient.GetOAuthAccessToken(data)
267	require.Error(t, err, "should have failed - bad client secret")
268
269	data.Set("client_secret", oauthApp.ClientSecret)
270	data.Set("code", "")
271	_, _, err = apiClient.GetOAuthAccessToken(data)
272	require.Error(t, err, "should have failed - missing code")
273
274	data.Set("code", "junk")
275	_, _, err = apiClient.GetOAuthAccessToken(data)
276	require.Error(t, err, "should have failed - bad code")
277
278	data.Set("code", rurl.Query().Get("code"))
279	data.Set("redirect_uri", "junk")
280	_, _, err = apiClient.GetOAuthAccessToken(data)
281	require.Error(t, err, "should have failed - non-matching redirect uri")
282
283	// reset data for successful request
284	data.Set("grant_type", model.AccessTokenGrantType)
285	data.Set("client_id", oauthApp.Id)
286	data.Set("client_secret", oauthApp.ClientSecret)
287	data.Set("code", rurl.Query().Get("code"))
288	data.Set("redirect_uri", oauthApp.CallbackUrls[0])
289
290	token := ""
291	refreshToken := ""
292	rsp, _, err := apiClient.GetOAuthAccessToken(data)
293	require.NoError(t, err)
294	require.NotEmpty(t, rsp.AccessToken, "access token not returned")
295	require.NotEmpty(t, rsp.RefreshToken, "refresh token not returned")
296	token, refreshToken = rsp.AccessToken, rsp.RefreshToken
297	require.Equal(t, rsp.TokenType, model.AccessTokenType, "access token type incorrect")
298
299	_, err = apiClient.DoAPIGet("/oauth_test", "")
300	require.NoError(t, err)
301
302	apiClient.SetOAuthToken("")
303	_, err = apiClient.DoAPIGet("/oauth_test", "")
304	require.Error(t, err, "should have failed - no access token provided")
305
306	apiClient.SetOAuthToken("badtoken")
307	_, err = apiClient.DoAPIGet("/oauth_test", "")
308	require.Error(t, err, "should have failed - bad token provided")
309
310	apiClient.SetOAuthToken(token)
311	_, err = apiClient.DoAPIGet("/oauth_test", "")
312	require.NoError(t, err)
313
314	_, _, err = apiClient.GetOAuthAccessToken(data)
315	require.Error(t, err, "should have failed - tried to reuse auth code")
316
317	data.Set("grant_type", model.RefreshTokenGrantType)
318	data.Set("client_id", oauthApp.Id)
319	data.Set("client_secret", oauthApp.ClientSecret)
320	data.Set("refresh_token", "")
321	data.Set("redirect_uri", oauthApp.CallbackUrls[0])
322	data.Del("code")
323	_, _, err = apiClient.GetOAuthAccessToken(data)
324	require.Error(t, err, "Should have failed - refresh token empty")
325
326	data.Set("refresh_token", refreshToken)
327	rsp, _, err = apiClient.GetOAuthAccessToken(data)
328	require.NoError(t, err)
329	require.NotEmpty(t, rsp.AccessToken, "access token not returned")
330	require.NotEmpty(t, rsp.RefreshToken, "refresh token not returned")
331	require.NotEqual(t, rsp.RefreshToken, refreshToken, "refresh token did not update")
332	require.Equal(t, rsp.TokenType, model.AccessTokenType, "access token type incorrect")
333
334	apiClient.SetOAuthToken(rsp.AccessToken)
335	_, err = apiClient.DoAPIGet("/oauth_test", "")
336	require.NoError(t, err)
337
338	data.Set("refresh_token", rsp.RefreshToken)
339	rsp, _, err = apiClient.GetOAuthAccessToken(data)
340	require.NoError(t, err)
341	require.NotEmpty(t, rsp.AccessToken, "access token not returned")
342	require.NotEmpty(t, rsp.RefreshToken, "refresh token not returned")
343	require.NotEqual(t, rsp.RefreshToken, refreshToken, "refresh token did not update")
344	require.Equal(t, rsp.TokenType, model.AccessTokenType, "access token type incorrect")
345
346	apiClient.SetOAuthToken(rsp.AccessToken)
347	_, err = apiClient.DoAPIGet("/oauth_test", "")
348	require.NoError(t, err)
349
350	authData := &model.AuthData{ClientId: oauthApp.Id, RedirectUri: oauthApp.CallbackUrls[0], UserId: th.BasicUser.Id, Code: model.NewId(), ExpiresIn: -1}
351	_, err = th.App.Srv().Store.OAuth().SaveAuthData(authData)
352	require.NoError(t, err)
353
354	data.Set("grant_type", model.AccessTokenGrantType)
355	data.Set("client_id", oauthApp.Id)
356	data.Set("client_secret", oauthApp.ClientSecret)
357	data.Set("redirect_uri", oauthApp.CallbackUrls[0])
358	data.Set("code", authData.Code)
359	data.Del("refresh_token")
360	_, _, err = apiClient.GetOAuthAccessToken(data)
361	require.Error(t, err, "Should have failed - code is expired")
362
363	apiClient.ClearOAuthToken()
364}
365
366func TestMobileLoginWithOAuth(t *testing.T) {
367	th := Setup(t).InitBasic()
368	defer th.TearDown()
369	c := &Context{
370		App:        th.App,
371		AppContext: &request.Context{},
372		Params: &Params{
373			Service: "gitlab",
374		},
375	}
376
377	var siteURL = "http://localhost:8065"
378	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.SiteURL = siteURL })
379
380	translationFunc := i18n.GetUserTranslations("en")
381	c.AppContext.SetT(translationFunc)
382	c.Logger = th.TestLogger
383	provider := &MattermostTestProvider{}
384	einterfaces.RegisterOAuthProvider(model.ServiceGitlab, provider)
385
386	t.Run("Should include redirect URL in the output when valid URL Scheme is passed", func(t *testing.T) {
387		responseWriter := httptest.NewRecorder()
388		request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/oauth/gitlab/mobile_login?redirect_to="+url.QueryEscape("randomScheme://"), nil)
389		mobileLoginWithOAuth(c, responseWriter, request)
390		assert.Contains(t, responseWriter.Body.String(), "randomScheme://")
391		assert.NotContains(t, responseWriter.Body.String(), siteURL)
392	})
393
394	t.Run("Should not include the redirect URL consisting of javascript protocol", func(t *testing.T) {
395		responseWriter := httptest.NewRecorder()
396		request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/oauth/gitlab/mobile_login?redirect_to="+url.QueryEscape("javascript:alert('hello')"), nil)
397		mobileLoginWithOAuth(c, responseWriter, request)
398		assert.NotContains(t, responseWriter.Body.String(), "javascript:alert('hello')")
399		assert.Contains(t, responseWriter.Body.String(), siteURL)
400	})
401
402	t.Run("Should not include the redirect URL consisting of javascript protocol in mixed case", func(t *testing.T) {
403		responseWriter := httptest.NewRecorder()
404		request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/oauth/gitlab/mobile_login?redirect_to="+url.QueryEscape("JaVasCript:alert('hello')"), nil)
405		mobileLoginWithOAuth(c, responseWriter, request)
406		assert.NotContains(t, responseWriter.Body.String(), "JaVasCript:alert('hello')")
407		assert.Contains(t, responseWriter.Body.String(), siteURL)
408	})
409}
410
411func TestOAuthComplete(t *testing.T) {
412	if testing.Short() {
413		t.SkipNow()
414	}
415
416	th := Setup(t).InitBasic()
417	th.Login(apiClient, th.SystemAdminUser)
418	defer th.TearDown()
419
420	gitLabSettingsEnable := th.App.Config().GitLabSettings.Enable
421	gitLabSettingsAuthEndpoint := th.App.Config().GitLabSettings.AuthEndpoint
422	gitLabSettingsId := th.App.Config().GitLabSettings.Id
423	gitLabSettingsSecret := th.App.Config().GitLabSettings.Secret
424	gitLabSettingsTokenEndpoint := th.App.Config().GitLabSettings.TokenEndpoint
425	gitLabSettingsUserAPIEndpoint := th.App.Config().GitLabSettings.UserAPIEndpoint
426	enableOAuthServiceProvider := th.App.Config().ServiceSettings.EnableOAuthServiceProvider
427	defer func() {
428		th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Enable = gitLabSettingsEnable })
429		th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.AuthEndpoint = gitLabSettingsAuthEndpoint })
430		th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Id = gitLabSettingsId })
431		th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.Secret = gitLabSettingsSecret })
432		th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.TokenEndpoint = gitLabSettingsTokenEndpoint })
433		th.App.UpdateConfig(func(cfg *model.Config) { cfg.GitLabSettings.UserAPIEndpoint = gitLabSettingsUserAPIEndpoint })
434		th.App.UpdateConfig(func(cfg *model.Config) { cfg.ServiceSettings.EnableOAuthServiceProvider = enableOAuthServiceProvider })
435	}()
436
437	r, err := HTTPGet(apiClient.URL+"/login/gitlab/complete?code=123", apiClient.HTTPClient, "", true)
438	assert.Error(t, err)
439	closeBody(r)
440
441	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Enable = true })
442	r, err = HTTPGet(apiClient.URL+"/login/gitlab/complete?code=123&state=!#$#F@#Yˆ&~ñ", apiClient.HTTPClient, "", true)
443	assert.Error(t, err)
444	closeBody(r)
445
446	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.AuthEndpoint = apiClient.URL + "/oauth/authorize" })
447	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Id = model.NewId() })
448
449	stateProps := map[string]string{}
450	stateProps["action"] = model.OAuthActionLogin
451	stateProps["team_id"] = th.BasicTeam.Id
452	stateProps["redirect_to"] = *th.App.Config().GitLabSettings.AuthEndpoint
453
454	state := base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
455	r, err = HTTPGet(apiClient.URL+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), apiClient.HTTPClient, "", true)
456	assert.Error(t, err)
457	closeBody(r)
458
459	stateProps["hash"] = utils.HashSha256(*th.App.Config().GitLabSettings.Id)
460	state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
461	r, err = HTTPGet(apiClient.URL+"/login/gitlab/complete?code=123&state="+url.QueryEscape(state), apiClient.HTTPClient, "", true)
462	assert.Error(t, err)
463	closeBody(r)
464
465	// We are going to use mattermost as the provider emulating gitlab
466	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
467
468	defaultRolePermissions := th.SaveDefaultRolePermissions()
469	defer func() {
470		th.RestoreDefaultRolePermissions(defaultRolePermissions)
471	}()
472	th.AddPermissionToRole(model.PermissionManageOAuth.Id, model.TeamUserRoleId)
473	th.AddPermissionToRole(model.PermissionManageOAuth.Id, model.SystemUserRoleId)
474
475	oauthApp := &model.OAuthApp{
476		Name:        "TestApp5" + model.NewId(),
477		Homepage:    "https://nowhere.com",
478		Description: "test",
479		CallbackUrls: []string{
480			apiClient.URL + "/signup/" + model.ServiceGitlab + "/complete",
481			apiClient.URL + "/login/" + model.ServiceGitlab + "/complete",
482		},
483		CreatorId: th.SystemAdminUser.Id,
484		IsTrusted: true,
485	}
486	oauthApp, appErr := th.App.CreateOAuthApp(oauthApp)
487	require.Nil(t, appErr)
488
489	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Id = oauthApp.Id })
490	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Secret = oauthApp.ClientSecret })
491	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.AuthEndpoint = apiClient.URL + "/oauth/authorize" })
492	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.TokenEndpoint = apiClient.URL + "/oauth/access_token" })
493	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.UserAPIEndpoint = apiClient.APIURL + "/users/me" })
494
495	provider := &MattermostTestProvider{}
496
497	authRequest := &model.AuthorizeRequest{
498		ResponseType: model.AuthCodeResponseType,
499		ClientId:     oauthApp.Id,
500		RedirectURI:  oauthApp.CallbackUrls[0],
501		Scope:        "all",
502		State:        "123",
503	}
504
505	redirect, _, err := apiClient.AuthorizeOAuthApp(authRequest)
506	require.NoError(t, err)
507	rurl, _ := url.Parse(redirect)
508
509	code := rurl.Query().Get("code")
510	stateProps["action"] = model.OAuthActionEmailToSSO
511	delete(stateProps, "team_id")
512	stateProps["redirect_to"] = *th.App.Config().GitLabSettings.AuthEndpoint
513	stateProps["hash"] = utils.HashSha256(*th.App.Config().GitLabSettings.Id)
514	stateProps["redirect_to"] = "/oauth/authorize"
515	state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
516	r, err = HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false)
517	if err == nil {
518		closeBody(r)
519	}
520
521	einterfaces.RegisterOAuthProvider(model.ServiceGitlab, provider)
522
523	redirect, _, err = apiClient.AuthorizeOAuthApp(authRequest)
524	require.NoError(t, err)
525	rurl, _ = url.Parse(redirect)
526
527	code = rurl.Query().Get("code")
528	r, err = HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false)
529	if err == nil {
530		closeBody(r)
531	}
532
533	_, nErr := th.App.Srv().Store.User().UpdateAuthData(
534		th.BasicUser.Id, model.ServiceGitlab, &th.BasicUser.Email, th.BasicUser.Email, true)
535	require.NoError(t, nErr)
536
537	redirect, _, err = apiClient.AuthorizeOAuthApp(authRequest)
538	require.NoError(t, err)
539	rurl, _ = url.Parse(redirect)
540
541	code = rurl.Query().Get("code")
542	stateProps["action"] = model.OAuthActionLogin
543	state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
544	if r, err = HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false); err == nil {
545		closeBody(r)
546	}
547
548	redirect, _, err = apiClient.AuthorizeOAuthApp(authRequest)
549	require.NoError(t, err)
550	rurl, _ = url.Parse(redirect)
551
552	code = rurl.Query().Get("code")
553	delete(stateProps, "action")
554	state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
555	if r, err = HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false); err == nil {
556		closeBody(r)
557	}
558
559	redirect, _, err = apiClient.AuthorizeOAuthApp(authRequest)
560	require.NoError(t, err)
561	rurl, _ = url.Parse(redirect)
562
563	code = rurl.Query().Get("code")
564	stateProps["action"] = model.OAuthActionSignup
565	state = base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
566	if r, err := HTTPGet(apiClient.URL+"/login/"+model.ServiceGitlab+"/complete?code="+url.QueryEscape(code)+"&state="+url.QueryEscape(state), apiClient.HTTPClient, "", false); err == nil {
567		closeBody(r)
568	}
569}
570
571func TestOAuthComplete_ErrorMessages(t *testing.T) {
572	th := Setup(t).InitBasic()
573	defer th.TearDown()
574	c := &Context{
575		App:        th.App,
576		AppContext: &request.Context{},
577		Params: &Params{
578			Service: "gitlab",
579		},
580	}
581
582	translationFunc := i18n.GetUserTranslations("en")
583	c.AppContext.SetT(translationFunc)
584	c.Logger = mlog.CreateConsoleTestLogger(true, mlog.LvlDebug)
585	defer c.Logger.Shutdown()
586	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.GitLabSettings.Enable = true })
587	th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true })
588	provider := &MattermostTestProvider{}
589	einterfaces.RegisterOAuthProvider(model.ServiceGitlab, provider)
590
591	responseWriter := httptest.NewRecorder()
592
593	// Renders for web & mobile app with webview
594	request, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/signup/gitlab/complete?code=1234", nil)
595
596	completeOAuth(c, responseWriter, request)
597	assert.Contains(t, responseWriter.Body.String(), "<!-- web error message -->")
598
599	// Renders for mobile app with redirect url
600	stateProps := map[string]string{}
601	stateProps["action"] = model.OAuthActionMobile
602	stateProps["redirect_to"] = th.App.Config().NativeAppSettings.AppCustomURLSchemes[0]
603	state := base64.StdEncoding.EncodeToString([]byte(model.MapToJSON(stateProps)))
604	request2, _ := http.NewRequest(http.MethodGet, th.App.GetSiteURL()+"/signup/gitlab/complete?code=1234&state="+url.QueryEscape(state), nil)
605
606	completeOAuth(c, responseWriter, request2)
607	assert.Contains(t, responseWriter.Body.String(), "<!-- mobile app message -->")
608}
609
610func HTTPGet(url string, httpClient *http.Client, authToken string, followRedirect bool) (*http.Response, error) {
611	rq, _ := http.NewRequest("GET", url, nil)
612	rq.Close = true
613
614	if authToken != "" {
615		rq.Header.Set(model.HeaderAuth, authToken)
616	}
617
618	if !followRedirect {
619		httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error {
620			return http.ErrUseLastResponse
621		}
622	}
623
624	if rp, err := httpClient.Do(rq); err != nil {
625		return nil, err
626	} else if rp.StatusCode == 304 {
627		return rp, nil
628	} else if rp.StatusCode == 307 {
629		return rp, nil
630	} else if rp.StatusCode >= 300 {
631		defer closeBody(rp)
632		return rp, model.AppErrorFromJSON(rp.Body)
633	} else {
634		return rp, nil
635	}
636}
637
638func closeBody(r *http.Response) {
639	if r != nil && r.Body != nil {
640		ioutil.ReadAll(r.Body)
641		r.Body.Close()
642	}
643}
644
645type MattermostTestProvider struct {
646}
647
648func (m *MattermostTestProvider) GetUserFromJSON(data io.Reader, tokenUser *model.User) (*model.User, error) {
649	var user model.User
650	if err := json.NewDecoder(data).Decode(&user); err != nil {
651		return nil, err
652	}
653	user.AuthData = &user.Email
654	return &user, nil
655}
656
657func (m *MattermostTestProvider) GetSSOSettings(config *model.Config, service string) (*model.SSOSettings, error) {
658	return &config.GitLabSettings, nil
659}
660
661func (m *MattermostTestProvider) GetUserFromIdToken(token string) (*model.User, error) {
662	return nil, nil
663}
664
665func (m *MattermostTestProvider) IsSameUser(dbUser, oauthUser *model.User) bool {
666	return dbUser.AuthData == oauthUser.AuthData
667}
668
669func GenerateTestAppName() string {
670	return "fakeoauthapp" + model.NewRandomString(10)
671}
672
673func checkHTTPStatus(t *testing.T, resp *model.Response, expectedStatus int) {
674	t.Helper()
675
676	require.NotNilf(t, resp, "Unexpected nil response, expected http status:%v", expectedStatus)
677
678	require.Equalf(t, expectedStatus, resp.StatusCode, "Expected http status:%v, got %v", expectedStatus, resp.StatusCode)
679}
680
681func CheckForbiddenStatus(t *testing.T, resp *model.Response) {
682	t.Helper()
683	checkHTTPStatus(t, resp, http.StatusForbidden)
684}
685
686func CheckUnauthorizedStatus(t *testing.T, resp *model.Response) {
687	t.Helper()
688	checkHTTPStatus(t, resp, http.StatusUnauthorized)
689}
690
691func CheckNotFoundStatus(t *testing.T, resp *model.Response) {
692	t.Helper()
693	checkHTTPStatus(t, resp, http.StatusNotFound)
694}
695
696func CheckBadRequestStatus(t *testing.T, resp *model.Response) {
697	t.Helper()
698	checkHTTPStatus(t, resp, http.StatusBadRequest)
699}
700
701func (th *TestHelper) Login(client *model.Client4, user *model.User) {
702	session := &model.Session{
703		UserId:  user.Id,
704		Roles:   user.GetRawRoles(),
705		IsOAuth: false,
706	}
707	session, _ = th.App.CreateSession(session)
708	client.AuthToken = session.Token
709	client.AuthType = model.HeaderBearer
710}
711
712func (th *TestHelper) Logout(client *model.Client4) {
713	client.AuthToken = ""
714}
715
716func (th *TestHelper) SaveDefaultRolePermissions() map[string][]string {
717	results := make(map[string][]string)
718
719	for _, roleName := range []string{
720		"system_user",
721		"system_admin",
722		"team_user",
723		"team_admin",
724		"channel_user",
725		"channel_admin",
726	} {
727		role, err1 := th.App.GetRoleByName(context.Background(), roleName)
728		if err1 != nil {
729			panic(err1)
730		}
731
732		results[roleName] = role.Permissions
733	}
734	return results
735}
736
737func (th *TestHelper) RestoreDefaultRolePermissions(data map[string][]string) {
738	for roleName, permissions := range data {
739		role, err1 := th.App.GetRoleByName(context.Background(), roleName)
740		if err1 != nil {
741			panic(err1)
742		}
743
744		if strings.Join(role.Permissions, " ") == strings.Join(permissions, " ") {
745			continue
746		}
747
748		role.Permissions = permissions
749
750		_, err2 := th.App.UpdateRole(role)
751		if err2 != nil {
752			panic(err2)
753		}
754	}
755}
756
757// func (th *TestHelper) RemovePermissionFromRole(permission string, roleName string) {
758// 	utils.DisableDebugLogForTest()
759
760// 	role, err1 := th.App.GetRoleByName(roleName)
761// 	if err1 != nil {
762// 		utils.EnableDebugLogForTest()
763// 		panic(err1)
764// 	}
765
766// 	var newPermissions []string
767// 	for _, p := range role.Permissions {
768// 		if p != permission {
769// 			newPermissions = append(newPermissions, p)
770// 		}
771// 	}
772
773// 	if strings.Join(role.Permissions, " ") == strings.Join(newPermissions, " ") {
774// 		utils.EnableDebugLogForTest()
775// 		return
776// 	}
777
778// 	role.Permissions = newPermissions
779
780// 	_, err2 := th.App.UpdateRole(role)
781// 	if err2 != nil {
782// 		utils.EnableDebugLogForTest()
783// 		panic(err2)
784// 	}
785
786// 	utils.EnableDebugLogForTest()
787// }
788
789func (th *TestHelper) AddPermissionToRole(permission string, roleName string) {
790	role, err1 := th.App.GetRoleByName(context.Background(), roleName)
791	if err1 != nil {
792		panic(err1)
793	}
794
795	for _, existingPermission := range role.Permissions {
796		if existingPermission == permission {
797			return
798		}
799	}
800
801	role.Permissions = append(role.Permissions, permission)
802
803	_, err2 := th.App.UpdateRole(role)
804	if err2 != nil {
805		panic(err2)
806	}
807}
808