diff options
author | Joram Wilander <jwawilander@gmail.com> | 2017-04-12 16:29:42 -0400 |
---|---|---|
committer | Harrison Healey <harrisonmhealey@gmail.com> | 2017-04-12 16:29:42 -0400 |
commit | 8b8aa2ca3c803b26fb4a1ba5f249111739376494 (patch) | |
tree | 9fa13e99e60a9effc12bad964b13a3c23fab795e /api | |
parent | 03502cf73b8513a40877b1ac5726523974661d4d (diff) | |
download | chat-8b8aa2ca3c803b26fb4a1ba5f249111739376494.tar.gz chat-8b8aa2ca3c803b26fb4a1ba5f249111739376494.tar.bz2 chat-8b8aa2ca3c803b26fb4a1ba5f249111739376494.zip |
Refactor OAuth 2.0 code into app layer (#6037)
Diffstat (limited to 'api')
-rw-r--r-- | api/oauth.go | 676 | ||||
-rw-r--r-- | api/oauth_test.go | 2 | ||||
-rw-r--r-- | api/user.go | 50 |
3 files changed, 104 insertions, 624 deletions
diff --git a/api/oauth.go b/api/oauth.go index 8bd4a9f7b..fa076c56e 100644 --- a/api/oauth.go +++ b/api/oauth.go @@ -4,20 +4,14 @@ package api import ( - "fmt" - "io" - "io/ioutil" "net/http" "net/url" - "strconv" "strings" l4g "github.com/alecthomas/log4go" "github.com/gorilla/mux" "github.com/mattermost/platform/app" - "github.com/mattermost/platform/einterfaces" "github.com/mattermost/platform/model" - "github.com/mattermost/platform/store" "github.com/mattermost/platform/utils" ) @@ -46,15 +40,8 @@ func InitOAuth() { } func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("registerOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewLocAppError("registerOAuthApp", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + c.Err = model.NewAppError("registerOAuthApp", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) return } @@ -65,72 +52,50 @@ func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { return } - secret := model.NewId() - - oauthApp.ClientSecret = secret oauthApp.CreatorId = c.Session.UserId - if result := <-app.Srv.Store.OAuth().SaveApp(oauthApp); result.Err != nil { - c.Err = result.Err - return - } else { - oauthApp = result.Data.(*model.OAuthApp) - - c.LogAudit("client_id=" + oauthApp.Id) + rapp, err := app.CreateOAuthApp(oauthApp) - w.Write([]byte(oauthApp.ToJson())) + if err != nil { + c.Err = err return } + c.LogAudit("client_id=" + rapp.Id) + w.Write([]byte(rapp.ToJson())) } func getOAuthApps(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("getOAuthAppsByUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewLocAppError("getOAuthApps", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + c.Err = model.NewAppError("getOAuthApps", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) return } - var ochan store.StoreChannel + var apps []*model.OAuthApp + var err *model.AppError if app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - ochan = app.Srv.Store.OAuth().GetApps() + apps, err = app.GetOAuthApps(0, 100000) } else { - c.Err = nil - ochan = app.Srv.Store.OAuth().GetAppByUser(c.Session.UserId) + apps, err = app.GetOAuthAppsByCreator(c.Session.UserId, 0, 100000) } - if result := <-ochan; result.Err != nil { - c.Err = result.Err + if err != nil { + c.Err = err return - } else { - apps := result.Data.([]*model.OAuthApp) - w.Write([]byte(model.OAuthAppListToJson(apps))) } + + w.Write([]byte(model.OAuthAppListToJson(apps))) } func getOAuthAppInfo(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("getOAuthAppInfo", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - params := mux.Vars(r) - clientId := params["client_id"] - var oauthApp *model.OAuthApp - if result := <-app.Srv.Store.OAuth().GetApp(clientId); result.Err != nil { - c.Err = model.NewLocAppError("getOAuthAppInfo", "api.oauth.allow_oauth.database.app_error", nil, "") + oauthApp, err := app.GetOAuthApp(clientId) + + if err != nil { + c.Err = err return - } else { - oauthApp = result.Data.(*model.OAuthApp) } oauthApp.Sanitize() @@ -138,123 +103,49 @@ func getOAuthAppInfo(c *Context, w http.ResponseWriter, r *http.Request) { } func allowOAuth(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - c.LogAudit("attempt") - - responseData := map[string]string{} - responseType := r.URL.Query().Get("response_type") if len(responseType) == 0 { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.bad_response.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest + c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_response.app_error", nil, "", http.StatusBadRequest) return } clientId := r.URL.Query().Get("client_id") if len(clientId) != 26 { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.bad_client.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest + c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_client.app_error", nil, "", http.StatusBadRequest) return } redirectUri := r.URL.Query().Get("redirect_uri") if len(redirectUri) == 0 { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.bad_redirect.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest + c.Err = model.NewAppError("allowOAuth", "api.oauth.allow_oauth.bad_redirect.app_error", nil, "", http.StatusBadRequest) return } scope := r.URL.Query().Get("scope") state := r.URL.Query().Get("state") - if len(scope) == 0 { - scope = model.DEFAULT_SCOPE - } - - var oauthApp *model.OAuthApp - if result := <-app.Srv.Store.OAuth().GetApp(clientId); result.Err != nil { - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.database.app_error", nil, "") - return - } else { - oauthApp = result.Data.(*model.OAuthApp) - } - - if !oauthApp.IsValidRedirectURL(redirectUri) { - c.LogAudit("fail - redirect_uri did not match registered callback") - c.Err = model.NewLocAppError("allowOAuth", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "") - c.Err.StatusCode = http.StatusBadRequest - return - } - - if responseType != model.AUTHCODE_RESPONSE_TYPE { - responseData["redirect"] = redirectUri + "?error=unsupported_response_type&state=" + state - w.Write([]byte(model.MapToJson(responseData))) - return - } - - authData := &model.AuthData{UserId: c.Session.UserId, ClientId: clientId, CreateAt: model.GetMillis(), RedirectUri: redirectUri, State: state, Scope: scope} - authData.Code = model.HashPassword(fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, c.Session.UserId)) - - // this saves the OAuth2 app as authorized - authorizedApp := model.Preference{ - UserId: c.Session.UserId, - Category: model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, - Name: clientId, - Value: scope, - } + c.LogAudit("attempt") - if result := <-app.Srv.Store.Preference().Save(&model.Preferences{authorizedApp}); result.Err != nil { - responseData["redirect"] = redirectUri + "?error=server_error&state=" + state - w.Write([]byte(model.MapToJson(responseData))) - return - } + redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, responseType, clientId, redirectUri, scope, state) - if result := <-app.Srv.Store.OAuth().SaveAuthData(authData); result.Err != nil { - responseData["redirect"] = redirectUri + "?error=server_error&state=" + state - w.Write([]byte(model.MapToJson(responseData))) + if err != nil { + c.Err = err return } - c.LogAudit("success") + c.LogAudit("") - responseData["redirect"] = redirectUri + "?code=" + url.QueryEscape(authData.Code) + "&state=" + url.QueryEscape(authData.State) - w.Write([]byte(model.MapToJson(responseData))) + w.Write([]byte(model.MapToJson(map[string]string{"redirect": redirectUrl}))) } func getAuthorizedApps(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("getAuthorizedApps", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - ochan := app.Srv.Store.OAuth().GetAuthorizedApps(c.Session.UserId) - if result := <-ochan; result.Err != nil { - c.Err = result.Err + apps, err := app.GetAuthorizedAppsForUser(c.Session.UserId, 0, 10000) + if err != nil { + c.Err = err return - } else { - apps := result.Data.([]*model.OAuthApp) - for k, a := range apps { - a.Sanitize() - apps[k] = a - } - - w.Write([]byte(model.OAuthAppListToJson(apps))) } -} -func GetAuthData(code string) *model.AuthData { - if result := <-app.Srv.Store.OAuth().GetAuthData(code); result.Err != nil { - l4g.Error(utils.T("api.oauth.get_auth_data.find.error"), code) - return nil - } else { - return result.Data.(*model.AuthData) - } + w.Write([]byte(model.OAuthAppListToJson(apps))) } func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { @@ -271,60 +162,36 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { uri := c.GetSiteURLHeader() + "/signup/" + service + "/complete" - if body, teamId, props, err := app.AuthorizeOAuthUser(service, code, state, uri); err != nil { + 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 { - defer func() { - ioutil.ReadAll(body) - body.Close() - }() - - action := props["action"] - switch action { - case model.OAUTH_ACTION_SIGNUP: - if user, err := app.CreateOAuthUser(service, body, teamId); err != nil { - c.Err = err - } else { - doLogin(c, w, r, user, "") - } - if c.Err == nil { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect) - } - break - case model.OAUTH_ACTION_LOGIN: - user := LoginByOAuth(c, w, r, service, body) - if len(teamId) > 0 { - c.Err = app.AddUserToTeamByTeamId(teamId, user) - } - if c.Err == nil { - if val, ok := props["redirect_to"]; ok { - http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusTemporaryRedirect) - return - } - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect) - } - break - case model.OAUTH_ACTION_EMAIL_TO_SSO: - CompleteSwitchWithOAuth(c, w, r, service, body, props["email"]) - if c.Err == nil { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host+"/login?extra=signin_change", http.StatusTemporaryRedirect) - } - break - case model.OAUTH_ACTION_SSO_TO_EMAIL: - LoginByOAuth(c, w, r, service, body) - if c.Err == nil { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host+"/claim?email="+url.QueryEscape(props["email"]), http.StatusTemporaryRedirect) - } - break - default: - LoginByOAuth(c, w, r, service, body) - if c.Err == nil { - http.Redirect(w, r, app.GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect) - } - break + 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) { @@ -371,42 +238,15 @@ func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { // Automatically allow if the app is trusted if oauthApp.IsTrusted || isAuthorized { - closeBody := func(r *http.Response) { - if r.Body != nil { - ioutil.ReadAll(r.Body) - r.Body.Close() - } - } - - doAllow := func() (*http.Response, *model.AppError) { - HttpClient := &http.Client{} - url := c.GetSiteURLHeader() + "/api/v3/oauth/allow?response_type=" + model.AUTHCODE_RESPONSE_TYPE + "&client_id=" + clientId + "&redirect_uri=" + url.QueryEscape(redirect) + "&scope=" + scope + "&state=" + url.QueryEscape(state) - rq, _ := http.NewRequest("GET", url, strings.NewReader("")) - - rq.Header.Set(model.HEADER_AUTH, model.HEADER_BEARER+" "+c.Session.Token) - - if rp, err := HttpClient.Do(rq); err != nil { - return nil, model.NewLocAppError(url, "model.client.connecting.app_error", nil, err.Error()) - } else if rp.StatusCode == 304 { - return rp, nil - } else if rp.StatusCode >= 300 { - defer closeBody(rp) - return rp, model.AppErrorFromJson(rp.Body) - } else { - return rp, nil - } - } + redirectUrl, err := app.AllowOAuthAppAccessToUser(c.Session.UserId, model.AUTHCODE_RESPONSE_TYPE, clientId, redirect, scope, state) - if result, err := doAllow(); err != nil { + if err != nil { c.Err = err return - } else { - //defer closeBody(result) - data := model.MapFromJson(result.Body) - redirectTo := data["redirect"] - http.Redirect(w, r, redirectTo, http.StatusFound) - return } + + http.Redirect(w, r, redirectUrl, http.StatusFound) + return } w.Header().Set("Content-Type", "text/html") @@ -416,14 +256,6 @@ func authorizeOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - - c.LogAudit("attempt") - r.ParseForm() code := r.FormValue("code") @@ -458,140 +290,21 @@ func getAccessToken(c *Context, w http.ResponseWriter, r *http.Request) { return } - var oauthApp *model.OAuthApp - achan := app.Srv.Store.OAuth().GetApp(clientId) - if result := <-achan; result.Err != nil { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "") - return - } else { - oauthApp = result.Data.(*model.OAuthApp) - } - - if oauthApp.ClientSecret != secret { - c.LogAudit("fail - invalid client credentials") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "") - return - } - - var user *model.User - var accessData *model.AccessData - var accessRsp *model.AccessResponse - if grantType == model.ACCESS_TOKEN_GRANT_TYPE { - redirectUri := r.FormValue("redirect_uri") - authData := GetAuthData(code) - - if authData == nil { - c.LogAudit("fail - invalid auth code") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "") - return - } - - if authData.IsExpired() { - <-app.Srv.Store.OAuth().RemoveAuthData(authData.Code) - c.LogAudit("fail - auth code expired") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "") - return - } - - if authData.RedirectUri != redirectUri { - c.LogAudit("fail - redirect uri provided did not match previous redirect uri") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "") - return - } - - if !model.ComparePassword(code, fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) { - c.LogAudit("fail - auth code is invalid") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "") - return - } - - uchan := app.Srv.Store.User().Get(authData.UserId) - if result := <-uchan; result.Err != nil { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "") - return - } else { - user = result.Data.(*model.User) - } - - tchan := app.Srv.Store.OAuth().GetPreviousAccessData(user.Id, clientId) - if result := <-tchan; result.Err != nil { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal.app_error", nil, "") - return - } else if result.Data != nil { - accessData := result.Data.(*model.AccessData) - if accessData.IsExpired() { - if access, err := newSessionUpdateToken(oauthApp.Name, accessData, user); err != nil { - c.Err = err - return - } else { - accessRsp = access - } - } else { - //return the same token and no need to create a new session - accessRsp = &model.AccessResponse{ - AccessToken: accessData.Token, - TokenType: model.ACCESS_TOKEN_TYPE, - ExpiresIn: int32((accessData.ExpiresAt - model.GetMillis()) / 1000), - } - } - } else { - // create a new session and return new access token - var session *model.Session - if result, err := newSession(oauthApp.Name, user); err != nil { - c.Err = err - return - } else { - session = result - } - - accessData = &model.AccessData{ClientId: clientId, UserId: user.Id, Token: session.Token, RefreshToken: model.NewId(), RedirectUri: redirectUri, ExpiresAt: session.ExpiresAt} - - if result := <-app.Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil { - l4g.Error(result.Err) - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal_saving.app_error", nil, "") - return - } - - accessRsp = &model.AccessResponse{ - AccessToken: session.Token, - TokenType: model.ACCESS_TOKEN_TYPE, - RefreshToken: accessData.RefreshToken, - ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24), - } - } - - <-app.Srv.Store.OAuth().RemoveAuthData(authData.Code) - } else { - // when grantType is refresh_token - if result := <-app.Srv.Store.OAuth().GetAccessDataByRefreshToken(refreshToken); result.Err != nil { - c.LogAudit("fail - refresh token is invalid") - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.refresh_token.app_error", nil, "") - return - } else { - accessData = result.Data.(*model.AccessData) - } + redirectUri := r.FormValue("redirect_uri") - uchan := app.Srv.Store.User().Get(accessData.UserId) - if result := <-uchan; result.Err != nil { - c.Err = model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "") - return - } else { - user = result.Data.(*model.User) - } + c.LogAudit("attempt") - if access, err := newSessionUpdateToken(oauthApp.Name, accessData, user); err != nil { - c.Err = err - return - } else { - accessRsp = access - } + 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.LogAuditWithUserId(user.Id, "success") + c.LogAudit("success") w.Write([]byte(accessRsp.ToJson())) } @@ -602,23 +315,13 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { loginHint := r.URL.Query().Get("login_hint") redirectTo := r.URL.Query().Get("redirect_to") - teamId, err := getTeamIdFromQuery(r.URL.Query()) + teamId, err := app.GetTeamIdFromQuery(r.URL.Query()) if err != nil { c.Err = err return } - stateProps := map[string]string{} - stateProps["action"] = model.OAUTH_ACTION_LOGIN - if len(teamId) != 0 { - stateProps["team_id"] = teamId - } - - if len(redirectTo) != 0 { - stateProps["redirect_to"] = redirectTo - } - - if authUrl, err := app.GetAuthorizationCode(service, stateProps, loginHint); err != nil { + if authUrl, err := app.GetOAuthLoginEndpoint(service, teamId, redirectTo, loginHint); err != nil { c.Err = err return } else { @@ -626,59 +329,22 @@ func loginWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } } -func getTeamIdFromQuery(query url.Values) (string, *model.AppError) { - hash := query.Get("h") - inviteId := query.Get("id") - - if len(hash) > 0 { - data := query.Get("d") - props := model.MapFromJson(strings.NewReader(data)) - - if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) { - return "", model.NewLocAppError("getTeamIdFromQuery", "api.oauth.singup_with_oauth.invalid_link.app_error", nil, "") - } - - t, err := strconv.ParseInt(props["time"], 10, 64) - if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours - return "", model.NewLocAppError("getTeamIdFromQuery", "api.oauth.singup_with_oauth.expired_link.app_error", nil, "") - } - - return props["id"], nil - } else if len(inviteId) > 0 { - if result := <-app.Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil { - // soft fail, so we still create user but don't auto-join team - l4g.Error("%v", result.Err) - } else { - return result.Data.(*model.Team).Id, nil - } - } - - return "", nil -} - func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) service := params["service"] if !utils.Cfg.TeamSettings.EnableUserCreation { - c.Err = model.NewLocAppError("signupWithOAuth", "api.oauth.singup_with_oauth.disabled.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented + c.Err = model.NewAppError("signupWithOAuth", "api.oauth.singup_with_oauth.disabled.app_error", nil, "", http.StatusNotImplemented) return } - teamId, err := getTeamIdFromQuery(r.URL.Query()) + teamId, err := app.GetTeamIdFromQuery(r.URL.Query()) if err != nil { c.Err = err return } - stateProps := map[string]string{} - stateProps["action"] = model.OAUTH_ACTION_SIGNUP - if len(teamId) != 0 { - stateProps["team_id"] = teamId - } - - if authUrl, err := app.GetAuthorizationCode(service, stateProps, ""); err != nil { + if authUrl, err := app.GetOAuthSignupEndpoint(service, teamId); err != nil { c.Err = err return } else { @@ -686,95 +352,36 @@ func signupWithOAuth(c *Context, w http.ResponseWriter, r *http.Request) { } } -func CompleteSwitchWithOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.ReadCloser, email string) { - authData := "" - ssoEmail := "" - provider := einterfaces.GetOauthProvider(service) - if provider == nil { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error", - map[string]interface{}{"Service": strings.Title(service)}, "") - return - } else { - ssoUser := provider.GetUserFromJson(userData) - ssoEmail = ssoUser.Email - - if ssoUser.AuthData != nil { - authData = *ssoUser.AuthData - } - } - - if len(authData) == 0 { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.parse.app_error", - map[string]interface{}{"Service": service}, "") - return - } - - if len(email) == 0 { - c.Err = model.NewLocAppError("CompleteClaimWithOAuth", "api.user.complete_switch_with_oauth.blank_email.app_error", nil, "") - return - } - - var user *model.User - if result := <-app.Srv.Store.User().GetByEmail(email); result.Err != nil { - c.Err = result.Err - return - } else { - user = result.Data.(*model.User) - } - - if err := app.RevokeAllSessions(user.Id); err != nil { - c.Err = err - return - } - c.LogAuditWithUserId(user.Id, "Revoked all sessions for user") +func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { + props := model.MapFromJson(r.Body) - if result := <-app.Srv.Store.User().UpdateAuthData(user.Id, service, &authData, ssoEmail, true); result.Err != nil { - c.Err = result.Err + id := props["id"] + if len(id) == 0 { + c.SetInvalidParam("deleteOAuthApp", "id") return } - go func() { - if err := app.SendSignInChangeEmail(user.Email, strings.Title(service)+" SSO", user.Locale, utils.GetSiteURL()); err != nil { - l4g.Error(err.Error()) - } - }() -} - -func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("deleteOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } + c.LogAudit("attempt") if !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_OAUTH) { - c.Err = model.NewLocAppError("deleteOAuthApp", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden + c.Err = model.NewAppError("deleteOAuthApp", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) return } - c.LogAudit("attempt") - - props := model.MapFromJson(r.Body) - - id := props["id"] - if len(id) == 0 { - c.SetInvalidParam("deleteOAuthApp", "id") + oauthApp, err := app.GetOAuthApp(id) + if err != nil { + c.Err = err return } - if result := <-app.Srv.Store.OAuth().GetApp(id); result.Err != nil { - c.Err = result.Err + if c.Session.UserId != oauthApp.CreatorId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { + c.LogAudit("fail - inappropriate permissions") + c.Err = model.NewAppError("deleteOAuthApp", "api.oauth.delete.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusForbidden) return - } else { - if c.Session.UserId != result.Data.(*model.OAuthApp).CreatorId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - c.LogAudit("fail - inappropriate permissions") - c.Err = model.NewLocAppError("deleteOAuthApp", "api.oauth.delete.permissions.app_error", nil, "user_id="+c.Session.UserId) - return - } } - if err := (<-app.Srv.Store.OAuth().DeleteApp(id)).Err; err != nil { + err = app.DeleteOAuthApp(id) + if err != nil { c.Err = err return } @@ -784,37 +391,11 @@ func deleteOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { } func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("deleteOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - params := mux.Vars(r) id := params["id"] - // revoke app sessions - if result := <-app.Srv.Store.OAuth().GetAccessDataByUserForApp(c.Session.UserId, id); result.Err != nil { - c.Err = result.Err - return - } else { - accessData := result.Data.([]*model.AccessData) - - for _, a := range accessData { - if err := app.RevokeAccessToken(a.Token); err != nil { - c.Err = err - return - } - - if rad := <-app.Srv.Store.OAuth().RemoveAccessData(a.Token); rad.Err != nil { - c.Err = rad.Err - return - } - } - } - - // Deauthorize the app - if err := (<-app.Srv.Store.Preference().Delete(c.Session.UserId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, id)).Err; err != nil { + err := app.DeauthorizeOAuthAppForUser(c.Session.UserId, id) + if err != nil { c.Err = err return } @@ -824,78 +405,25 @@ func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) { } func regenerateOAuthSecret(c *Context, w http.ResponseWriter, r *http.Request) { - if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider { - c.Err = model.NewLocAppError("registerOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "") - c.Err.StatusCode = http.StatusNotImplemented - return - } - params := mux.Vars(r) id := params["id"] - var oauthApp *model.OAuthApp - if result := <-app.Srv.Store.OAuth().GetApp(id); result.Err != nil { - c.Err = model.NewLocAppError("regenerateOAuthSecret", "api.oauth.allow_oauth.database.app_error", nil, "") - return - } else { - oauthApp = result.Data.(*model.OAuthApp) - - if oauthApp.CreatorId != c.Session.UserId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { - c.Err = model.NewLocAppError("registerOAuthApp", "api.command.admin_only.app_error", nil, "") - c.Err.StatusCode = http.StatusForbidden - return - } - - oauthApp.ClientSecret = model.NewId() - if update := <-app.Srv.Store.OAuth().UpdateApp(oauthApp); update.Err != nil { - c.Err = update.Err - return - } - - w.Write([]byte(oauthApp.ToJson())) + oauthApp, err := app.GetOAuthApp(id) + if err != nil { + c.Err = err return } -} - -func newSession(appName string, user *model.User) (*model.Session, *model.AppError) { - // set new token an session - session := &model.Session{UserId: user.Id, Roles: user.Roles, IsOAuth: true} - session.SetExpireInDays(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays) - session.AddProp(model.SESSION_PROP_PLATFORM, appName) - session.AddProp(model.SESSION_PROP_OS, "OAuth2") - session.AddProp(model.SESSION_PROP_BROWSER, "OAuth2") - - if result := <-app.Srv.Store.Session().Save(session); result.Err != nil { - return nil, model.NewLocAppError("getAccessToken", "api.oauth.get_access_token.internal_session.app_error", nil, "") - } else { - session = result.Data.(*model.Session) - app.AddSessionToCache(session) - } - - return session, nil -} -func newSessionUpdateToken(appName string, accessData *model.AccessData, user *model.User) (*model.AccessResponse, *model.AppError) { - var session *model.Session - <-app.Srv.Store.Session().Remove(accessData.Token) //remove the previous session - - if result, err := newSession(appName, user); err != nil { - return nil, err - } else { - session = result + if oauthApp.CreatorId != c.Session.UserId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM_WIDE_OAUTH) { + c.Err = model.NewAppError("regenerateOAuthSecret", "api.command.admin_only.app_error", nil, "", http.StatusForbidden) + return } - accessData.Token = session.Token - accessData.ExpiresAt = session.ExpiresAt - if result := <-app.Srv.Store.OAuth().UpdateAccessData(accessData); result.Err != nil { - l4g.Error(result.Err) - return nil, model.NewLocAppError("getAccessToken", "web.get_access_token.internal_saving.app_error", nil, "") - } - accessRsp := &model.AccessResponse{ - AccessToken: session.Token, - TokenType: model.ACCESS_TOKEN_TYPE, - ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24), + oauthApp, err = app.RegenerateOAuthAppSecret(oauthApp) + if err != nil { + c.Err = err + return } - return accessRsp, nil + w.Write([]byte(oauthApp.ToJson())) } diff --git a/api/oauth_test.go b/api/oauth_test.go index 7231c777d..18938b902 100644 --- a/api/oauth_test.go +++ b/api/oauth_test.go @@ -491,7 +491,7 @@ func TestOAuthAuthorize(t *testing.T) { } authToken := Client.AuthType + " " + Client.AuthToken - if r, err := HttpGet(Client.Url+"/oauth/authorize?client_id="+oauthApp.Id+"&&redirect_uri=http://example.com&response_type="+model.AUTHCODE_RESPONSE_TYPE, Client.HttpClient, authToken, true); err != nil { + if r, err := HttpGet(Client.Url+"/oauth/authorize?client_id="+oauthApp.Id+"&redirect_uri=http://example.com&response_type="+model.AUTHCODE_RESPONSE_TYPE, Client.HttpClient, authToken, true); err != nil { t.Fatal(err) closeBody(r) } diff --git a/api/user.go b/api/user.go index 466f12873..8b32dff36 100644 --- a/api/user.go +++ b/api/user.go @@ -4,10 +4,8 @@ package api import ( - "bytes" b64 "encoding/base64" "fmt" - "io" "net/http" "strconv" "strings" @@ -132,52 +130,6 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) { w.Write([]byte(user.ToJson())) } -func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader) *model.User { - buf := bytes.Buffer{} - buf.ReadFrom(userData) - - authData := "" - provider := einterfaces.GetOauthProvider(service) - if provider == nil { - c.Err = model.NewLocAppError("LoginByOAuth", "api.user.login_by_oauth.not_available.app_error", - map[string]interface{}{"Service": strings.Title(service)}, "") - return nil - } else { - authData = provider.GetAuthDataFromJson(bytes.NewReader(buf.Bytes())) - } - - if len(authData) == 0 { - c.Err = model.NewLocAppError("LoginByOAuth", "api.user.login_by_oauth.parse.app_error", - map[string]interface{}{"Service": service}, "") - return nil - } - - var user *model.User - var err *model.AppError - if user, err = app.GetUserByAuth(&authData, service); err != nil { - if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR { - if user, err = app.CreateOAuthUser(service, bytes.NewReader(buf.Bytes()), ""); err != nil { - c.Err = err - return nil - } - } - c.Err = err - return nil - } - - if err = app.UpdateOAuthUserAttrs(bytes.NewReader(buf.Bytes()), user, provider, service); err != nil { - c.Err = err - return nil - } - - doLogin(c, w, r, user, "") - if c.Err != nil { - return nil - } - - return user -} - // User MUST be authenticated completely before calling Login func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) { session, err := app.DoLogin(w, r, user, deviceId) @@ -1188,7 +1140,7 @@ func loginWithSaml(c *Context, w http.ResponseWriter, r *http.Request) { return } - teamId, err := getTeamIdFromQuery(r.URL.Query()) + teamId, err := app.GetTeamIdFromQuery(r.URL.Query()) if err != nil { c.Err = err return |