github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/oauth_test.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "encoding/base64" 8 "encoding/json" 9 "io/ioutil" 10 "net/http" 11 "net/http/httptest" 12 "testing" 13 14 "github.com/mattermost/mattermost-server/v5/model" 15 "github.com/stretchr/testify/assert" 16 "github.com/stretchr/testify/require" 17 ) 18 19 func TestGetOAuthAccessTokenForImplicitFlow(t *testing.T) { 20 th := Setup(t).InitBasic() 21 defer th.TearDown() 22 23 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true }) 24 25 oapp := &model.OAuthApp{ 26 Name: "fakeoauthapp" + model.NewRandomString(10), 27 CreatorId: th.BasicUser2.Id, 28 Homepage: "https://nowhere.com", 29 Description: "test", 30 CallbackUrls: []string{"https://nowhere.com"}, 31 } 32 33 oapp, err := th.App.CreateOAuthApp(oapp) 34 require.Nil(t, err) 35 36 authRequest := &model.AuthorizeRequest{ 37 ResponseType: model.IMPLICIT_RESPONSE_TYPE, 38 ClientId: oapp.Id, 39 RedirectUri: oapp.CallbackUrls[0], 40 Scope: "", 41 State: "123", 42 } 43 44 session, err := th.App.GetOAuthAccessTokenForImplicitFlow(th.BasicUser.Id, authRequest) 45 assert.Nil(t, err) 46 assert.NotNil(t, session) 47 48 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = false }) 49 50 session, err = th.App.GetOAuthAccessTokenForImplicitFlow(th.BasicUser.Id, authRequest) 51 assert.NotNil(t, err, "should fail - oauth2 disabled") 52 assert.Nil(t, session) 53 54 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableOAuthServiceProvider = true }) 55 authRequest.ClientId = "junk" 56 57 session, err = th.App.GetOAuthAccessTokenForImplicitFlow(th.BasicUser.Id, authRequest) 58 assert.NotNil(t, err, "should fail - bad client id") 59 assert.Nil(t, session) 60 61 authRequest.ClientId = oapp.Id 62 63 session, err = th.App.GetOAuthAccessTokenForImplicitFlow("junk", authRequest) 64 assert.NotNil(t, err, "should fail - bad user id") 65 assert.Nil(t, session) 66 } 67 68 func TestOAuthRevokeAccessToken(t *testing.T) { 69 th := Setup(t) 70 defer th.TearDown() 71 72 err := th.App.RevokeAccessToken(model.NewRandomString(16)) 73 require.NotNil(t, err, "Should have failed bad token") 74 75 session := &model.Session{} 76 session.CreateAt = model.GetMillis() 77 session.UserId = model.NewId() 78 session.Token = model.NewId() 79 session.Roles = model.SYSTEM_USER_ROLE_ID 80 th.App.SetSessionExpireInDays(session, 1) 81 82 session, _ = th.App.CreateSession(session) 83 err = th.App.RevokeAccessToken(session.Token) 84 require.NotNil(t, err, "Should have failed does not have an access token") 85 86 accessData := &model.AccessData{} 87 accessData.Token = session.Token 88 accessData.UserId = session.UserId 89 accessData.RedirectUri = "http://example.com" 90 accessData.ClientId = model.NewId() 91 accessData.ExpiresAt = session.ExpiresAt 92 93 _, nErr := th.App.Srv().Store.OAuth().SaveAccessData(accessData) 94 require.Nil(t, nErr) 95 96 err = th.App.RevokeAccessToken(accessData.Token) 97 require.Nil(t, err) 98 } 99 100 func TestOAuthDeleteApp(t *testing.T) { 101 th := Setup(t) 102 defer th.TearDown() 103 104 *th.App.Config().ServiceSettings.EnableOAuthServiceProvider = true 105 106 a1 := &model.OAuthApp{} 107 a1.CreatorId = model.NewId() 108 a1.Name = "TestApp" + model.NewId() 109 a1.CallbackUrls = []string{"https://nowhere.com"} 110 a1.Homepage = "https://nowhere.com" 111 112 var err *model.AppError 113 a1, err = th.App.CreateOAuthApp(a1) 114 require.Nil(t, err) 115 116 session := &model.Session{} 117 session.CreateAt = model.GetMillis() 118 session.UserId = model.NewId() 119 session.Token = model.NewId() 120 session.Roles = model.SYSTEM_USER_ROLE_ID 121 session.IsOAuth = true 122 th.App.SetSessionExpireInDays(session, 1) 123 124 session, _ = th.App.CreateSession(session) 125 126 accessData := &model.AccessData{} 127 accessData.Token = session.Token 128 accessData.UserId = session.UserId 129 accessData.RedirectUri = "http://example.com" 130 accessData.ClientId = a1.Id 131 accessData.ExpiresAt = session.ExpiresAt 132 133 _, nErr := th.App.Srv().Store.OAuth().SaveAccessData(accessData) 134 require.Nil(t, nErr) 135 136 err = th.App.DeleteOAuthApp(a1.Id) 137 require.Nil(t, err) 138 139 _, err = th.App.GetSession(session.Token) 140 require.NotNil(t, err, "should not get session from cache or db") 141 } 142 143 func TestAuthorizeOAuthUser(t *testing.T) { 144 setup := func(t *testing.T, enable, tokenEndpoint, userEndpoint bool, serverURL string) *TestHelper { 145 th := Setup(t) 146 147 th.App.UpdateConfig(func(cfg *model.Config) { 148 *cfg.GitLabSettings.Enable = enable 149 150 if tokenEndpoint { 151 *cfg.GitLabSettings.TokenEndpoint = serverURL + "/token" 152 } else { 153 *cfg.GitLabSettings.TokenEndpoint = "" 154 } 155 156 if userEndpoint { 157 *cfg.GitLabSettings.UserApiEndpoint = serverURL + "/user" 158 } else { 159 *cfg.GitLabSettings.UserApiEndpoint = "" 160 } 161 }) 162 163 return th 164 } 165 166 makeState := func(token *model.Token) string { 167 return base64.StdEncoding.EncodeToString([]byte(model.MapToJson(map[string]string{ 168 "token": token.Token, 169 }))) 170 } 171 172 makeToken := func(th *TestHelper, cookie string) *model.Token { 173 token, _ := th.App.CreateOAuthStateToken(generateOAuthStateTokenExtra("", "", cookie)) 174 return token 175 } 176 177 makeRequest := func(t *testing.T, cookie string) *http.Request { 178 request, _ := http.NewRequest(http.MethodGet, "https://mattermost.example.com", nil) 179 180 if cookie != "" { 181 request.AddCookie(&http.Cookie{ 182 Name: COOKIE_OAUTH, 183 Value: cookie, 184 }) 185 } 186 187 return request 188 } 189 190 t.Run("not enabled", func(t *testing.T) { 191 th := setup(t, false, true, true, "") 192 defer th.TearDown() 193 194 _, _, _, err := th.App.AuthorizeOAuthUser(nil, nil, model.SERVICE_GITLAB, "", "", "") 195 require.NotNil(t, err) 196 assert.Equal(t, "api.user.authorize_oauth_user.unsupported.app_error", err.Id) 197 }) 198 199 t.Run("with an improperly encoded state", func(t *testing.T) { 200 th := setup(t, true, true, true, "") 201 defer th.TearDown() 202 203 state := "!" 204 205 _, _, _, err := th.App.AuthorizeOAuthUser(nil, nil, model.SERVICE_GITLAB, "", state, "") 206 require.NotNil(t, err) 207 assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id) 208 }) 209 210 t.Run("without a stored token", func(t *testing.T) { 211 th := setup(t, true, true, true, "") 212 defer th.TearDown() 213 214 state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(map[string]string{ 215 "token": model.NewId(), 216 }))) 217 218 _, _, _, err := th.App.AuthorizeOAuthUser(nil, nil, model.SERVICE_GITLAB, "", state, "") 219 require.NotNil(t, err) 220 assert.Equal(t, "api.oauth.invalid_state_token.app_error", err.Id) 221 assert.NotEqual(t, "", err.DetailedError) 222 }) 223 224 t.Run("with a stored token of the wrong type", func(t *testing.T) { 225 th := setup(t, true, true, true, "") 226 defer th.TearDown() 227 228 token := model.NewToken("invalid", "") 229 require.Nil(t, th.App.Srv().Store.Token().Save(token)) 230 231 state := makeState(token) 232 233 _, _, _, err := th.App.AuthorizeOAuthUser(nil, nil, model.SERVICE_GITLAB, "", state, "") 234 require.NotNil(t, err) 235 assert.Equal(t, "api.oauth.invalid_state_token.app_error", err.Id) 236 assert.Equal(t, "", err.DetailedError) 237 }) 238 239 t.Run("with email missing when changing login types", func(t *testing.T) { 240 th := setup(t, true, true, true, "") 241 defer th.TearDown() 242 243 email := "" 244 action := model.OAUTH_ACTION_EMAIL_TO_SSO 245 cookie := model.NewId() 246 247 token, err := th.App.CreateOAuthStateToken(generateOAuthStateTokenExtra(email, action, cookie)) 248 require.Nil(t, err) 249 250 state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(map[string]string{ 251 "action": action, 252 "email": email, 253 "token": token.Token, 254 }))) 255 256 _, _, _, err = th.App.AuthorizeOAuthUser(nil, nil, model.SERVICE_GITLAB, "", state, "") 257 require.NotNil(t, err) 258 assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id) 259 }) 260 261 t.Run("without an OAuth cookie", func(t *testing.T) { 262 th := setup(t, true, true, true, "") 263 defer th.TearDown() 264 265 cookie := model.NewId() 266 request := makeRequest(t, "") 267 state := makeState(makeToken(th, cookie)) 268 269 _, _, _, err := th.App.AuthorizeOAuthUser(nil, request, model.SERVICE_GITLAB, "", state, "") 270 require.NotNil(t, err) 271 assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id) 272 }) 273 274 t.Run("with an invalid token", func(t *testing.T) { 275 th := setup(t, true, true, true, "") 276 defer th.TearDown() 277 278 cookie := model.NewId() 279 280 token, err := th.App.CreateOAuthStateToken(model.NewId()) 281 require.Nil(t, err) 282 283 request := makeRequest(t, cookie) 284 state := makeState(token) 285 286 _, _, _, err = th.App.AuthorizeOAuthUser(nil, request, model.SERVICE_GITLAB, "", state, "") 287 require.NotNil(t, err) 288 assert.Equal(t, "api.user.authorize_oauth_user.invalid_state.app_error", err.Id) 289 }) 290 291 t.Run("with an incorrect token endpoint", func(t *testing.T) { 292 th := setup(t, true, false, true, "") 293 defer th.TearDown() 294 295 cookie := model.NewId() 296 request := makeRequest(t, cookie) 297 state := makeState(makeToken(th, cookie)) 298 299 _, _, _, err := th.App.AuthorizeOAuthUser(&httptest.ResponseRecorder{}, request, model.SERVICE_GITLAB, "", state, "") 300 require.NotNil(t, err) 301 assert.Equal(t, "api.user.authorize_oauth_user.token_failed.app_error", err.Id) 302 }) 303 304 t.Run("with an error token response", func(t *testing.T) { 305 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 306 w.WriteHeader(http.StatusTeapot) 307 })) 308 defer server.Close() 309 310 th := setup(t, true, true, true, server.URL) 311 defer th.TearDown() 312 313 cookie := model.NewId() 314 request := makeRequest(t, cookie) 315 state := makeState(makeToken(th, cookie)) 316 317 _, _, _, err := th.App.AuthorizeOAuthUser(&httptest.ResponseRecorder{}, request, model.SERVICE_GITLAB, "", state, "") 318 require.NotNil(t, err) 319 assert.Equal(t, "api.user.authorize_oauth_user.bad_response.app_error", err.Id) 320 assert.Contains(t, err.DetailedError, "status_code=418") 321 }) 322 323 t.Run("with an invalid token response", func(t *testing.T) { 324 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 325 w.Write([]byte("invalid")) 326 })) 327 defer server.Close() 328 329 th := setup(t, true, true, true, server.URL) 330 defer th.TearDown() 331 332 cookie := model.NewId() 333 request := makeRequest(t, cookie) 334 state := makeState(makeToken(th, cookie)) 335 336 _, _, _, err := th.App.AuthorizeOAuthUser(&httptest.ResponseRecorder{}, request, model.SERVICE_GITLAB, "", state, "") 337 require.NotNil(t, err) 338 assert.Equal(t, "api.user.authorize_oauth_user.bad_response.app_error", err.Id) 339 assert.Contains(t, err.DetailedError, "response_body=invalid") 340 }) 341 342 t.Run("with an invalid token type", func(t *testing.T) { 343 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 344 json.NewEncoder(w).Encode(&model.AccessResponse{ 345 AccessToken: model.NewId(), 346 TokenType: "", 347 }) 348 })) 349 defer server.Close() 350 351 th := setup(t, true, true, true, server.URL) 352 defer th.TearDown() 353 354 cookie := model.NewId() 355 request := makeRequest(t, cookie) 356 state := makeState(makeToken(th, cookie)) 357 358 _, _, _, err := th.App.AuthorizeOAuthUser(&httptest.ResponseRecorder{}, request, model.SERVICE_GITLAB, "", state, "") 359 require.NotNil(t, err) 360 assert.Equal(t, "api.user.authorize_oauth_user.bad_token.app_error", err.Id) 361 }) 362 363 t.Run("with an empty token response", func(t *testing.T) { 364 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 365 json.NewEncoder(w).Encode(&model.AccessResponse{ 366 AccessToken: "", 367 TokenType: model.ACCESS_TOKEN_TYPE, 368 }) 369 })) 370 defer server.Close() 371 372 th := setup(t, true, true, true, server.URL) 373 defer th.TearDown() 374 375 cookie := model.NewId() 376 request := makeRequest(t, cookie) 377 state := makeState(makeToken(th, cookie)) 378 379 _, _, _, err := th.App.AuthorizeOAuthUser(&httptest.ResponseRecorder{}, request, model.SERVICE_GITLAB, "", state, "") 380 require.NotNil(t, err) 381 assert.Equal(t, "api.user.authorize_oauth_user.missing.app_error", err.Id) 382 }) 383 384 t.Run("with an incorrect user endpoint", func(t *testing.T) { 385 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 386 json.NewEncoder(w).Encode(&model.AccessResponse{ 387 AccessToken: model.NewId(), 388 TokenType: model.ACCESS_TOKEN_TYPE, 389 }) 390 })) 391 defer server.Close() 392 393 th := setup(t, true, true, false, server.URL) 394 defer th.TearDown() 395 396 cookie := model.NewId() 397 request := makeRequest(t, cookie) 398 state := makeState(makeToken(th, cookie)) 399 400 _, _, _, err := th.App.AuthorizeOAuthUser(&httptest.ResponseRecorder{}, request, model.SERVICE_GITLAB, "", state, "") 401 require.NotNil(t, err) 402 assert.Equal(t, "api.user.authorize_oauth_user.service.app_error", err.Id) 403 }) 404 405 t.Run("with an error user response", func(t *testing.T) { 406 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 407 switch r.URL.Path { 408 case "/token": 409 t.Log("hit token") 410 json.NewEncoder(w).Encode(&model.AccessResponse{ 411 AccessToken: model.NewId(), 412 TokenType: model.ACCESS_TOKEN_TYPE, 413 }) 414 case "/user": 415 t.Log("hit user") 416 w.WriteHeader(http.StatusTeapot) 417 } 418 })) 419 defer server.Close() 420 421 th := setup(t, true, true, true, server.URL) 422 defer th.TearDown() 423 424 cookie := model.NewId() 425 request := makeRequest(t, cookie) 426 state := makeState(makeToken(th, cookie)) 427 428 _, _, _, err := th.App.AuthorizeOAuthUser(&httptest.ResponseRecorder{}, request, model.SERVICE_GITLAB, "", state, "") 429 require.NotNil(t, err) 430 assert.Equal(t, "api.user.authorize_oauth_user.response.app_error", err.Id) 431 }) 432 433 t.Run("with an error user response due to GitLab TOS", func(t *testing.T) { 434 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 435 switch r.URL.Path { 436 case "/token": 437 t.Log("hit token") 438 json.NewEncoder(w).Encode(&model.AccessResponse{ 439 AccessToken: model.NewId(), 440 TokenType: model.ACCESS_TOKEN_TYPE, 441 }) 442 case "/user": 443 t.Log("hit user") 444 w.WriteHeader(http.StatusForbidden) 445 w.Write([]byte("Terms of Service")) 446 } 447 })) 448 defer server.Close() 449 450 th := setup(t, true, true, true, server.URL) 451 defer th.TearDown() 452 453 cookie := model.NewId() 454 request := makeRequest(t, cookie) 455 state := makeState(makeToken(th, cookie)) 456 457 _, _, _, err := th.App.AuthorizeOAuthUser(&httptest.ResponseRecorder{}, request, model.SERVICE_GITLAB, "", state, "") 458 require.NotNil(t, err) 459 assert.Equal(t, "oauth.gitlab.tos.error", err.Id) 460 }) 461 462 t.Run("enabled and properly configured", func(t *testing.T) { 463 testCases := []struct { 464 Description string 465 SiteURL string 466 ExpectedSetCookieHeaderRegexp string 467 }{ 468 {"no subpath", "http://localhost:8065", "^MMOAUTH=; Path=/"}, 469 {"subpath", "http://localhost:8065/subpath", "^MMOAUTH=; Path=/subpath"}, 470 } 471 472 for _, tc := range testCases { 473 t.Run(tc.Description, func(t *testing.T) { 474 userData := "Hello, World!" 475 476 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 477 switch r.URL.Path { 478 case "/token": 479 json.NewEncoder(w).Encode(&model.AccessResponse{ 480 AccessToken: model.NewId(), 481 TokenType: model.ACCESS_TOKEN_TYPE, 482 }) 483 case "/user": 484 w.WriteHeader(http.StatusOK) 485 w.Write([]byte(userData)) 486 } 487 })) 488 defer server.Close() 489 490 th := setup(t, true, true, true, server.URL) 491 defer th.TearDown() 492 493 th.App.UpdateConfig(func(cfg *model.Config) { 494 *cfg.ServiceSettings.SiteURL = tc.SiteURL 495 }) 496 497 cookie := model.NewId() 498 request := makeRequest(t, cookie) 499 500 stateProps := map[string]string{ 501 "team_id": model.NewId(), 502 "token": makeToken(th, cookie).Token, 503 } 504 state := base64.StdEncoding.EncodeToString([]byte(model.MapToJson(stateProps))) 505 506 recorder := httptest.ResponseRecorder{} 507 body, receivedTeamId, receivedStateProps, err := th.App.AuthorizeOAuthUser(&recorder, request, model.SERVICE_GITLAB, "", state, "") 508 509 require.NotNil(t, body) 510 bodyBytes, bodyErr := ioutil.ReadAll(body) 511 require.Nil(t, bodyErr) 512 assert.Equal(t, userData, string(bodyBytes)) 513 514 assert.Equal(t, stateProps["team_id"], receivedTeamId) 515 assert.Equal(t, stateProps, receivedStateProps) 516 assert.Nil(t, err) 517 518 cookies := recorder.Header().Get("Set-Cookie") 519 assert.Regexp(t, tc.ExpectedSetCookieHeaderRegexp, cookies) 520 }) 521 } 522 }) 523 } 524 525 func TestGetAuthorizationCode(t *testing.T) { 526 t.Run("not enabled", func(t *testing.T) { 527 th := Setup(t) 528 defer th.TearDown() 529 530 th.App.UpdateConfig(func(cfg *model.Config) { 531 *cfg.GitLabSettings.Enable = false 532 }) 533 534 _, err := th.App.GetAuthorizationCode(nil, nil, model.SERVICE_GITLAB, map[string]string{}, "") 535 require.NotNil(t, err) 536 assert.Equal(t, "api.user.get_authorization_code.unsupported.app_error", err.Id) 537 }) 538 539 t.Run("enabled and properly configured", func(t *testing.T) { 540 th := Setup(t) 541 defer th.TearDown() 542 543 th.App.UpdateConfig(func(cfg *model.Config) { 544 *cfg.GitLabSettings.Enable = true 545 }) 546 547 testCases := []struct { 548 Description string 549 SiteURL string 550 ExpectedSetCookieHeaderRegexp string 551 }{ 552 {"no subpath", "http://localhost:8065", "^MMOAUTH=[a-z0-9]+; Path=/"}, 553 {"subpath", "http://localhost:8065/subpath", "^MMOAUTH=[a-z0-9]+; Path=/subpath"}, 554 } 555 556 for _, tc := range testCases { 557 t.Run(tc.Description, func(t *testing.T) { 558 th.App.UpdateConfig(func(cfg *model.Config) { 559 *cfg.ServiceSettings.SiteURL = tc.SiteURL 560 }) 561 562 request, _ := http.NewRequest(http.MethodGet, "https://mattermost.example.com", nil) 563 564 stateProps := map[string]string{ 565 "email": "email@example.com", 566 "action": "action", 567 } 568 569 recorder := httptest.ResponseRecorder{} 570 url, err := th.App.GetAuthorizationCode(&recorder, request, model.SERVICE_GITLAB, stateProps, "") 571 require.Nil(t, err) 572 assert.NotEmpty(t, url) 573 574 cookies := recorder.Header().Get("Set-Cookie") 575 assert.Regexp(t, tc.ExpectedSetCookieHeaderRegexp, cookies) 576 }) 577 } 578 }) 579 }