github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/app/oauth.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 "bytes" 8 b64 "encoding/base64" 9 "fmt" 10 "io" 11 "io/ioutil" 12 "net/http" 13 "net/url" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/vnforks/kid/v5/einterfaces" 19 "github.com/vnforks/kid/v5/mlog" 20 "github.com/vnforks/kid/v5/model" 21 "github.com/vnforks/kid/v5/store" 22 "github.com/vnforks/kid/v5/utils" 23 ) 24 25 const ( 26 OAUTH_COOKIE_MAX_AGE_SECONDS = 30 * 60 // 30 minutes 27 COOKIE_OAUTH = "MMOAUTH" 28 ) 29 30 func (a *App) CreateOAuthApp(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) { 31 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 32 return nil, model.NewAppError("CreateOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "", http.StatusNotImplemented) 33 } 34 35 app.ClientSecret = model.NewId() 36 37 return a.Srv().Store.OAuth().SaveApp(app) 38 } 39 40 func (a *App) GetOAuthApp(appId string) (*model.OAuthApp, *model.AppError) { 41 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 42 return nil, model.NewAppError("GetOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 43 } 44 return a.Srv().Store.OAuth().GetApp(appId) 45 } 46 47 func (a *App) UpdateOauthApp(oldApp, updatedApp *model.OAuthApp) (*model.OAuthApp, *model.AppError) { 48 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 49 return nil, model.NewAppError("UpdateOauthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 50 } 51 52 updatedApp.Id = oldApp.Id 53 updatedApp.CreatorId = oldApp.CreatorId 54 updatedApp.CreateAt = oldApp.CreateAt 55 updatedApp.ClientSecret = oldApp.ClientSecret 56 57 return a.Srv().Store.OAuth().UpdateApp(updatedApp) 58 } 59 60 func (a *App) DeleteOAuthApp(appId string) *model.AppError { 61 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 62 return model.NewAppError("DeleteOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 63 } 64 65 if err := a.Srv().Store.OAuth().DeleteApp(appId); err != nil { 66 return err 67 } 68 69 if err := a.InvalidateAllCaches(); err != nil { 70 mlog.Error("error in invalidating cache", mlog.Err(err)) 71 } 72 73 return nil 74 } 75 76 func (a *App) GetOAuthApps(page, perPage int) ([]*model.OAuthApp, *model.AppError) { 77 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 78 return nil, model.NewAppError("GetOAuthApps", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 79 } 80 81 return a.Srv().Store.OAuth().GetApps(page*perPage, perPage) 82 } 83 84 func (a *App) GetOAuthAppsByCreator(userId string, page, perPage int) ([]*model.OAuthApp, *model.AppError) { 85 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 86 return nil, model.NewAppError("GetOAuthAppsByUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 87 } 88 89 return a.Srv().Store.OAuth().GetAppByUser(userId, page*perPage, perPage) 90 } 91 92 func (a *App) GetOAuthImplicitRedirect(userId string, authRequest *model.AuthorizeRequest) (string, *model.AppError) { 93 session, err := a.GetOAuthAccessTokenForImplicitFlow(userId, authRequest) 94 if err != nil { 95 return "", err 96 } 97 98 values := &url.Values{} 99 values.Add("access_token", session.Token) 100 values.Add("token_type", "bearer") 101 values.Add("expires_in", strconv.FormatInt((session.ExpiresAt-model.GetMillis())/1000, 10)) 102 values.Add("scope", authRequest.Scope) 103 values.Add("state", authRequest.State) 104 105 return fmt.Sprintf("%s#%s", authRequest.RedirectUri, values.Encode()), nil 106 } 107 108 func (a *App) GetOAuthCodeRedirect(userId string, authRequest *model.AuthorizeRequest) (string, *model.AppError) { 109 authData := &model.AuthData{UserId: userId, ClientId: authRequest.ClientId, CreateAt: model.GetMillis(), RedirectUri: authRequest.RedirectUri, State: authRequest.State, Scope: authRequest.Scope} 110 authData.Code = model.NewId() + model.NewId() 111 112 if _, err := a.Srv().Store.OAuth().SaveAuthData(authData); err != nil { 113 return authRequest.RedirectUri + "?error=server_error&state=" + authRequest.State, nil 114 } 115 116 return authRequest.RedirectUri + "?code=" + url.QueryEscape(authData.Code) + "&state=" + url.QueryEscape(authData.State), nil 117 } 118 119 func (a *App) AllowOAuthAppAccessToUser(userId string, authRequest *model.AuthorizeRequest) (string, *model.AppError) { 120 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 121 return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 122 } 123 124 if len(authRequest.Scope) == 0 { 125 authRequest.Scope = model.DEFAULT_SCOPE 126 } 127 128 oauthApp, err := a.Srv().Store.OAuth().GetApp(authRequest.ClientId) 129 if err != nil { 130 return "", err 131 } 132 133 if !oauthApp.IsValidRedirectURL(authRequest.RedirectUri) { 134 return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest) 135 } 136 137 var redirectURI string 138 139 switch authRequest.ResponseType { 140 case model.AUTHCODE_RESPONSE_TYPE: 141 redirectURI, err = a.GetOAuthCodeRedirect(userId, authRequest) 142 case model.IMPLICIT_RESPONSE_TYPE: 143 redirectURI, err = a.GetOAuthImplicitRedirect(userId, authRequest) 144 default: 145 return authRequest.RedirectUri + "?error=unsupported_response_type&state=" + authRequest.State, nil 146 } 147 148 if err != nil { 149 mlog.Error("error getting oauth redirect uri", mlog.Err(err)) 150 return authRequest.RedirectUri + "?error=server_error&state=" + authRequest.State, nil 151 } 152 153 // This saves the OAuth2 app as authorized 154 authorizedApp := model.Preference{ 155 UserId: userId, 156 Category: model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, 157 Name: authRequest.ClientId, 158 Value: authRequest.Scope, 159 } 160 161 if err = a.Srv().Store.Preference().Save(&model.Preferences{authorizedApp}); err != nil { 162 mlog.Error("error saving store prefrence", mlog.Err(err)) 163 return authRequest.RedirectUri + "?error=server_error&state=" + authRequest.State, nil 164 } 165 166 return redirectURI, nil 167 } 168 169 func (a *App) GetOAuthAccessTokenForImplicitFlow(userId string, authRequest *model.AuthorizeRequest) (*model.Session, *model.AppError) { 170 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 171 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.disabled.app_error", nil, "", http.StatusNotImplemented) 172 } 173 174 oauthApp, err := a.GetOAuthApp(authRequest.ClientId) 175 if err != nil { 176 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "", http.StatusNotFound) 177 } 178 179 user, err := a.GetUser(userId) 180 if err != nil { 181 return nil, err 182 } 183 184 session, err := a.newSession(oauthApp.Name, user) 185 if err != nil { 186 return nil, err 187 } 188 189 accessData := &model.AccessData{ClientId: authRequest.ClientId, UserId: user.Id, Token: session.Token, RefreshToken: "", RedirectUri: authRequest.RedirectUri, ExpiresAt: session.ExpiresAt, Scope: authRequest.Scope} 190 191 if _, err := a.Srv().Store.OAuth().SaveAccessData(accessData); err != nil { 192 mlog.Error("error saving oauth access data in implicit flow", mlog.Err(err)) 193 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError) 194 } 195 196 return session, nil 197 } 198 199 func (a *App) GetOAuthAccessTokenForCodeFlow(clientId, grantType, redirectUri, code, secret, refreshToken string) (*model.AccessResponse, *model.AppError) { 200 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 201 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.disabled.app_error", nil, "", http.StatusNotImplemented) 202 } 203 204 oauthApp, err := a.Srv().Store.OAuth().GetApp(clientId) 205 if err != nil { 206 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "", http.StatusNotFound) 207 } 208 209 if oauthApp.ClientSecret != secret { 210 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "", http.StatusForbidden) 211 } 212 213 var user *model.User 214 var accessData *model.AccessData 215 var accessRsp *model.AccessResponse 216 if grantType == model.ACCESS_TOKEN_GRANT_TYPE { 217 var authData *model.AuthData 218 authData, err = a.Srv().Store.OAuth().GetAuthData(code) 219 if err != nil { 220 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusBadRequest) 221 } 222 223 if authData.IsExpired() { 224 a.Srv().Store.OAuth().RemoveAuthData(authData.Code) 225 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusForbidden) 226 } 227 228 if authData.RedirectUri != redirectUri { 229 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "", http.StatusBadRequest) 230 } 231 232 user, err = a.Srv().Store.User().Get(authData.UserId) 233 if err != nil { 234 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound) 235 } 236 237 accessData, err = a.Srv().Store.OAuth().GetPreviousAccessData(user.Id, clientId) 238 if err != nil { 239 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal.app_error", nil, "", http.StatusBadRequest) 240 } 241 242 if accessData != nil { 243 if accessData.IsExpired() { 244 var access *model.AccessResponse 245 access, err = a.newSessionUpdateToken(oauthApp.Name, accessData, user) 246 if err != nil { 247 return nil, err 248 } 249 accessRsp = access 250 } else { 251 // Return the same token and no need to create a new session 252 accessRsp = &model.AccessResponse{ 253 AccessToken: accessData.Token, 254 TokenType: model.ACCESS_TOKEN_TYPE, 255 RefreshToken: accessData.RefreshToken, 256 ExpiresIn: int32((accessData.ExpiresAt - model.GetMillis()) / 1000), 257 } 258 } 259 } else { 260 var session *model.Session 261 // Create a new session and return new access token 262 session, err = a.newSession(oauthApp.Name, user) 263 if err != nil { 264 return nil, err 265 } 266 267 accessData = &model.AccessData{ClientId: clientId, UserId: user.Id, Token: session.Token, RefreshToken: model.NewId(), RedirectUri: redirectUri, ExpiresAt: session.ExpiresAt, Scope: authData.Scope} 268 269 if _, err = a.Srv().Store.OAuth().SaveAccessData(accessData); err != nil { 270 mlog.Error("error saving oauth access data in token for code flow", mlog.Err(err)) 271 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError) 272 } 273 274 accessRsp = &model.AccessResponse{ 275 AccessToken: session.Token, 276 TokenType: model.ACCESS_TOKEN_TYPE, 277 RefreshToken: accessData.RefreshToken, 278 ExpiresIn: int32(*a.Config().ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24), 279 } 280 } 281 282 a.Srv().Store.OAuth().RemoveAuthData(authData.Code) 283 } else { 284 // When grantType is refresh_token 285 accessData, err = a.Srv().Store.OAuth().GetAccessDataByRefreshToken(refreshToken) 286 if err != nil { 287 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.refresh_token.app_error", nil, "", http.StatusNotFound) 288 } 289 290 user, err := a.Srv().Store.User().Get(accessData.UserId) 291 if err != nil { 292 return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound) 293 } 294 295 access, err := a.newSessionUpdateToken(oauthApp.Name, accessData, user) 296 if err != nil { 297 return nil, err 298 } 299 accessRsp = access 300 } 301 302 return accessRsp, nil 303 } 304 305 func (a *App) newSession(appName string, user *model.User) (*model.Session, *model.AppError) { 306 // Set new token an session 307 session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true} 308 session.GenerateCSRF() 309 session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthSSOInDays) 310 session.AddProp(model.SESSION_PROP_PLATFORM, appName) 311 session.AddProp(model.SESSION_PROP_OS, "OAuth2") 312 session.AddProp(model.SESSION_PROP_BROWSER, "OAuth2") 313 314 session, err := a.Srv().Store.Session().Save(session) 315 if err != nil { 316 return nil, model.NewAppError("newSession", "api.oauth.get_access_token.internal_session.app_error", nil, "", http.StatusInternalServerError) 317 } 318 319 a.AddSessionToCache(session) 320 321 return session, nil 322 } 323 324 func (a *App) newSessionUpdateToken(appName string, accessData *model.AccessData, user *model.User) (*model.AccessResponse, *model.AppError) { 325 // Remove the previous session 326 if err := a.Srv().Store.Session().Remove(accessData.Token); err != nil { 327 mlog.Error("error removing access data token from session", mlog.Err(err)) 328 } 329 330 session, err := a.newSession(appName, user) 331 if err != nil { 332 return nil, err 333 } 334 335 accessData.Token = session.Token 336 accessData.RefreshToken = model.NewId() 337 accessData.ExpiresAt = session.ExpiresAt 338 339 if _, err := a.Srv().Store.OAuth().UpdateAccessData(accessData); err != nil { 340 mlog.Error("error updating oauth access data", mlog.Err(err)) 341 return nil, model.NewAppError("newSessionUpdateToken", "web.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError) 342 } 343 accessRsp := &model.AccessResponse{ 344 AccessToken: session.Token, 345 RefreshToken: accessData.RefreshToken, 346 TokenType: model.ACCESS_TOKEN_TYPE, 347 ExpiresIn: int32(*a.Config().ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24), 348 } 349 350 return accessRsp, nil 351 } 352 353 func (a *App) GetOAuthLoginEndpoint(w http.ResponseWriter, r *http.Request, service, branchId, action, redirectTo, loginHint string) (string, *model.AppError) { 354 stateProps := map[string]string{} 355 stateProps["action"] = action 356 if len(branchId) != 0 { 357 stateProps["branch_id"] = branchId 358 } 359 360 if len(redirectTo) != 0 { 361 stateProps["redirect_to"] = redirectTo 362 } 363 364 authUrl, err := a.GetAuthorizationCode(w, r, service, stateProps, loginHint) 365 if err != nil { 366 return "", err 367 } 368 369 return authUrl, nil 370 } 371 372 func (a *App) GetOAuthSignupEndpoint(w http.ResponseWriter, r *http.Request, service, branchId string) (string, *model.AppError) { 373 stateProps := map[string]string{} 374 stateProps["action"] = model.OAUTH_ACTION_SIGNUP 375 if len(branchId) != 0 { 376 stateProps["branch_id"] = branchId 377 } 378 379 authUrl, err := a.GetAuthorizationCode(w, r, service, stateProps, "") 380 if err != nil { 381 return "", err 382 } 383 384 return authUrl, nil 385 } 386 387 func (a *App) GetAuthorizedAppsForUser(userId string, page, perPage int) ([]*model.OAuthApp, *model.AppError) { 388 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 389 return nil, model.NewAppError("GetAuthorizedAppsForUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 390 } 391 392 apps, err := a.Srv().Store.OAuth().GetAuthorizedApps(userId, page*perPage, perPage) 393 if err != nil { 394 return nil, err 395 } 396 397 for k, a := range apps { 398 a.Sanitize() 399 apps[k] = a 400 } 401 402 return apps, nil 403 } 404 405 func (a *App) DeauthorizeOAuthAppForUser(userId, appId string) *model.AppError { 406 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 407 return model.NewAppError("DeauthorizeOAuthAppForUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 408 } 409 410 // Revoke app sessions 411 accessData, err := a.Srv().Store.OAuth().GetAccessDataByUserForApp(userId, appId) 412 if err != nil { 413 return err 414 } 415 416 for _, ad := range accessData { 417 if err := a.RevokeAccessToken(ad.Token); err != nil { 418 return err 419 } 420 421 if err := a.Srv().Store.OAuth().RemoveAccessData(ad.Token); err != nil { 422 return err 423 } 424 } 425 426 // Deauthorize the app 427 if err := a.Srv().Store.Preference().Delete(userId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, appId); err != nil { 428 return err 429 } 430 431 return nil 432 } 433 434 func (a *App) RegenerateOAuthAppSecret(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) { 435 if !*a.Config().ServiceSettings.EnableOAuthServiceProvider { 436 return nil, model.NewAppError("RegenerateOAuthAppSecret", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented) 437 } 438 439 app.ClientSecret = model.NewId() 440 if _, err := a.Srv().Store.OAuth().UpdateApp(app); err != nil { 441 return nil, err 442 } 443 444 return app, nil 445 } 446 447 func (a *App) RevokeAccessToken(token string) *model.AppError { 448 session, _ := a.GetSession(token) 449 450 schan := make(chan *model.AppError, 1) 451 go func() { 452 schan <- a.Srv().Store.Session().Remove(token) 453 close(schan) 454 }() 455 456 if _, err := a.Srv().Store.OAuth().GetAccessData(token); err != nil { 457 return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.get.app_error", nil, "", http.StatusBadRequest) 458 } 459 460 if err := a.Srv().Store.OAuth().RemoveAccessData(token); err != nil { 461 return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_token.app_error", nil, "", http.StatusInternalServerError) 462 } 463 464 if err := <-schan; err != nil { 465 return model.NewAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_session.app_error", nil, "", http.StatusInternalServerError) 466 } 467 468 if session != nil { 469 a.ClearSessionCacheForUser(session.UserId) 470 } 471 472 return nil 473 } 474 475 func (a *App) CompleteOAuth(service string, body io.ReadCloser, branchId string, props map[string]string) (*model.User, *model.AppError) { 476 defer body.Close() 477 478 action := props["action"] 479 480 switch action { 481 case model.OAUTH_ACTION_SIGNUP: 482 return a.CreateOAuthUser(service, body, branchId) 483 case model.OAUTH_ACTION_LOGIN: 484 return a.LoginByOAuth(service, body, branchId) 485 case model.OAUTH_ACTION_EMAIL_TO_SSO: 486 return a.CompleteSwitchWithOAuth(service, body, props["email"]) 487 case model.OAUTH_ACTION_SSO_TO_EMAIL: 488 return a.LoginByOAuth(service, body, branchId) 489 default: 490 return a.LoginByOAuth(service, body, branchId) 491 } 492 } 493 494 func (a *App) LoginByOAuth(service string, userData io.Reader, branchId string) (*model.User, *model.AppError) { 495 provider := einterfaces.GetOauthProvider(service) 496 if provider == nil { 497 return nil, model.NewAppError("LoginByOAuth", "api.user.login_by_oauth.not_available.app_error", 498 map[string]interface{}{"Service": strings.Title(service)}, "", http.StatusNotImplemented) 499 } 500 501 buf := bytes.Buffer{} 502 if _, err := buf.ReadFrom(userData); err != nil { 503 return nil, model.NewAppError("LoginByOAuth", "api.user.login_by_oauth.parse.app_error", 504 map[string]interface{}{"Service": service}, "", http.StatusBadRequest) 505 } 506 authUser := provider.GetUserFromJson(bytes.NewReader(buf.Bytes())) 507 508 authData := "" 509 if authUser.AuthData != nil { 510 authData = *authUser.AuthData 511 } 512 513 if len(authData) == 0 { 514 return nil, model.NewAppError("LoginByOAuth", "api.user.login_by_oauth.parse.app_error", 515 map[string]interface{}{"Service": service}, "", http.StatusBadRequest) 516 } 517 518 user, err := a.GetUserByAuth(&authData, service) 519 if err != nil { 520 if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR { 521 user, err = a.CreateOAuthUser(service, bytes.NewReader(buf.Bytes()), branchId) 522 } else { 523 return nil, err 524 } 525 } else { 526 // OAuth doesn't run through CheckUserPreflightAuthenticationCriteria, so prevent bot login 527 // here manually. Technically, the auth data above will fail to match a bot in the first 528 // place, but explicit is always better. 529 if user.IsBot { 530 return nil, model.NewAppError("loginByOAuth", "api.user.login_by_oauth.bot_login_forbidden.app_error", nil, "", http.StatusForbidden) 531 } 532 533 if err = a.UpdateOAuthUserAttrs(bytes.NewReader(buf.Bytes()), user, provider, service); err != nil { 534 return nil, err 535 } 536 if len(branchId) > 0 { 537 err = a.AddUserToBranchByBranchId(branchId, user) 538 } 539 } 540 541 if err != nil { 542 return nil, err 543 } 544 545 return user, nil 546 } 547 548 func (a *App) CompleteSwitchWithOAuth(service string, userData io.Reader, email string) (*model.User, *model.AppError) { 549 provider := einterfaces.GetOauthProvider(service) 550 if provider == nil { 551 return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error", 552 map[string]interface{}{"Service": strings.Title(service)}, "", http.StatusNotImplemented) 553 } 554 ssoUser := provider.GetUserFromJson(userData) 555 ssoEmail := ssoUser.Email 556 557 authData := "" 558 if ssoUser.AuthData != nil { 559 authData = *ssoUser.AuthData 560 } 561 562 if len(authData) == 0 { 563 return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.parse.app_error", 564 map[string]interface{}{"Service": service}, "", http.StatusBadRequest) 565 } 566 567 if len(email) == 0 { 568 return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.blank_email.app_error", nil, "", http.StatusBadRequest) 569 } 570 571 user, err := a.Srv().Store.User().GetByEmail(email) 572 if err != nil { 573 return nil, err 574 } 575 576 if err = a.RevokeAllSessions(user.Id); err != nil { 577 return nil, err 578 } 579 580 if _, err = a.Srv().Store.User().UpdateAuthData(user.Id, service, &authData, ssoEmail, true); err != nil { 581 return nil, err 582 } 583 584 a.Srv().Go(func() { 585 if err = a.SendSignInChangeEmail(user.Email, strings.Title(service)+" SSO", user.Locale, a.GetSiteURL()); err != nil { 586 mlog.Error("error sending signin change email", mlog.Err(err)) 587 } 588 }) 589 590 return user, nil 591 } 592 593 func (a *App) CreateOAuthStateToken(extra string) (*model.Token, *model.AppError) { 594 token := model.NewToken(model.TOKEN_TYPE_OAUTH, extra) 595 596 if err := a.Srv().Store.Token().Save(token); err != nil { 597 return nil, err 598 } 599 600 return token, nil 601 } 602 603 func (a *App) GetOAuthStateToken(token string) (*model.Token, *model.AppError) { 604 mToken, err := a.Srv().Store.Token().GetByToken(token) 605 if err != nil { 606 return nil, model.NewAppError("GetOAuthStateToken", "api.oauth.invalid_state_token.app_error", nil, err.Error(), http.StatusBadRequest) 607 } 608 609 if mToken.Type != model.TOKEN_TYPE_OAUTH { 610 return nil, model.NewAppError("GetOAuthStateToken", "api.oauth.invalid_state_token.app_error", nil, "", http.StatusBadRequest) 611 } 612 613 return mToken, nil 614 } 615 616 func (a *App) GetAuthorizationCode(w http.ResponseWriter, r *http.Request, service string, props map[string]string, loginHint string) (string, *model.AppError) { 617 sso := a.Config().GetSSOService(service) 618 if sso == nil || !*sso.Enable { 619 return "", model.NewAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service, http.StatusNotImplemented) 620 } 621 622 secure := false 623 if GetProtocol(r) == "https" { 624 secure = true 625 } 626 627 cookieValue := model.NewId() 628 subpath, _ := utils.GetSubpathFromConfig(a.Config()) 629 630 expiresAt := time.Unix(model.GetMillis()/1000+int64(OAUTH_COOKIE_MAX_AGE_SECONDS), 0) 631 oauthCookie := &http.Cookie{ 632 Name: COOKIE_OAUTH, 633 Value: cookieValue, 634 Path: subpath, 635 MaxAge: OAUTH_COOKIE_MAX_AGE_SECONDS, 636 Expires: expiresAt, 637 HttpOnly: true, 638 Secure: secure, 639 } 640 641 http.SetCookie(w, oauthCookie) 642 643 clientId := *sso.Id 644 endpoint := *sso.AuthEndpoint 645 scope := *sso.Scope 646 647 tokenExtra := generateOAuthStateTokenExtra(props["email"], props["action"], cookieValue) 648 stateToken, err := a.CreateOAuthStateToken(tokenExtra) 649 if err != nil { 650 return "", err 651 } 652 653 props["token"] = stateToken.Token 654 state := b64.StdEncoding.EncodeToString([]byte(model.MapToJson(props))) 655 656 siteUrl := a.GetSiteURL() 657 if strings.TrimSpace(siteUrl) == "" { 658 siteUrl = GetProtocol(r) + "://" + r.Host 659 } 660 661 redirectUri := siteUrl + "/signup/" + service + "/complete" 662 663 authUrl := endpoint + "?response_type=code&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirectUri) + "&state=" + url.QueryEscape(state) 664 665 if len(scope) > 0 { 666 authUrl += "&scope=" + utils.UrlEncode(scope) 667 } 668 669 if len(loginHint) > 0 { 670 authUrl += "&login_hint=" + utils.UrlEncode(loginHint) 671 } 672 673 return authUrl, nil 674 } 675 676 func (a *App) AuthorizeOAuthUser(w http.ResponseWriter, r *http.Request, service, code, state, redirectUri string) (io.ReadCloser, string, map[string]string, *model.AppError) { 677 sso := a.Config().GetSSOService(service) 678 if sso == nil || !*sso.Enable { 679 return nil, "", nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.unsupported.app_error", nil, "service="+service, http.StatusNotImplemented) 680 } 681 682 b, strErr := b64.StdEncoding.DecodeString(state) 683 if strErr != nil { 684 return nil, "", nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, strErr.Error(), http.StatusBadRequest) 685 } 686 687 stateStr := string(b) 688 689 stateProps := model.MapFromJson(strings.NewReader(stateStr)) 690 691 expectedToken, appErr := a.GetOAuthStateToken(stateProps["token"]) 692 if appErr != nil { 693 return nil, "", stateProps, appErr 694 } 695 696 stateEmail := stateProps["email"] 697 stateAction := stateProps["action"] 698 if stateAction == model.OAUTH_ACTION_EMAIL_TO_SSO && stateEmail == "" { 699 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest) 700 } 701 702 cookie, cookieErr := r.Cookie(COOKIE_OAUTH) 703 if cookieErr != nil { 704 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest) 705 } 706 707 expectedTokenExtra := generateOAuthStateTokenExtra(stateEmail, stateAction, cookie.Value) 708 if expectedTokenExtra != expectedToken.Extra { 709 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.invalid_state.app_error", nil, "", http.StatusBadRequest) 710 } 711 712 appErr = a.DeleteToken(expectedToken) 713 if appErr != nil { 714 mlog.Error("error deleting token", mlog.Err(appErr)) 715 } 716 717 subpath, _ := utils.GetSubpathFromConfig(a.Config()) 718 719 httpCookie := &http.Cookie{ 720 Name: COOKIE_OAUTH, 721 Value: "", 722 Path: subpath, 723 MaxAge: -1, 724 HttpOnly: true, 725 } 726 727 http.SetCookie(w, httpCookie) 728 729 branchId := stateProps["branch_id"] 730 731 p := url.Values{} 732 p.Set("client_id", *sso.Id) 733 p.Set("client_secret", *sso.Secret) 734 p.Set("code", code) 735 p.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE) 736 p.Set("redirect_uri", redirectUri) 737 738 req, requestErr := http.NewRequest("POST", *sso.TokenEndpoint, strings.NewReader(p.Encode())) 739 if requestErr != nil { 740 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, requestErr.Error(), http.StatusInternalServerError) 741 } 742 743 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 744 req.Header.Set("Accept", "application/json") 745 746 resp, err := a.HTTPService().MakeClient(true).Do(req) 747 if err != nil { 748 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, err.Error(), http.StatusInternalServerError) 749 } 750 defer resp.Body.Close() 751 752 var buf bytes.Buffer 753 tee := io.TeeReader(resp.Body, &buf) 754 ar := model.AccessResponseFromJson(tee) 755 756 if ar == nil || resp.StatusCode != http.StatusOK { 757 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_response.app_error", nil, fmt.Sprintf("response_body=%s, status_code=%d", buf.String(), resp.StatusCode), http.StatusInternalServerError) 758 } 759 760 if strings.ToLower(ar.TokenType) != model.ACCESS_TOKEN_TYPE { 761 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.bad_token.app_error", nil, "token_type="+ar.TokenType+", response_body="+buf.String(), http.StatusInternalServerError) 762 } 763 764 if len(ar.AccessToken) == 0 { 765 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.missing.app_error", nil, "response_body="+buf.String(), http.StatusInternalServerError) 766 } 767 768 p = url.Values{} 769 p.Set("access_token", ar.AccessToken) 770 req, requestErr = http.NewRequest("GET", *sso.UserApiEndpoint, strings.NewReader("")) 771 if requestErr != nil { 772 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]interface{}{"Service": service}, requestErr.Error(), http.StatusInternalServerError) 773 } 774 775 req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 776 req.Header.Set("Accept", "application/json") 777 req.Header.Set("Authorization", "Bearer "+ar.AccessToken) 778 779 resp, err = a.HTTPService().MakeClient(true).Do(req) 780 if err != nil { 781 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]interface{}{"Service": service}, err.Error(), http.StatusInternalServerError) 782 } else if resp.StatusCode != http.StatusOK { 783 defer resp.Body.Close() 784 785 // Ignore the error below because the resulting string will just be the empty string if bodyBytes is nil 786 bodyBytes, _ := ioutil.ReadAll(resp.Body) 787 bodyString := string(bodyBytes) 788 789 mlog.Error("Error getting OAuth user", mlog.String("body_string", bodyString)) 790 791 if service == model.SERVICE_GITLAB && resp.StatusCode == http.StatusForbidden && strings.Contains(bodyString, "Terms of Service") { 792 // Return a nicer error when the user hasn't accepted GitLab's terms of service 793 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "oauth.gitlab.tos.error", nil, "", http.StatusBadRequest) 794 } 795 796 return nil, "", stateProps, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.response.app_error", nil, "response_body="+bodyString, http.StatusInternalServerError) 797 } 798 799 // Note that resp.Body is not closed here, so it must be closed by the caller 800 return resp.Body, branchId, stateProps, nil 801 } 802 803 func (a *App) SwitchEmailToOAuth(w http.ResponseWriter, r *http.Request, email, password, code, service string) (string, *model.AppError) { 804 if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer { 805 return "", model.NewAppError("emailToOAuth", "api.user.email_to_oauth.not_available.app_error", nil, "", http.StatusForbidden) 806 } 807 808 user, err := a.GetUserByEmail(email) 809 if err != nil { 810 return "", err 811 } 812 813 if err = a.CheckPasswordAndAllCriteria(user, password, code); err != nil { 814 return "", err 815 } 816 817 stateProps := map[string]string{} 818 stateProps["action"] = model.OAUTH_ACTION_EMAIL_TO_SSO 819 stateProps["email"] = email 820 821 if service == model.USER_AUTH_SERVICE_SAML { 822 return a.GetSiteURL() + "/login/sso/saml?action=" + model.OAUTH_ACTION_EMAIL_TO_SSO + "&email=" + utils.UrlEncode(email), nil 823 } 824 825 authUrl, err := a.GetAuthorizationCode(w, r, service, stateProps, "") 826 if err != nil { 827 return "", err 828 } 829 830 return authUrl, nil 831 } 832 833 func (a *App) SwitchOAuthToEmail(email, password, requesterId string) (string, *model.AppError) { 834 if a.License() != nil && !*a.Config().ServiceSettings.ExperimentalEnableAuthenticationTransfer { 835 return "", model.NewAppError("oauthToEmail", "api.user.oauth_to_email.not_available.app_error", nil, "", http.StatusForbidden) 836 } 837 838 user, err := a.GetUserByEmail(email) 839 if err != nil { 840 return "", err 841 } 842 843 if user.Id != requesterId { 844 return "", model.NewAppError("SwitchOAuthToEmail", "api.user.oauth_to_email.context.app_error", nil, "", http.StatusForbidden) 845 } 846 847 if err := a.UpdatePassword(user, password); err != nil { 848 return "", err 849 } 850 851 T := utils.GetUserTranslations(user.Locale) 852 853 a.Srv().Go(func() { 854 if err := a.SendSignInChangeEmail(user.Email, T("api.templates.signin_change_email.body.method_email"), user.Locale, a.GetSiteURL()); err != nil { 855 mlog.Error("error sending signin change email", mlog.Err(err)) 856 } 857 }) 858 859 if err := a.RevokeAllSessions(requesterId); err != nil { 860 return "", err 861 } 862 863 return "/login?extra=signin_change", nil 864 } 865 866 func generateOAuthStateTokenExtra(email, action, cookie string) string { 867 return email + ":" + action + ":" + cookie 868 }