github.com/ashishbhate/mattermost-server@v5.11.1+incompatible/api4/oauth.go (about) 1 // Copyright (c) 2017 Mattermost, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package api4 5 6 import ( 7 "net/http" 8 "net/url" 9 "path/filepath" 10 "strings" 11 12 "github.com/mattermost/mattermost-server/app" 13 "github.com/mattermost/mattermost-server/mlog" 14 "github.com/mattermost/mattermost-server/model" 15 "github.com/mattermost/mattermost-server/utils" 16 "github.com/mattermost/mattermost-server/utils/fileutils" 17 ) 18 19 func (api *API) InitOAuth() { 20 api.BaseRoutes.OAuthApps.Handle("", api.ApiSessionRequired(createOAuthApp)).Methods("POST") 21 api.BaseRoutes.OAuthApp.Handle("", api.ApiSessionRequired(updateOAuthApp)).Methods("PUT") 22 api.BaseRoutes.OAuthApps.Handle("", api.ApiSessionRequired(getOAuthApps)).Methods("GET") 23 api.BaseRoutes.OAuthApp.Handle("", api.ApiSessionRequired(getOAuthApp)).Methods("GET") 24 api.BaseRoutes.OAuthApp.Handle("/info", api.ApiSessionRequired(getOAuthAppInfo)).Methods("GET") 25 api.BaseRoutes.OAuthApp.Handle("", api.ApiSessionRequired(deleteOAuthApp)).Methods("DELETE") 26 api.BaseRoutes.OAuthApp.Handle("/regen_secret", api.ApiSessionRequired(regenerateOAuthAppSecret)).Methods("POST") 27 28 api.BaseRoutes.User.Handle("/oauth/apps/authorized", api.ApiSessionRequired(getAuthorizedOAuthApps)).Methods("GET") 29 30 // API version independent OAuth 2.0 as a service provider endpoints 31 api.BaseRoutes.Root.Handle("/oauth/authorize", api.ApiHandlerTrustRequester(authorizeOAuthPage)).Methods("GET") 32 api.BaseRoutes.Root.Handle("/oauth/authorize", api.ApiSessionRequired(authorizeOAuthApp)).Methods("POST") 33 api.BaseRoutes.Root.Handle("/oauth/deauthorize", api.ApiSessionRequired(deauthorizeOAuthApp)).Methods("POST") 34 api.BaseRoutes.Root.Handle("/oauth/access_token", api.ApiHandlerTrustRequester(getAccessToken)).Methods("POST") 35 36 // API version independent OAuth as a client endpoints 37 api.BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/complete", api.ApiHandler(completeOAuth)).Methods("GET") 38 api.BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/login", api.ApiHandler(loginWithOAuth)).Methods("GET") 39 api.BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/mobile_login", api.ApiHandler(mobileLoginWithOAuth)).Methods("GET") 40 api.BaseRoutes.Root.Handle("/oauth/{service:[A-Za-z0-9]+}/signup", api.ApiHandler(signupWithOAuth)).Methods("GET") 41 42 // Old endpoints for backwards compatibility, needed to not break SSO for any old setups 43 api.BaseRoutes.Root.Handle("/api/v3/oauth/{service:[A-Za-z0-9]+}/complete", api.ApiHandler(completeOAuth)).Methods("GET") 44 api.BaseRoutes.Root.Handle("/signup/{service:[A-Za-z0-9]+}/complete", api.ApiHandler(completeOAuth)).Methods("GET") 45 api.BaseRoutes.Root.Handle("/login/{service:[A-Za-z0-9]+}/complete", api.ApiHandler(completeOAuth)).Methods("GET") 46 } 47 48 func createOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { 49 oauthApp := model.OAuthAppFromJson(r.Body) 50 51 if oauthApp == nil { 52 c.SetInvalidParam("oauth_app") 53 return 54 } 55 56 if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_OAUTH) { 57 c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH) 58 return 59 } 60 61 if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM) { 62 oauthApp.IsTrusted = false 63 } 64 65 oauthApp.CreatorId = c.App.Session.UserId 66 67 rapp, err := c.App.CreateOAuthApp(oauthApp) 68 if err != nil { 69 c.Err = err 70 return 71 } 72 73 c.LogAudit("client_id=" + rapp.Id) 74 w.WriteHeader(http.StatusCreated) 75 w.Write([]byte(rapp.ToJson())) 76 } 77 78 func updateOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { 79 c.RequireAppId() 80 if c.Err != nil { 81 return 82 } 83 84 if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_OAUTH) { 85 c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH) 86 return 87 } 88 89 oauthApp := model.OAuthAppFromJson(r.Body) 90 if oauthApp == nil { 91 c.SetInvalidParam("oauth_app") 92 return 93 } 94 95 // The app being updated in the payload must be the same one as indicated in the URL. 96 if oauthApp.Id != c.Params.AppId { 97 c.SetInvalidParam("app_id") 98 return 99 } 100 101 c.LogAudit("attempt") 102 103 oldOauthApp, err := c.App.GetOAuthApp(c.Params.AppId) 104 if err != nil { 105 c.Err = err 106 return 107 } 108 109 if c.App.Session.UserId != oldOauthApp.CreatorId && !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { 110 c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) 111 return 112 } 113 114 updatedOauthApp, err := c.App.UpdateOauthApp(oldOauthApp, oauthApp) 115 if err != nil { 116 c.Err = err 117 return 118 } 119 120 c.LogAudit("success") 121 122 w.Write([]byte(updatedOauthApp.ToJson())) 123 } 124 125 func getOAuthApps(c *Context, w http.ResponseWriter, r *http.Request) { 126 if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_OAUTH) { 127 c.Err = model.NewAppError("getOAuthApps", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) 128 return 129 } 130 131 var apps []*model.OAuthApp 132 var err *model.AppError 133 if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { 134 apps, err = c.App.GetOAuthApps(c.Params.Page, c.Params.PerPage) 135 } else if c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_OAUTH) { 136 apps, err = c.App.GetOAuthAppsByCreator(c.App.Session.UserId, c.Params.Page, c.Params.PerPage) 137 } else { 138 c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH) 139 return 140 } 141 142 if err != nil { 143 c.Err = err 144 return 145 } 146 147 w.Write([]byte(model.OAuthAppListToJson(apps))) 148 } 149 150 func getOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { 151 c.RequireAppId() 152 if c.Err != nil { 153 return 154 } 155 156 if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_OAUTH) { 157 c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH) 158 return 159 } 160 161 oauthApp, err := c.App.GetOAuthApp(c.Params.AppId) 162 if err != nil { 163 c.Err = err 164 return 165 } 166 167 if oauthApp.CreatorId != c.App.Session.UserId && !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { 168 c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) 169 return 170 } 171 172 w.Write([]byte(oauthApp.ToJson())) 173 } 174 175 func getOAuthAppInfo(c *Context, w http.ResponseWriter, r *http.Request) { 176 c.RequireAppId() 177 if c.Err != nil { 178 return 179 } 180 181 oauthApp, err := c.App.GetOAuthApp(c.Params.AppId) 182 if err != nil { 183 c.Err = err 184 return 185 } 186 187 oauthApp.Sanitize() 188 w.Write([]byte(oauthApp.ToJson())) 189 } 190 191 func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { 192 c.RequireAppId() 193 if c.Err != nil { 194 return 195 } 196 197 c.LogAudit("attempt") 198 199 if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_OAUTH) { 200 c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH) 201 return 202 } 203 204 oauthApp, err := c.App.GetOAuthApp(c.Params.AppId) 205 if err != nil { 206 c.Err = err 207 return 208 } 209 210 if c.App.Session.UserId != oauthApp.CreatorId && !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { 211 c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) 212 return 213 } 214 215 err = c.App.DeleteOAuthApp(oauthApp.Id) 216 if err != nil { 217 c.Err = err 218 return 219 } 220 221 c.LogAudit("success") 222 ReturnStatusOK(w) 223 } 224 225 func regenerateOAuthAppSecret(c *Context, w http.ResponseWriter, r *http.Request) { 226 c.RequireAppId() 227 if c.Err != nil { 228 return 229 } 230 231 if !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_OAUTH) { 232 c.SetPermissionError(model.PERMISSION_MANAGE_OAUTH) 233 return 234 } 235 236 oauthApp, err := c.App.GetOAuthApp(c.Params.AppId) 237 if err != nil { 238 c.Err = err 239 return 240 } 241 242 if oauthApp.CreatorId != c.App.Session.UserId && !c.App.SessionHasPermissionTo(c.App.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { 243 c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) 244 return 245 } 246 247 oauthApp, err = c.App.RegenerateOAuthAppSecret(oauthApp) 248 if err != nil { 249 c.Err = err 250 return 251 } 252 253 c.LogAudit("success") 254 w.Write([]byte(oauthApp.ToJson())) 255 } 256 257 func getAuthorizedOAuthApps(c *Context, w http.ResponseWriter, r *http.Request) { 258 c.RequireUserId() 259 if c.Err != nil { 260 return 261 } 262 263 if !c.App.SessionHasPermissionToUser(c.App.Session, c.Params.UserId) { 264 c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) 265 return 266 } 267 268 apps, err := c.App.GetAuthorizedAppsForUser(c.Params.UserId, c.Params.Page, c.Params.PerPage) 269 if err != nil { 270 c.Err = err 271 return 272 } 273 274 w.Write([]byte(model.OAuthAppListToJson(apps))) 275 } 276 277 func authorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { 278 authRequest := model.AuthorizeRequestFromJson(r.Body) 279 if authRequest == nil { 280 c.SetInvalidParam("authorize_request") 281 } 282 283 if err := authRequest.IsValid(); err != nil { 284 c.Err = err 285 return 286 } 287 288 if c.App.Session.IsOAuth { 289 c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) 290 c.Err.DetailedError += ", attempted access by oauth app" 291 return 292 } 293 294 c.LogAudit("attempt") 295 296 redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.App.Session.UserId, authRequest) 297 298 if err != nil { 299 c.Err = err 300 return 301 } 302 303 c.LogAudit("") 304 305 w.Write([]byte(model.MapToJson(map[string]string{"redirect": redirectUrl}))) 306 } 307 308 func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { 309 requestData := model.MapFromJson(r.Body) 310 clientId := requestData["client_id"] 311 312 if len(clientId) != 26 { 313 c.SetInvalidParam("client_id") 314 return 315 } 316 317 err := c.App.DeauthorizeOAuthAppForUser(c.App.Session.UserId, clientId) 318 if err != nil { 319 c.Err = err 320 return 321 } 322 323 c.LogAudit("success") 324 ReturnStatusOK(w) 325 } 326 327 func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { 328 if !*c.App.Config().ServiceSettings.EnableOAuthServiceProvider { 329 err := model.NewAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "", http.StatusNotImplemented) 330 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 331 return 332 } 333 334 authRequest := &model.AuthorizeRequest{ 335 ResponseType: r.URL.Query().Get("response_type"), 336 ClientId: r.URL.Query().Get("client_id"), 337 RedirectUri: r.URL.Query().Get("redirect_uri"), 338 Scope: r.URL.Query().Get("scope"), 339 State: r.URL.Query().Get("state"), 340 } 341 342 loginHint := r.URL.Query().Get("login_hint") 343 344 if err := authRequest.IsValid(); err != nil { 345 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 346 return 347 } 348 349 oauthApp, err := c.App.GetOAuthApp(authRequest.ClientId) 350 if err != nil { 351 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 352 return 353 } 354 355 // here we should check if the user is logged in 356 if len(c.App.Session.UserId) == 0 { 357 if loginHint == model.USER_AUTH_SERVICE_SAML { 358 http.Redirect(w, r, c.GetSiteURLHeader()+"/login/sso/saml?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound) 359 } else { 360 http.Redirect(w, r, c.GetSiteURLHeader()+"/login?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound) 361 } 362 return 363 } 364 365 if !oauthApp.IsValidRedirectURL(authRequest.RedirectUri) { 366 err := model.NewAppError("authorizeOAuthPage", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest) 367 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 368 return 369 } 370 371 isAuthorized := false 372 373 if _, err := c.App.GetPreferenceByCategoryAndNameForUser(c.App.Session.UserId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, authRequest.ClientId); err == nil { 374 // when we support scopes we should check if the scopes match 375 isAuthorized = true 376 } 377 378 // Automatically allow if the app is trusted 379 if oauthApp.IsTrusted || isAuthorized { 380 redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.App.Session.UserId, authRequest) 381 382 if err != nil { 383 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 384 return 385 } 386 387 http.Redirect(w, r, redirectUrl, http.StatusFound) 388 return 389 } 390 391 w.Header().Set("X-Frame-Options", "SAMEORIGIN") 392 w.Header().Set("Content-Security-Policy", "frame-ancestors 'self'") 393 w.Header().Set("Content-Type", "text/html; charset=utf-8") 394 w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public") 395 396 staticDir, _ := fileutils.FindDir(model.CLIENT_DIR) 397 http.ServeFile(w, r, filepath.Join(staticDir, "root.html")) 398 } 399 400 func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { 401 r.ParseForm() 402 403 code := r.FormValue("code") 404 refreshToken := r.FormValue("refresh_token") 405 406 grantType := r.FormValue("grant_type") 407 switch grantType { 408 case model.ACCESS_TOKEN_GRANT_TYPE: 409 if len(code) == 0 { 410 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.missing_code.app_error", nil, "", http.StatusBadRequest) 411 return 412 } 413 case model.REFRESH_TOKEN_GRANT_TYPE: 414 if len(refreshToken) == 0 { 415 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.missing_refresh_token.app_error", nil, "", http.StatusBadRequest) 416 return 417 } 418 default: 419 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_grant.app_error", nil, "", http.StatusBadRequest) 420 return 421 } 422 423 clientId := r.FormValue("client_id") 424 if len(clientId) != 26 { 425 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_client_id.app_error", nil, "", http.StatusBadRequest) 426 return 427 } 428 429 secret := r.FormValue("client_secret") 430 if len(secret) == 0 { 431 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_client_secret.app_error", nil, "", http.StatusBadRequest) 432 return 433 } 434 435 redirectUri := r.FormValue("redirect_uri") 436 437 c.LogAudit("attempt") 438 439 accessRsp, err := c.App.GetOAuthAccessTokenForCodeFlow(clientId, grantType, redirectUri, code, secret, refreshToken) 440 if err != nil { 441 c.Err = err 442 return 443 } 444 445 w.Header().Set("Content-Type", "application/json") 446 w.Header().Set("Cache-Control", "no-store") 447 w.Header().Set("Pragma", "no-cache") 448 449 c.LogAudit("success") 450 451 w.Write([]byte(accessRsp.ToJson())) 452 } 453 454 func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { 455 c.RequireService() 456 if c.Err != nil { 457 return 458 } 459 460 service := c.Params.Service 461 462 oauthError := r.URL.Query().Get("error") 463 if oauthError == "access_denied" { 464 utils.RenderWebError(c.App.Config(), w, r, http.StatusTemporaryRedirect, url.Values{ 465 "type": []string{"oauth_access_denied"}, 466 "service": []string{strings.Title(service)}, 467 }, c.App.AsymmetricSigningKey()) 468 return 469 } 470 471 code := r.URL.Query().Get("code") 472 if len(code) == 0 { 473 utils.RenderWebError(c.App.Config(), w, r, http.StatusTemporaryRedirect, url.Values{ 474 "type": []string{"oauth_missing_code"}, 475 "service": []string{strings.Title(service)}, 476 }, c.App.AsymmetricSigningKey()) 477 return 478 } 479 480 state := r.URL.Query().Get("state") 481 482 uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete" 483 484 body, teamId, props, err := c.App.AuthorizeOAuthUser(w, r, service, code, state, uri) 485 486 action := "" 487 if props != nil { 488 action = props["action"] 489 } 490 491 if err != nil { 492 err.Translate(c.App.T) 493 mlog.Error(err.Error()) 494 if action == model.OAUTH_ACTION_MOBILE { 495 w.Write([]byte(err.ToJson())) 496 } else { 497 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 498 } 499 return 500 } 501 502 user, err := c.App.CompleteOAuth(service, body, teamId, props) 503 if err != nil { 504 err.Translate(c.App.T) 505 mlog.Error(err.Error()) 506 if action == model.OAUTH_ACTION_MOBILE { 507 w.Write([]byte(err.ToJson())) 508 } else { 509 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 510 } 511 return 512 } 513 514 var redirectUrl string 515 if action == model.OAUTH_ACTION_EMAIL_TO_SSO { 516 redirectUrl = c.GetSiteURLHeader() + "/login?extra=signin_change" 517 } else if action == model.OAUTH_ACTION_SSO_TO_EMAIL { 518 redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"]) 519 } else { 520 session, err := c.App.DoLogin(w, r, user, "") 521 if err != nil { 522 err.Translate(c.App.T) 523 c.Err = err 524 if action == model.OAUTH_ACTION_MOBILE { 525 w.Write([]byte(err.ToJson())) 526 } 527 return 528 } 529 530 c.App.AttachSessionCookies(w, r, session) 531 532 c.App.Session = *session 533 534 redirectUrl = c.GetSiteURLHeader() 535 } 536 537 if action == model.OAUTH_ACTION_MOBILE { 538 ReturnStatusOK(w) 539 return 540 } 541 542 http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect) 543 } 544 545 func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { 546 c.RequireService() 547 if c.Err != nil { 548 return 549 } 550 551 loginHint := r.URL.Query().Get("login_hint") 552 redirectTo := r.URL.Query().Get("redirect_to") 553 554 teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query()) 555 if err != nil { 556 c.Err = err 557 return 558 } 559 560 authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, c.Params.Service, teamId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint) 561 if err != nil { 562 c.Err = err 563 return 564 } 565 566 http.Redirect(w, r, authUrl, http.StatusFound) 567 } 568 569 func mobileLoginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { 570 c.RequireService() 571 if c.Err != nil { 572 return 573 } 574 575 teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query()) 576 if err != nil { 577 c.Err = err 578 return 579 } 580 581 authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, c.Params.Service, teamId, model.OAUTH_ACTION_MOBILE, "", "") 582 if err != nil { 583 c.Err = err 584 return 585 } 586 587 http.Redirect(w, r, authUrl, http.StatusFound) 588 } 589 590 func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { 591 c.RequireService() 592 if c.Err != nil { 593 return 594 } 595 596 if !*c.App.Config().TeamSettings.EnableUserCreation { 597 utils.RenderWebError(c.App.Config(), w, r, http.StatusBadRequest, url.Values{ 598 "message": []string{utils.T("api.oauth.singup_with_oauth.disabled.app_error")}, 599 }, c.App.AsymmetricSigningKey()) 600 return 601 } 602 603 teamId, err := c.App.GetTeamIdFromQuery(r.URL.Query()) 604 if err != nil { 605 c.Err = err 606 return 607 } 608 609 authUrl, err := c.App.GetOAuthSignupEndpoint(w, r, c.Params.Service, teamId) 610 if err != nil { 611 c.Err = err 612 return 613 } 614 615 http.Redirect(w, r, authUrl, http.StatusFound) 616 }