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