summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/oauth.go676
-rw-r--r--api/oauth_test.go2
-rw-r--r--api/user.go50
-rw-r--r--app/oauth.go477
-rw-r--r--app/team.go31
-rw-r--r--model/access.go1
-rw-r--r--store/sql_oauth_store.go24
-rw-r--r--store/sql_oauth_store_test.go12
-rw-r--r--store/sql_upgrade.go1
-rw-r--r--store/sql_user_store.go3
-rw-r--r--store/store.go6
-rw-r--r--webapp/tests/client/client_oauth.test.jsx4
12 files changed, 638 insertions, 649 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
diff --git a/app/oauth.go b/app/oauth.go
index e79d240fe..260e4ac00 100644
--- a/app/oauth.go
+++ b/app/oauth.go
@@ -4,8 +4,10 @@
package app
import (
+ "bytes"
"crypto/tls"
b64 "encoding/base64"
+ "fmt"
"io"
"io/ioutil"
"net/http"
@@ -13,10 +15,371 @@ import (
"strings"
l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
)
+func CreateOAuthApp(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return nil, model.NewAppError("CreateOAuthApp", "api.oauth.register_oauth_app.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ secret := model.NewId()
+ app.ClientSecret = secret
+
+ if result := <-Srv.Store.OAuth().SaveApp(app); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.OAuthApp), nil
+ }
+}
+
+func GetOAuthApp(appId string) (*model.OAuthApp, *model.AppError) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return nil, model.NewAppError("GetOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if result := <-Srv.Store.OAuth().GetApp(appId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.OAuthApp), nil
+ }
+}
+
+func DeleteOAuthApp(appId string) *model.AppError {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return model.NewAppError("DeleteOAuthApp", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if err := (<-Srv.Store.OAuth().DeleteApp(appId)).Err; err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func GetOAuthApps(page, perPage int) ([]*model.OAuthApp, *model.AppError) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return nil, model.NewAppError("GetOAuthApps", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if result := <-Srv.Store.OAuth().GetApps(page*perPage, perPage); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.([]*model.OAuthApp), nil
+ }
+}
+
+func GetOAuthAppsByCreator(userId string, page, perPage int) ([]*model.OAuthApp, *model.AppError) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return nil, model.NewAppError("GetOAuthAppsByUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if result := <-Srv.Store.OAuth().GetAppByUser(userId, page*perPage, perPage); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.([]*model.OAuthApp), nil
+ }
+}
+
+func AllowOAuthAppAccessToUser(userId, responseType, clientId, redirectUri, scope, state string) (string, *model.AppError) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if len(scope) == 0 {
+ scope = model.DEFAULT_SCOPE
+ }
+
+ var oauthApp *model.OAuthApp
+ if result := <-Srv.Store.OAuth().GetApp(clientId); result.Err != nil {
+ return "", result.Err
+ } else {
+ oauthApp = result.Data.(*model.OAuthApp)
+ }
+
+ if !oauthApp.IsValidRedirectURL(redirectUri) {
+ return "", model.NewAppError("AllowOAuthAppAccessToUser", "api.oauth.allow_oauth.redirect_callback.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if responseType != model.AUTHCODE_RESPONSE_TYPE {
+ return redirectUri + "?error=unsupported_response_type&state=" + state, nil
+ }
+
+ authData := &model.AuthData{UserId: 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, userId))
+
+ // this saves the OAuth2 app as authorized
+ authorizedApp := model.Preference{
+ UserId: userId,
+ Category: model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP,
+ Name: clientId,
+ Value: scope,
+ }
+
+ if result := <-Srv.Store.Preference().Save(&model.Preferences{authorizedApp}); result.Err != nil {
+ return redirectUri + "?error=server_error&state=" + state, nil
+ }
+
+ if result := <-Srv.Store.OAuth().SaveAuthData(authData); result.Err != nil {
+ return redirectUri + "?error=server_error&state=" + state, nil
+ }
+
+ return redirectUri + "?code=" + url.QueryEscape(authData.Code) + "&state=" + url.QueryEscape(authData.State), nil
+}
+
+func GetOAuthAccessToken(clientId, grantType, redirectUri, code, secret, refreshToken string) (*model.AccessResponse, *model.AppError) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.disabled.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ var oauthApp *model.OAuthApp
+ if result := <-Srv.Store.OAuth().GetApp(clientId); result.Err != nil {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "", http.StatusNotFound)
+ } else {
+ oauthApp = result.Data.(*model.OAuthApp)
+ }
+
+ if oauthApp.ClientSecret != secret {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.credentials.app_error", nil, "", http.StatusForbidden)
+ }
+
+ var user *model.User
+ var accessData *model.AccessData
+ var accessRsp *model.AccessResponse
+ if grantType == model.ACCESS_TOKEN_GRANT_TYPE {
+
+ var authData *model.AuthData
+ if result := <-Srv.Store.OAuth().GetAuthData(code); result.Err != nil {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusInternalServerError)
+ } else {
+ authData = result.Data.(*model.AuthData)
+ }
+
+ if authData.IsExpired() {
+ <-Srv.Store.OAuth().RemoveAuthData(authData.Code)
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusForbidden)
+ }
+
+ if authData.RedirectUri != redirectUri {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.redirect_uri.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if !model.ComparePassword(code, fmt.Sprintf("%v:%v:%v:%v", clientId, redirectUri, authData.CreateAt, authData.UserId)) {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.expired_code.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ if result := <-Srv.Store.User().Get(authData.UserId); result.Err != nil {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound)
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ if result := <-Srv.Store.OAuth().GetPreviousAccessData(user.Id, clientId); result.Err != nil {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal.app_error", nil, "", http.StatusInternalServerError)
+ } else if result.Data != nil {
+ accessData := result.Data.(*model.AccessData)
+ if accessData.IsExpired() {
+ if access, err := newSessionUpdateToken(oauthApp.Name, accessData, user); err != nil {
+ return nil, err
+ } 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 {
+ return nil, err
+ } else {
+ session = result
+ }
+
+ accessData = &model.AccessData{ClientId: clientId, UserId: user.Id, Token: session.Token, RefreshToken: model.NewId(), RedirectUri: redirectUri, ExpiresAt: session.ExpiresAt, Scope: authData.Scope}
+
+ if result := <-Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil {
+ l4g.Error(result.Err)
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError)
+ }
+
+ accessRsp = &model.AccessResponse{
+ AccessToken: session.Token,
+ TokenType: model.ACCESS_TOKEN_TYPE,
+ RefreshToken: accessData.RefreshToken,
+ ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24),
+ }
+ }
+
+ <-Srv.Store.OAuth().RemoveAuthData(authData.Code)
+ } else {
+ // when grantType is refresh_token
+ if result := <-Srv.Store.OAuth().GetAccessDataByRefreshToken(refreshToken); result.Err != nil {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.refresh_token.app_error", nil, "", http.StatusNotFound)
+ } else {
+ accessData = result.Data.(*model.AccessData)
+ }
+
+ if result := <-Srv.Store.User().Get(accessData.UserId); result.Err != nil {
+ return nil, model.NewAppError("GetOAuthAccessToken", "api.oauth.get_access_token.internal_user.app_error", nil, "", http.StatusNotFound)
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ if access, err := newSessionUpdateToken(oauthApp.Name, accessData, user); err != nil {
+ return nil, err
+ } else {
+ accessRsp = access
+ }
+ }
+
+ return accessRsp, nil
+}
+
+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 := <-Srv.Store.Session().Save(session); result.Err != nil {
+ return nil, model.NewAppError("newSession", "api.oauth.get_access_token.internal_session.app_error", nil, "", http.StatusInternalServerError)
+ } else {
+ session = result.Data.(*model.Session)
+ AddSessionToCache(session)
+ }
+
+ return session, nil
+}
+
+func newSessionUpdateToken(appName string, accessData *model.AccessData, user *model.User) (*model.AccessResponse, *model.AppError) {
+ var session *model.Session
+ <-Srv.Store.Session().Remove(accessData.Token) //remove the previous session
+
+ if result, err := newSession(appName, user); err != nil {
+ return nil, err
+ } else {
+ session = result
+ }
+
+ accessData.Token = session.Token
+ accessData.ExpiresAt = session.ExpiresAt
+ if result := <-Srv.Store.OAuth().UpdateAccessData(accessData); result.Err != nil {
+ l4g.Error(result.Err)
+ return nil, model.NewAppError("newSessionUpdateToken", "web.get_access_token.internal_saving.app_error", nil, "", http.StatusInternalServerError)
+ }
+ accessRsp := &model.AccessResponse{
+ AccessToken: session.Token,
+ TokenType: model.ACCESS_TOKEN_TYPE,
+ ExpiresIn: int32(*utils.Cfg.ServiceSettings.SessionLengthSSOInDays * 60 * 60 * 24),
+ }
+
+ return accessRsp, nil
+}
+
+func GetOAuthLoginEndpoint(service, teamId, redirectTo, loginHint string) (string, *model.AppError) {
+ 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 := GetAuthorizationCode(service, stateProps, loginHint); err != nil {
+ return "", err
+ } else {
+ return authUrl, nil
+ }
+}
+
+func GetOAuthSignupEndpoint(service, teamId string) (string, *model.AppError) {
+ stateProps := map[string]string{}
+ stateProps["action"] = model.OAUTH_ACTION_SIGNUP
+ if len(teamId) != 0 {
+ stateProps["team_id"] = teamId
+ }
+
+ if authUrl, err := GetAuthorizationCode(service, stateProps, ""); err != nil {
+ return "", err
+ } else {
+ return authUrl, nil
+ }
+}
+
+func GetAuthorizedAppsForUser(userId string, page, perPage int) ([]*model.OAuthApp, *model.AppError) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return nil, model.NewAppError("GetAuthorizedAppsForUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ if result := <-Srv.Store.OAuth().GetAuthorizedApps(userId, page*perPage, perPage); result.Err != nil {
+ return nil, result.Err
+ } else {
+ apps := result.Data.([]*model.OAuthApp)
+ for k, a := range apps {
+ a.Sanitize()
+ apps[k] = a
+ }
+
+ return apps, nil
+ }
+}
+
+func DeauthorizeOAuthAppForUser(userId, appId string) *model.AppError {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return model.NewAppError("DeauthorizeOAuthAppForUser", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ // revoke app sessions
+ if result := <-Srv.Store.OAuth().GetAccessDataByUserForApp(userId, appId); result.Err != nil {
+ return result.Err
+ } else {
+ accessData := result.Data.([]*model.AccessData)
+
+ for _, a := range accessData {
+ if err := RevokeAccessToken(a.Token); err != nil {
+ return err
+ }
+
+ if rad := <-Srv.Store.OAuth().RemoveAccessData(a.Token); rad.Err != nil {
+ return rad.Err
+ }
+ }
+ }
+
+ // Deauthorize the app
+ if err := (<-Srv.Store.Preference().Delete(userId, model.PREFERENCE_CATEGORY_AUTHORIZED_OAUTH_APP, appId)).Err; err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func RegenerateOAuthAppSecret(app *model.OAuthApp) (*model.OAuthApp, *model.AppError) {
+ if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
+ return nil, model.NewAppError("RegenerateOAuthAppSecret", "api.oauth.allow_oauth.turn_off.app_error", nil, "", http.StatusNotImplemented)
+ }
+
+ app.ClientSecret = model.NewId()
+ if update := <-Srv.Store.OAuth().UpdateApp(app); update.Err != nil {
+ return nil, update.Err
+ }
+
+ return app, nil
+}
+
func RevokeAccessToken(token string) *model.AppError {
session, _ := GetSession(token)
schan := Srv.Store.Session().Remove(token)
@@ -42,10 +405,122 @@ func RevokeAccessToken(token string) *model.AppError {
return nil
}
+func CompleteOAuth(service string, body io.ReadCloser, teamId string, props map[string]string) (*model.User, *model.AppError) {
+ defer func() {
+ ioutil.ReadAll(body)
+ body.Close()
+ }()
+
+ action := props["action"]
+
+ switch action {
+ case model.OAUTH_ACTION_SIGNUP:
+ return CreateOAuthUser(service, body, teamId)
+ case model.OAUTH_ACTION_LOGIN:
+ return LoginByOAuth(service, body, teamId)
+ case model.OAUTH_ACTION_EMAIL_TO_SSO:
+ return CompleteSwitchWithOAuth(service, body, props["email"])
+ case model.OAUTH_ACTION_SSO_TO_EMAIL:
+ return LoginByOAuth(service, body, teamId)
+ default:
+ return LoginByOAuth(service, body, teamId)
+ }
+}
+
+func LoginByOAuth(service string, userData io.Reader, teamId string) (*model.User, *model.AppError) {
+ buf := bytes.Buffer{}
+ buf.ReadFrom(userData)
+
+ authData := ""
+ provider := einterfaces.GetOauthProvider(service)
+ if provider == nil {
+ return nil, model.NewAppError("LoginByOAuth", "api.user.login_by_oauth.not_available.app_error",
+ map[string]interface{}{"Service": strings.Title(service)}, "", http.StatusNotImplemented)
+ } else {
+ authData = provider.GetAuthDataFromJson(bytes.NewReader(buf.Bytes()))
+ }
+
+ if len(authData) == 0 {
+ return nil, model.NewAppError("LoginByOAuth", "api.user.login_by_oauth.parse.app_error",
+ map[string]interface{}{"Service": service}, "", http.StatusBadRequest)
+ }
+
+ user, err := GetUserByAuth(&authData, service)
+ if err != nil {
+ if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR {
+ return CreateOAuthUser(service, bytes.NewReader(buf.Bytes()), teamId)
+ }
+ return nil, err
+ }
+
+ if err = UpdateOAuthUserAttrs(bytes.NewReader(buf.Bytes()), user, provider, service); err != nil {
+ return nil, err
+ }
+
+ if len(teamId) > 0 {
+ err = AddUserToTeamByTeamId(teamId, user)
+ }
+
+ if err != nil {
+ return nil, err
+ }
+
+ return user, nil
+}
+
+func CompleteSwitchWithOAuth(service string, userData io.ReadCloser, email string) (*model.User, *model.AppError) {
+ authData := ""
+ ssoEmail := ""
+ provider := einterfaces.GetOauthProvider(service)
+ if provider == nil {
+ return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.unavailable.app_error",
+ map[string]interface{}{"Service": strings.Title(service)}, "", http.StatusNotImplemented)
+ } else {
+ ssoUser := provider.GetUserFromJson(userData)
+ ssoEmail = ssoUser.Email
+
+ if ssoUser.AuthData != nil {
+ authData = *ssoUser.AuthData
+ }
+ }
+
+ if len(authData) == 0 {
+ return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.parse.app_error",
+ map[string]interface{}{"Service": service}, "", http.StatusBadRequest)
+ }
+
+ if len(email) == 0 {
+ return nil, model.NewAppError("CompleteSwitchWithOAuth", "api.user.complete_switch_with_oauth.blank_email.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ var user *model.User
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
+ return nil, result.Err
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ if err := RevokeAllSessions(user.Id); err != nil {
+ return nil, err
+ }
+
+ if result := <-Srv.Store.User().UpdateAuthData(user.Id, service, &authData, ssoEmail, true); result.Err != nil {
+ return nil, result.Err
+ }
+
+ go func() {
+ if err := SendSignInChangeEmail(user.Email, strings.Title(service)+" SSO", user.Locale, utils.GetSiteURL()); err != nil {
+ l4g.Error(err.Error())
+ }
+ }()
+
+ return user, nil
+}
+
func GetAuthorizationCode(service string, props map[string]string, loginHint string) (string, *model.AppError) {
sso := utils.Cfg.GetSSOService(service)
if sso != nil && !sso.Enable {
- return "", model.NewLocAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service)
+ return "", model.NewAppError("GetAuthorizationCode", "api.user.get_authorization_code.unsupported.app_error", nil, "service="+service, http.StatusNotImplemented)
}
clientId := sso.Id
diff --git a/app/team.go b/app/team.go
index 327ab7f3e..d4e6d6308 100644
--- a/app/team.go
+++ b/app/team.go
@@ -6,6 +6,7 @@ package app
import (
"fmt"
"net/http"
+ "net/url"
"strconv"
"strings"
@@ -747,3 +748,33 @@ func GetTeamStats(teamId string) (*model.TeamStats, *model.AppError) {
return stats, nil
}
+
+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.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.invalid_link.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ t, err := strconv.ParseInt(props["time"], 10, 64)
+ if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours
+ return "", model.NewAppError("GetTeamIdFromQuery", "api.oauth.singup_with_oauth.expired_link.app_error", nil, "", http.StatusBadRequest)
+ }
+
+ return props["id"], nil
+ } else if len(inviteId) > 0 {
+ if result := <-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
+}
diff --git a/model/access.go b/model/access.go
index bab29dd84..9e16ed58b 100644
--- a/model/access.go
+++ b/model/access.go
@@ -21,6 +21,7 @@ type AccessData struct {
RefreshToken string `json:"refresh_token"`
RedirectUri string `json:"redirect_uri"`
ExpiresAt int64 `json:"expires_at"`
+ Scope string `json:"scope"`
}
type AccessResponse struct {
diff --git a/store/sql_oauth_store.go b/store/sql_oauth_store.go
index bc97ee33a..6311b56dd 100644
--- a/store/sql_oauth_store.go
+++ b/store/sql_oauth_store.go
@@ -4,6 +4,7 @@
package store
import (
+ "net/http"
"strings"
"github.com/go-gorp/gorp"
@@ -42,6 +43,7 @@ func NewSqlOAuthStore(sqlStore *SqlStore) OAuthStore {
tableAccess.ColMap("Token").SetMaxSize(26)
tableAccess.ColMap("RefreshToken").SetMaxSize(26)
tableAccess.ColMap("RedirectUri").SetMaxSize(256)
+ tableAccess.ColMap("Scope").SetMaxSize(128)
tableAccess.SetUniqueTogether("ClientId", "UserId")
}
@@ -138,9 +140,9 @@ func (as SqlOAuthStore) GetApp(id string) StoreChannel {
result := StoreResult{}
if obj, err := as.GetReplica().Get(model.OAuthApp{}, id); err != nil {
- result.Err = model.NewLocAppError("SqlOAuthStore.GetApp", "store.sql_oauth.get_app.finding.app_error", nil, "app_id="+id+", "+err.Error())
+ result.Err = model.NewAppError("SqlOAuthStore.GetApp", "store.sql_oauth.get_app.finding.app_error", nil, "app_id="+id+", "+err.Error(), http.StatusInternalServerError)
} else if obj == nil {
- result.Err = model.NewLocAppError("SqlOAuthStore.GetApp", "store.sql_oauth.get_app.find.app_error", nil, "app_id="+id)
+ result.Err = model.NewAppError("SqlOAuthStore.GetApp", "store.sql_oauth.get_app.find.app_error", nil, "app_id="+id, http.StatusNotFound)
} else {
result.Data = obj.(*model.OAuthApp)
}
@@ -153,7 +155,7 @@ func (as SqlOAuthStore) GetApp(id string) StoreChannel {
return storeChannel
}
-func (as SqlOAuthStore) GetAppByUser(userId string) StoreChannel {
+func (as SqlOAuthStore) GetAppByUser(userId string, offset, limit int) StoreChannel {
storeChannel := make(StoreChannel, 1)
@@ -162,8 +164,8 @@ func (as SqlOAuthStore) GetAppByUser(userId string) StoreChannel {
var apps []*model.OAuthApp
- if _, err := as.GetReplica().Select(&apps, "SELECT * FROM OAuthApps WHERE CreatorId = :UserId", map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewLocAppError("SqlOAuthStore.GetAppByUser", "store.sql_oauth.get_app_by_user.find.app_error", nil, "user_id="+userId+", "+err.Error())
+ if _, err := as.GetReplica().Select(&apps, "SELECT * FROM OAuthApps WHERE CreatorId = :UserId LIMIT :Limit OFFSET :Offset", map[string]interface{}{"UserId": userId, "Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewAppError("SqlOAuthStore.GetAppByUser", "store.sql_oauth.get_app_by_user.find.app_error", nil, "user_id="+userId+", "+err.Error(), http.StatusInternalServerError)
}
result.Data = apps
@@ -175,7 +177,7 @@ func (as SqlOAuthStore) GetAppByUser(userId string) StoreChannel {
return storeChannel
}
-func (as SqlOAuthStore) GetApps() StoreChannel {
+func (as SqlOAuthStore) GetApps(offset, limit int) StoreChannel {
storeChannel := make(StoreChannel, 1)
@@ -184,8 +186,8 @@ func (as SqlOAuthStore) GetApps() StoreChannel {
var apps []*model.OAuthApp
- if _, err := as.GetReplica().Select(&apps, "SELECT * FROM OAuthApps"); err != nil {
- result.Err = model.NewLocAppError("SqlOAuthStore.GetAppByUser", "store.sql_oauth.get_apps.find.app_error", nil, "err="+err.Error())
+ if _, err := as.GetReplica().Select(&apps, "SELECT * FROM OAuthApps LIMIT :Limit OFFSET :Offset", map[string]interface{}{"Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewAppError("SqlOAuthStore.GetAppByUser", "store.sql_oauth.get_apps.find.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
result.Data = apps
@@ -197,7 +199,7 @@ func (as SqlOAuthStore) GetApps() StoreChannel {
return storeChannel
}
-func (as SqlOAuthStore) GetAuthorizedApps(userId string) StoreChannel {
+func (as SqlOAuthStore) GetAuthorizedApps(userId string, offset, limit int) StoreChannel {
storeChannel := make(StoreChannel, 1)
go func() {
@@ -207,8 +209,8 @@ func (as SqlOAuthStore) GetAuthorizedApps(userId string) StoreChannel {
if _, err := as.GetReplica().Select(&apps,
`SELECT o.* FROM OAuthApps AS o INNER JOIN
- Preferences AS p ON p.Name=o.Id AND p.UserId=:UserId`, map[string]interface{}{"UserId": userId}); err != nil {
- result.Err = model.NewLocAppError("SqlOAuthStore.GetAuthorizedApps", "store.sql_oauth.get_apps.find.app_error", nil, "err="+err.Error())
+ Preferences AS p ON p.Name=o.Id AND p.UserId=:UserId LIMIT :Limit OFFSET :Offset`, map[string]interface{}{"UserId": userId, "Offset": offset, "Limit": limit}); err != nil {
+ result.Err = model.NewAppError("SqlOAuthStore.GetAuthorizedApps", "store.sql_oauth.get_apps.find.app_error", nil, "err="+err.Error(), http.StatusInternalServerError)
}
result.Data = apps
diff --git a/store/sql_oauth_store_test.go b/store/sql_oauth_store_test.go
index d0c04c53f..8c707562f 100644
--- a/store/sql_oauth_store_test.go
+++ b/store/sql_oauth_store_test.go
@@ -56,7 +56,7 @@ func TestOAuthStoreGetApp(t *testing.T) {
}
// Lets try and get the app from a user that hasn't created any apps
- if result := (<-store.OAuth().GetAppByUser("fake0123456789abcderfgret1")); result.Err == nil {
+ if result := (<-store.OAuth().GetAppByUser("fake0123456789abcderfgret1", 0, 1000)); result.Err == nil {
if len(result.Data.([]*model.OAuthApp)) > 0 {
t.Fatal("Should have failed. Fake user hasn't created any apps")
}
@@ -64,11 +64,11 @@ func TestOAuthStoreGetApp(t *testing.T) {
t.Fatal(result.Err)
}
- if err := (<-store.OAuth().GetAppByUser(a1.CreatorId)).Err; err != nil {
+ if err := (<-store.OAuth().GetAppByUser(a1.CreatorId, 0, 1000)).Err; err != nil {
t.Fatal(err)
}
- if err := (<-store.OAuth().GetApps()).Err; err != nil {
+ if err := (<-store.OAuth().GetApps(0, 1000)).Err; err != nil {
t.Fatal(err)
}
}
@@ -324,7 +324,7 @@ func TestOAuthGetAuthorizedApps(t *testing.T) {
Must(store.OAuth().SaveApp(&a1))
// Lets try and get an Authorized app for a user who hasn't authorized it
- if result := <-store.OAuth().GetAuthorizedApps("fake0123456789abcderfgret1"); result.Err == nil {
+ if result := <-store.OAuth().GetAuthorizedApps("fake0123456789abcderfgret1", 0, 1000); result.Err == nil {
if len(result.Data.([]*model.OAuthApp)) > 0 {
t.Fatal("Should have failed. Fake user hasn't authorized the app")
}
@@ -340,7 +340,7 @@ func TestOAuthGetAuthorizedApps(t *testing.T) {
p.Value = "true"
Must(store.Preference().Save(&model.Preferences{p}))
- if result := <-store.OAuth().GetAuthorizedApps(a1.CreatorId); result.Err != nil {
+ if result := <-store.OAuth().GetAuthorizedApps(a1.CreatorId, 0, 1000); result.Err != nil {
t.Fatal(result.Err)
} else {
apps := result.Data.([]*model.OAuthApp)
@@ -368,7 +368,7 @@ func TestOAuthGetAccessDataByUserForApp(t *testing.T) {
p.Value = "true"
Must(store.Preference().Save(&model.Preferences{p}))
- if result := <-store.OAuth().GetAuthorizedApps(a1.CreatorId); result.Err != nil {
+ if result := <-store.OAuth().GetAuthorizedApps(a1.CreatorId, 0, 1000); result.Err != nil {
t.Fatal(result.Err)
} else {
apps := result.Data.([]*model.OAuthApp)
diff --git a/store/sql_upgrade.go b/store/sql_upgrade.go
index dbdf12605..b25e7e73d 100644
--- a/store/sql_upgrade.go
+++ b/store/sql_upgrade.go
@@ -257,6 +257,7 @@ func UpgradeDatabaseToVersion38(sqlStore *SqlStore) {
func UpgradeDatabaseToVersion39(sqlStore *SqlStore) {
// TODO: Uncomment following condition when version 3.9.0 is released
//if shouldPerformUpgrade(sqlStore, VERSION_3_8_0, VERSION_3_9_0) {
+ sqlStore.CreateColumnIfNotExists("OAuthAccessData", "Scope", "varchar(128)", "varchar(128)", model.DEFAULT_SCOPE)
// saveSchemaVersion(sqlStore, VERSION_3_9_0)
//}
diff --git a/store/sql_user_store.go b/store/sql_user_store.go
index 52e45ed7d..5ea04155d 100644
--- a/store/sql_user_store.go
+++ b/store/sql_user_store.go
@@ -389,8 +389,7 @@ func (us SqlUserStore) Get(id string) StoreChannel {
if obj, err := us.GetReplica().Get(model.User{}, id); err != nil {
result.Err = model.NewLocAppError("SqlUserStore.Get", "store.sql_user.get.app_error", nil, "user_id="+id+", "+err.Error())
} else if obj == nil {
- result.Err = model.NewLocAppError("SqlUserStore.Get", MISSING_ACCOUNT_ERROR, nil, "user_id="+id)
- result.Err.StatusCode = http.StatusNotFound
+ result.Err = model.NewAppError("SqlUserStore.Get", MISSING_ACCOUNT_ERROR, nil, "user_id="+id, http.StatusNotFound)
} else {
result.Data = obj.(*model.User)
}
diff --git a/store/store.go b/store/store.go
index b78d4a458..18f7374dc 100644
--- a/store/store.go
+++ b/store/store.go
@@ -246,9 +246,9 @@ type OAuthStore interface {
SaveApp(app *model.OAuthApp) StoreChannel
UpdateApp(app *model.OAuthApp) StoreChannel
GetApp(id string) StoreChannel
- GetAppByUser(userId string) StoreChannel
- GetApps() StoreChannel
- GetAuthorizedApps(userId string) StoreChannel
+ GetAppByUser(userId string, offset, limit int) StoreChannel
+ GetApps(offset, limit int) StoreChannel
+ GetAuthorizedApps(userId string, offset, limit int) StoreChannel
DeleteApp(id string) StoreChannel
SaveAuthData(authData *model.AuthData) StoreChannel
GetAuthData(code string) StoreChannel
diff --git a/webapp/tests/client/client_oauth.test.jsx b/webapp/tests/client/client_oauth.test.jsx
index acf35f18f..44ce95a19 100644
--- a/webapp/tests/client/client_oauth.test.jsx
+++ b/webapp/tests/client/client_oauth.test.jsx
@@ -12,7 +12,7 @@ describe('Client.OAuth', function() {
app.name = 'test';
app.homepage = 'homepage';
app.description = 'desc';
- app.callback_urls = '';
+ app.callback_urls = [''];
TestHelper.basicClient().registerOAuthApp(
app,
@@ -33,7 +33,7 @@ describe('Client.OAuth', function() {
TestHelper.basicClient().allowOAuth2(
'GET',
- '123456',
+ '12345678901234567890123456',
'http://nowhere.com',
'state',
'scope',