From be9624e2adce7c95039e62fc4ee22538d7fa2d2f Mon Sep 17 00:00:00 2001 From: Joram Wilander Date: Thu, 20 Apr 2017 09:55:02 -0400 Subject: Implement v4 endpoints for OAuth (#6040) * Implement POST /oauth/apps endpoint for APIv4 * Implement GET /oauth/apps endpoint for APIv4 * Implement GET /oauth/apps/{app_id} and /oauth/apps/{app_id}/info endpoints for APIv4 * Refactor API version independent oauth endpoints * Implement DELETE /oauth/apps/{app_id} endpoint for APIv4 * Implement /oauth/apps/{app_id}/regen_secret endpoint for APIv4 * Implement GET /user/{user_id}/oauth/apps/authorized endpoint for APIv4 * Implement POST /oauth/deauthorize endpoint --- api/apitestlib.go | 1 + api/context.go | 29 +-------- api/oauth.go | 182 +++--------------------------------------------------- api/oauth_test.go | 1 - 4 files changed, 12 insertions(+), 201 deletions(-) (limited to 'api') diff --git a/api/apitestlib.go b/api/apitestlib.go index af14ac431..e857a5080 100644 --- a/api/apitestlib.go +++ b/api/apitestlib.go @@ -75,6 +75,7 @@ func Setup() *TestHelper { InitRouter() wsapi.InitRouter() app.StartServer() + api4.InitApi(false) InitApi() wsapi.InitApi() utils.EnableDebugLogForTest() diff --git a/api/context.go b/api/context.go index 21bbb1e37..282b45c86 100644 --- a/api/context.go +++ b/api/context.go @@ -242,7 +242,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if c.Err.StatusCode == http.StatusUnauthorized { http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect) } else { - RenderWebError(c.Err, w, r) + utils.RenderWebError(c.Err, w, r) } } @@ -421,31 +421,6 @@ func IsApiCall(r *http.Request) bool { return strings.Index(r.URL.Path, "/api/") == 0 } -func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) { - T, _ := utils.GetTranslationsAndLocale(w, r) - - title := T("api.templates.error.title", map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]}) - message := err.Message - details := err.DetailedError - link := "/" - linkMessage := T("api.templates.error.link") - - status := http.StatusTemporaryRedirect - if err.StatusCode != http.StatusInternalServerError { - status = err.StatusCode - } - - http.Redirect( - w, - r, - "/error?title="+url.QueryEscape(title)+ - "&message="+url.QueryEscape(message)+ - "&details="+url.QueryEscape(details)+ - "&link="+url.QueryEscape(link)+ - "&linkmessage="+url.QueryEscape(linkMessage), - status) -} - func Handle404(w http.ResponseWriter, r *http.Request) { err := model.NewLocAppError("Handle404", "api.context.404.app_error", nil, "") err.Translate(utils.T) @@ -458,7 +433,7 @@ func Handle404(w http.ResponseWriter, r *http.Request) { err.DetailedError = "There doesn't appear to be an api call for the url='" + r.URL.Path + "'. Typo? are you missing a team_id or user_id as part of the url?" w.Write([]byte(err.ToJson())) } else { - RenderWebError(err, w, r) + utils.RenderWebError(err, w, r) } } diff --git a/api/oauth.go b/api/oauth.go index fa076c56e..6ff04d644 100644 --- a/api/oauth.go +++ b/api/oauth.go @@ -5,8 +5,6 @@ package api import ( "net/http" - "net/url" - "strings" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" @@ -26,17 +24,8 @@ func InitOAuth() { BaseRoutes.OAuth.Handle("/delete", ApiUserRequired(deleteOAuthApp)).Methods("POST") BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/deauthorize", ApiUserRequired(deauthorizeOAuthApp)).Methods("POST") BaseRoutes.OAuth.Handle("/{id:[A-Za-z0-9]+}/regen_secret", ApiUserRequired(regenerateOAuthSecret)).Methods("POST") - BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET") BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/login", AppHandlerIndependent(loginWithOAuth)).Methods("GET") BaseRoutes.OAuth.Handle("/{service:[A-Za-z0-9]+}/signup", AppHandlerIndependent(signupWithOAuth)).Methods("GET") - - BaseRoutes.Root.Handle("/oauth/authorize", AppHandlerTrustRequester(authorizeOAuth)).Methods("GET") - BaseRoutes.Root.Handle("/oauth/access_token", ApiAppHandlerTrustRequester(getAccessToken)).Methods("POST") - - // Handle all the old routes, to be later removed - BaseRoutes.Root.Handle("/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET") - BaseRoutes.Root.Handle("/signup/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET") - BaseRoutes.Root.Handle("/login/{service:[A-Za-z0-9]+}/complete", AppHandlerIndependent(completeOAuth)).Methods("GET") } func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { @@ -126,7 +115,15 @@ func allowOAuth(c *Context, w http.ResponseWriter, r *http.Request) { c.LogAudit("attempt") - redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, responseType, clientId, redirectUri, scope, state) + authRequest := &model.AuthorizeRequest{ + ResponseType: responseType, + ClientId: clientId, + RedirectUri: redirectUri, + Scope: scope, + State: state, + } + + redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, authRequest) if err != nil { c.Err = err @@ -148,167 +145,6 @@ func getAuthorizedApps(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(model.OAuthAppListToJson(apps))) } -func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { - params := mux.Vars(r) - service := params["service"] - - code := r.URL.Query().Get("code") - if len(code) == 0 { - c.Err = model.NewLocAppError("completeOAuth", "api.oauth.complete_oauth.missing_code.app_error", map[string]interface{}{"service": strings.Title(service)}, "URL: "+r.URL.String()) - return - } - - state := r.URL.Query().Get("state") - - uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete" - - body, teamId, props, err := app.AuthorizeOAuthUser(service, code, state, uri) - if err != nil { - c.Err = err - return - } - - user, err := app.CompleteOAuth(service, body, teamId, props) - if err != nil { - c.Err = err - return - } - - action := props["action"] - - var redirectUrl string - if action == model.OAUTH_ACTION_EMAIL_TO_SSO { - redirectUrl = c.GetSiteURLHeader() + "/login?extra=signin_change" - } else if action == model.OAUTH_ACTION_SSO_TO_EMAIL { - - redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"]) - } else { - doLogin(c, w, r, user, "") - if c.Err != nil { - return - } - - redirectUrl = c.GetSiteURLHeader() - } - - http.Redirect(w, r, redirectUrl, http.StatusTemporaryRedirect) -} - -func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("authorizeOAuth", "api.oauth.authorize_oauth.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - responseType := r.URL.Query().Get("response_type") - clientId := r.URL.Query().Get("client_id") - redirect := r.URL.Query().Get("redirect_uri") - scope := r.URL.Query().Get("scope") - state := r.URL.Query().Get("state") - - if len(scope) == 0 { - scope = model.DEFAULT_SCOPE - } - - if len(responseType) == 0 || len(clientId) == 0 || len(redirect) == 0 { - c.Err = model.NewLocAppError("authorizeOAuth", "api.oauth.authorize_oauth.missing.app_error", nil, "") - return - } - - var oauthApp *model.OAuthApp - if result := <-app.Srv.Store.OAuth().GetApp(clientId); result.Err != nil { - c.Err = result.Err - return - } else { - oauthApp = result.Data.(*model.OAuthApp) - } - - // here we should check if the user is logged in - if len(c.Session.UserId) == 0 { - http.Redirect(w, r, c.GetSiteURLHeader()+"/login?redirect_to="+url.QueryEscape(r.RequestURI), http.StatusFound) - return - } - - isAuthorized := false - if result := <-app.Srv.Store.Preference().Get(c.Session.UserId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, clientId); result.Err == nil { - // when we support scopes we should check if the scopes match - isAuthorized = true - } - - // Automatically allow if the app is trusted - if oauthApp.IsTrusted || isAuthorized { - redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, model.AUTHCODE_RESPONSE_TYPE, clientId, redirect, scope, state) - - if err != nil { - c.Err = err - return - } - - http.Redirect(w, r, redirectUrl, http.StatusFound) - return - } - - w.Header().Set("Content-Type", "text/html") - - w.Header().Set("Cache-Control", "no-cache, max-age=31556926, public") - http.ServeFile(w, r, utils.FindDir(model.CLIENT_DIR)+"root.html") -} - -func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { - r.ParseForm() - - code := r.FormValue("code") - refreshToken := r.FormValue("refresh_token") - - grantType := r.FormValue("grant_type") - switch grantType { - case model.ACCESS_TOKEN_GRANT_TYPE: - if len(code) == 0 { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.missing_code.app_error", nil, "") - return - } - case model.REFRESH_TOKEN_GRANT_TYPE: - if len(refreshToken) == 0 { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.missing_refresh_token.app_error", nil, "") - return - } - default: - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.bad_grant.app_error", nil, "") - return - } - - clientId := r.FormValue("client_id") - if len(clientId) != 26 { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.bad_client_id.app_error", nil, "") - return - } - - secret := r.FormValue("client_secret") - if len(secret) == 0 { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.bad_client_secret.app_error", nil, "") - return - } - - redirectUri := r.FormValue("redirect_uri") - - c.LogAudit("attempt") - - accessRsp, err := app.GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret, refreshToken) - if err != nil { - c.Err = err - return - } - - w.Header().Set("Content-Type", "application/json") - w.Header().Set("Cache-Control", "no-store") - w.Header().Set("Pragma", "no-cache") - - c.LogAudit("success") - - w.Write([]byte(accessRsp.ToJson())) -} - func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) service := params["service"] diff --git a/api/oauth_test.go b/api/oauth_test.go index 3dcaa0ddf..9e5102b97 100644 --- a/api/oauth_test.go +++ b/api/oauth_test.go @@ -28,7 +28,6 @@ func TestOAuthRegisterApp(t *testing.T) { if _, err := Client.RegisterApp(oauthApp); err == nil { t.Fatal("should have failed - oauth providing turned off") } - } utils.Cfg.ServiceSettings.EnableOAuthServiceProvider = true -- cgit v1.2.3-1-g7c22