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