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