github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/web/oauth.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package web 5 6 import ( 7 "net/http" 8 "net/url" 9 "path/filepath" 10 "strings" 11 12 "github.com/vnforks/kid/v5/app" 13 "github.com/vnforks/kid/v5/audit" 14 "github.com/vnforks/kid/v5/mlog" 15 "github.com/vnforks/kid/v5/model" 16 "github.com/vnforks/kid/v5/utils" 17 "github.com/vnforks/kid/v5/utils/fileutils" 18 ) 19 20 func (w *Web) InitOAuth() { 21 // API version independent OAuth 2.0 as a service provider endpoints 22 w.MainRouter.Handle("/oauth/authorize", w.ApiHandlerTrustRequester(authorizeOAuthPage)).Methods("GET") 23 w.MainRouter.Handle("/oauth/authorize", w.ApiSessionRequired(authorizeOAuthApp)).Methods("POST") 24 w.MainRouter.Handle("/oauth/deauthorize", w.ApiSessionRequired(deauthorizeOAuthApp)).Methods("POST") 25 w.MainRouter.Handle("/oauth/access_token", w.ApiHandlerTrustRequester(getAccessToken)).Methods("POST") 26 27 // API version independent OAuth as a client endpoints 28 w.MainRouter.Handle("/oauth/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET") 29 w.MainRouter.Handle("/oauth/{service:[A-Za-z0-9]+}/login", w.ApiHandler(loginWithOAuth)).Methods("GET") 30 w.MainRouter.Handle("/oauth/{service:[A-Za-z0-9]+}/mobile_login", w.ApiHandler(mobileLoginWithOAuth)).Methods("GET") 31 w.MainRouter.Handle("/oauth/{service:[A-Za-z0-9]+}/signup", w.ApiHandler(signupWithOAuth)).Methods("GET") 32 33 // Old endpoints for backwards compatibility, needed to not break SSO for any old setups 34 w.MainRouter.Handle("/api/v3/oauth/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET") 35 w.MainRouter.Handle("/signup/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET") 36 w.MainRouter.Handle("/login/{service:[A-Za-z0-9]+}/complete", w.ApiHandler(completeOAuth)).Methods("GET") 37 w.MainRouter.Handle("/api/v4/oauth_test", w.ApiSessionRequired(testHandler)).Methods("GET") 38 } 39 40 func testHandler(c *Context, w http.ResponseWriter, r *http.Request) { 41 ReturnStatusOK(w) 42 } 43 44 func authorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { 45 authRequest := model.AuthorizeRequestFromJson(r.Body) 46 if authRequest == nil { 47 c.SetInvalidParam("authorize_request") 48 } 49 50 if err := authRequest.IsValid(); err != nil { 51 c.Err = err 52 return 53 } 54 55 if c.App.Session().IsOAuth { 56 c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS) 57 c.Err.DetailedError += ", attempted access by oauth app" 58 return 59 } 60 61 auditRec := c.MakeAuditRecord("authorizeOAuthApp", audit.Fail) 62 defer c.LogAuditRec(auditRec) 63 c.LogAudit("attempt") 64 65 redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.App.Session().UserId, authRequest) 66 67 if err != nil { 68 c.Err = err 69 return 70 } 71 72 auditRec.Success() 73 c.LogAudit("") 74 75 w.Write([]byte(model.MapToJson(map[string]string{"redirect": redirectUrl}))) 76 } 77 78 func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { 79 requestData := model.MapFromJson(r.Body) 80 clientId := requestData["client_id"] 81 82 if len(clientId) != 26 { 83 c.SetInvalidParam("client_id") 84 return 85 } 86 87 auditRec := c.MakeAuditRecord("deauthorizeOAuthApp", audit.Fail) 88 defer c.LogAuditRec(auditRec) 89 90 err := c.App.DeauthorizeOAuthAppForUser(c.App.Session().UserId, clientId) 91 if err != nil { 92 c.Err = err 93 return 94 } 95 96 auditRec.Success() 97 c.LogAudit("success") 98 99 ReturnStatusOK(w) 100 } 101 102 func authorizeOAuthPage(c *Context, w http.ResponseWriter, r *http.Request) { 103 if !*c.App.Config().ServiceSettings.EnableOAuthServiceProvider { 104 err := model.NewAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "", http.StatusNotImplemented) 105 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 106 return 107 } 108 109 authRequest := &model.AuthorizeRequest{ 110 ResponseType: r.URL.Query().Get("response_type"), 111 ClientId: r.URL.Query().Get("client_id"), 112 RedirectUri: r.URL.Query().Get("redirect_uri"), 113 Scope: r.URL.Query().Get("scope"), 114 State: r.URL.Query().Get("state"), 115 } 116 117 loginHint := r.URL.Query().Get("login_hint") 118 119 if err := authRequest.IsValid(); err != nil { 120 utils.RenderWebError(c.App.Config(), w, r, err.StatusCode, 121 url.Values{ 122 "type": []string{"oauth_invalid_param"}, 123 "message": []string{err.Message}, 124 }, c.App.AsymmetricSigningKey()) 125 return 126 } 127 128 oauthApp, err := c.App.GetOAuthApp(authRequest.ClientId) 129 if err != nil { 130 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 131 return 132 } 133 134 // here we should check if the user is logged in 135 if len(c.App.Session().UserId) == 0 { 136 if loginHint == model.USER_AUTH_SERVICE_SAML { 137 http.Redirect(w, r, c.GetSiteURLHeader()+"/login/sso/saml?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound) 138 } else { 139 http.Redirect(w, r, c.GetSiteURLHeader()+"/login?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound) 140 } 141 return 142 } 143 144 if !oauthApp.IsValidRedirectURL(authRequest.RedirectUri) { 145 err := model.NewAppError("authorizeOAuthPage", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest) 146 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 147 return 148 } 149 150 isAuthorized := false 151 152 if _, err := c.App.GetPreferenceByCategoryAndNameForUser(c.App.Session().UserId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, authRequest.ClientId); err == nil { 153 // when we support scopes we should check if the scopes match 154 isAuthorized = true 155 } 156 157 // Automatically allow if the app is trusted 158 if oauthApp.IsTrusted || isAuthorized { 159 redirectUrl, err := c.App.AllowOAuthAppAccessToUser(c.App.Session().UserId, authRequest) 160 161 if err != nil { 162 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 163 return 164 } 165 166 http.Redirect(w, r, redirectUrl, http.StatusFound) 167 return 168 } 169 170 w.Header().Set("X-Frame-Options", "SAMEORIGIN") 171 w.Header().Set("Content-Security-Policy", "frame-ancestors 'self'") 172 w.Header().Set("Content-Type", "text/html; charset=utf-8") 173 w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public") 174 175 staticDir, _ := fileutils.FindDir(model.CLIENT_DIR) 176 http.ServeFile(w, r, filepath.Join(staticDir, "root.html")) 177 } 178 179 func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { 180 r.ParseForm() 181 182 code := r.FormValue("code") 183 refreshToken := r.FormValue("refresh_token") 184 185 grantType := r.FormValue("grant_type") 186 switch grantType { 187 case model.ACCESS_TOKEN_GRANT_TYPE: 188 if len(code) == 0 { 189 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.missing_code.app_error", nil, "", http.StatusBadRequest) 190 return 191 } 192 case model.REFRESH_TOKEN_GRANT_TYPE: 193 if len(refreshToken) == 0 { 194 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.missing_refresh_token.app_error", nil, "", http.StatusBadRequest) 195 return 196 } 197 default: 198 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_grant.app_error", nil, "", http.StatusBadRequest) 199 return 200 } 201 202 clientId := r.FormValue("client_id") 203 if len(clientId) != 26 { 204 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_client_id.app_error", nil, "", http.StatusBadRequest) 205 return 206 } 207 208 secret := r.FormValue("client_secret") 209 if len(secret) == 0 { 210 c.Err = model.NewAppError("getAccessToken", "api.oauth.get_access_token.bad_client_secret.app_error", nil, "", http.StatusBadRequest) 211 return 212 } 213 214 redirectUri := r.FormValue("redirect_uri") 215 216 auditRec := c.MakeAuditRecord("getAccessToken", audit.Fail) 217 defer c.LogAuditRec(auditRec) 218 auditRec.AddMeta("grant_type", grantType) 219 auditRec.AddMeta("client_id", clientId) 220 c.LogAudit("attempt") 221 222 accessRsp, err := c.App.GetOAuthAccessTokenForCodeFlow(clientId, grantType, redirectUri, code, secret, refreshToken) 223 if err != nil { 224 c.Err = err 225 return 226 } 227 228 w.Header().Set("Content-Type", "application/json") 229 w.Header().Set("Cache-Control", "no-store") 230 w.Header().Set("Pragma", "no-cache") 231 232 auditRec.Success() 233 c.LogAudit("success") 234 235 w.Write([]byte(accessRsp.ToJson())) 236 } 237 238 func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { 239 c.RequireService() 240 if c.Err != nil { 241 return 242 } 243 244 service := c.Params.Service 245 246 oauthError := r.URL.Query().Get("error") 247 if oauthError == "access_denied" { 248 utils.RenderWebError(c.App.Config(), w, r, http.StatusTemporaryRedirect, url.Values{ 249 "type": []string{"oauth_access_denied"}, 250 "service": []string{strings.Title(service)}, 251 }, c.App.AsymmetricSigningKey()) 252 return 253 } 254 255 code := r.URL.Query().Get("code") 256 if len(code) == 0 { 257 utils.RenderWebError(c.App.Config(), w, r, http.StatusTemporaryRedirect, url.Values{ 258 "type": []string{"oauth_missing_code"}, 259 "service": []string{strings.Title(service)}, 260 }, c.App.AsymmetricSigningKey()) 261 return 262 } 263 264 state := r.URL.Query().Get("state") 265 266 uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete" 267 268 body, branchId, props, err := c.App.AuthorizeOAuthUser(w, r, service, code, state, uri) 269 270 action := "" 271 if props != nil { 272 action = props["action"] 273 } 274 275 if err != nil { 276 err.Translate(c.App.T) 277 mlog.Error(err.Error()) 278 if action == model.OAUTH_ACTION_MOBILE { 279 w.Write([]byte(err.ToJson())) 280 } else { 281 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 282 } 283 return 284 } 285 286 user, err := c.App.CompleteOAuth(service, body, branchId, props) 287 if err != nil { 288 err.Translate(c.App.T) 289 mlog.Error(err.Error()) 290 if action == model.OAUTH_ACTION_MOBILE { 291 w.Write([]byte(err.ToJson())) 292 } else { 293 utils.RenderWebAppError(c.App.Config(), w, r, err, c.App.AsymmetricSigningKey()) 294 } 295 return 296 } 297 298 var redirectUrl string 299 if action == model.OAUTH_ACTION_EMAIL_TO_SSO { 300 redirectUrl = c.GetSiteURLHeader() + "/login?extra=signin_change" 301 } else if action == model.OAUTH_ACTION_SSO_TO_EMAIL { 302 redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"]) 303 } else { 304 err = c.App.DoLogin(w, r, user, "") 305 if err != nil { 306 err.Translate(c.App.T) 307 c.Err = err 308 if action == model.OAUTH_ACTION_MOBILE { 309 w.Write([]byte(err.ToJson())) 310 } 311 return 312 } 313 314 c.App.AttachSessionCookies(w, r) 315 316 if _, ok := props["redirect_to"]; ok { 317 redirectUrl = props["redirect_to"] 318 } else { 319 redirectUrl = c.GetSiteURLHeader() 320 } 321 } 322 323 if action == model.OAUTH_ACTION_MOBILE { 324 return 325 } 326 327 w.Header().Set("Content-Type", "text/html; charset=utf-8") 328 http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect) 329 } 330 331 func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { 332 c.RequireService() 333 if c.Err != nil { 334 return 335 } 336 337 loginHint := r.URL.Query().Get("login_hint") 338 redirectTo := r.URL.Query().Get("redirect_to") 339 340 branchId, err := c.App.GetBranchIdFromQuery(r.URL.Query()) 341 if err != nil { 342 c.Err = err 343 return 344 } 345 346 authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, c.Params.Service, branchId, model.OAUTH_ACTION_LOGIN, redirectTo, loginHint) 347 if err != nil { 348 c.Err = err 349 return 350 } 351 352 http.Redirect(w, r, authUrl, http.StatusFound) 353 } 354 355 func mobileLoginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { 356 c.RequireService() 357 if c.Err != nil { 358 return 359 } 360 361 branchId, err := c.App.GetBranchIdFromQuery(r.URL.Query()) 362 if err != nil { 363 c.Err = err 364 return 365 } 366 367 authUrl, err := c.App.GetOAuthLoginEndpoint(w, r, c.Params.Service, branchId, model.OAUTH_ACTION_MOBILE, "", "") 368 if err != nil { 369 c.Err = err 370 return 371 } 372 373 http.Redirect(w, r, authUrl, http.StatusFound) 374 } 375 376 func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { 377 c.RequireService() 378 if c.Err != nil { 379 return 380 } 381 382 if !*c.App.Config().BranchSettings.EnableUserCreation { 383 utils.RenderWebError(c.App.Config(), w, r, http.StatusBadRequest, url.Values{ 384 "message": []string{utils.T("api.oauth.singup_with_oauth.disabled.app_error")}, 385 }, c.App.AsymmetricSigningKey()) 386 return 387 } 388 389 branchId, err := c.App.GetBranchIdFromQuery(r.URL.Query()) 390 if err != nil { 391 c.Err = err 392 return 393 } 394 395 authUrl, err := c.App.GetOAuthSignupEndpoint(w, r, c.Params.Service, branchId) 396 if err != nil { 397 c.Err = err 398 return 399 } 400 401 http.Redirect(w, r, authUrl, http.StatusFound) 402 }