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