Merge pull request #693 from mattermost/mm-1419

Implement OAuth2 service provider functionality.
Этот коммит содержится в:
Christopher Speller
2015-09-16 16:26:56 -04:00
родитель 4e11fa23d7 47e6a33a45
Коммит b1f26540ce
45 изменённых файлов: 2506 добавлений и 199 удалений

Просмотреть файл

@@ -43,6 +43,7 @@ func InitApi() {
InitFile(r) InitFile(r)
InitCommand(r) InitCommand(r)
InitAdmin(r) InitAdmin(r)
InitOAuth(r)
templatesDir := utils.FindDir("api/templates") templatesDir := utils.FindDir("api/templates")
l4g.Debug("Parsing server templates at %v", templatesDir) l4g.Debug("Parsing server templates at %v", templatesDir)

Просмотреть файл

@@ -17,7 +17,7 @@ func Setup() {
NewServer() NewServer()
StartServer() StartServer()
InitApi() InitApi()
Client = model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port + "/api/v1") Client = model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port)
} }
} }

Просмотреть файл

@@ -62,7 +62,7 @@ func TestCreateChannel(t *testing.T) {
} }
} }
if _, err := Client.DoPost("/channels/create", "garbage"); err == nil { if _, err := Client.DoApiPost("/channels/create", "garbage"); err == nil {
t.Fatal("should have been an error") t.Fatal("should have been an error")
} }
@@ -627,7 +627,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
currentEtag = cache_result.Etag currentEtag = cache_result.Etag
} }
Client2 := model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port + "/api/v1") Client2 := model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port)
user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "tester2@test.com", Nickname: "Tester 2", Password: "pwd"} user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "tester2@test.com", Nickname: "Tester 2", Password: "pwd"}
user2 = Client2.Must(Client2.CreateUser(user2, "")).Data.(*model.User) user2 = Client2.Must(Client2.CreateUser(user2, "")).Data.(*model.User)

Просмотреть файл

@@ -315,7 +315,7 @@ func loadTestSetupCommand(c *Context, command *model.Command) bool {
numPosts, _ = strconv.Atoi(tokens[numArgs+2]) numPosts, _ = strconv.Atoi(tokens[numArgs+2])
} }
} }
client := model.NewClient(c.GetSiteURL() + "/api/v1") client := model.NewClient(c.GetSiteURL())
if doTeams { if doTeams {
if err := CreateBasicUser(client); err != nil { if err := CreateBasicUser(client); err != nil {
@@ -375,7 +375,7 @@ func loadTestUsersCommand(c *Context, command *model.Command) bool {
if err == false { if err == false {
usersr = utils.Range{10, 15} usersr = utils.Range{10, 15}
} }
client := model.NewClient(c.GetSiteURL() + "/api/v1") client := model.NewClient(c.GetSiteURL())
userCreator := NewAutoUserCreator(client, c.Session.TeamId) userCreator := NewAutoUserCreator(client, c.Session.TeamId)
userCreator.Fuzzy = doFuzz userCreator.Fuzzy = doFuzz
userCreator.CreateTestUsers(usersr) userCreator.CreateTestUsers(usersr)
@@ -405,7 +405,7 @@ func loadTestChannelsCommand(c *Context, command *model.Command) bool {
if err == false { if err == false {
channelsr = utils.Range{20, 30} channelsr = utils.Range{20, 30}
} }
client := model.NewClient(c.GetSiteURL() + "/api/v1") client := model.NewClient(c.GetSiteURL())
client.MockSession(c.Session.Id) client.MockSession(c.Session.Id)
channelCreator := NewAutoChannelCreator(client, c.Session.TeamId) channelCreator := NewAutoChannelCreator(client, c.Session.TeamId)
channelCreator.Fuzzy = doFuzz channelCreator.Fuzzy = doFuzz
@@ -457,7 +457,7 @@ func loadTestPostsCommand(c *Context, command *model.Command) bool {
} }
} }
client := model.NewClient(c.GetSiteURL() + "/api/v1") client := model.NewClient(c.GetSiteURL())
client.MockSession(c.Session.Id) client.MockSession(c.Session.Id)
testPoster := NewAutoPostCreator(client, command.ChannelId) testPoster := NewAutoPostCreator(client, command.ChannelId)
testPoster.Fuzzy = doFuzz testPoster.Fuzzy = doFuzz

Просмотреть файл

@@ -80,9 +80,36 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.RequestId = model.NewId() c.RequestId = model.NewId()
c.IpAddress = GetIpAddress(r) c.IpAddress = GetIpAddress(r)
token := ""
isTokenFromQueryString := false
// Attempt to parse token out of the header
authHeader := r.Header.Get(model.HEADER_AUTH)
if len(authHeader) > 6 && strings.ToUpper(authHeader[0:6]) == model.HEADER_BEARER {
// Default session token
token = authHeader[7:]
} else if len(authHeader) > 5 && strings.ToLower(authHeader[0:5]) == model.HEADER_TOKEN {
// OAuth token
token = authHeader[6:]
}
// Attempt to parse the token from the cookie
if len(token) == 0 {
if cookie, err := r.Cookie(model.SESSION_TOKEN); err == nil {
token = cookie.Value
}
}
// Attempt to parse token out of the query string
if len(token) == 0 {
token = r.URL.Query().Get("access_token")
isTokenFromQueryString = true
}
protocol := "http" protocol := "http"
// if the request came from the ELB then assume this is produciton // If the request came from the ELB then assume this is produciton
// and redirect all http requests to https // and redirect all http requests to https
if utils.Cfg.ServiceSettings.UseSSL { if utils.Cfg.ServiceSettings.UseSSL {
forwardProto := r.Header.Get(model.HEADER_FORWARDED_PROTO) forwardProto := r.Header.Get(model.HEADER_FORWARDED_PROTO)
@@ -105,36 +132,19 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Frame-Options", "DENY") w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("Content-Security-Policy", "frame-ancestors none") w.Header().Set("Content-Security-Policy", "frame-ancestors none")
} else { } else {
// All api response bodies will be JSON formatted // All api response bodies will be JSON formatted by default
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
} }
sessionId := "" if len(token) != 0 {
// attempt to parse the session token from the header
if ah := r.Header.Get(model.HEADER_AUTH); ah != "" {
if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" {
sessionId = ah[7:]
}
}
// attempt to parse the session token from the cookie
if sessionId == "" {
if cookie, err := r.Cookie(model.SESSION_TOKEN); err == nil {
sessionId = cookie.Value
}
}
if sessionId != "" {
var session *model.Session var session *model.Session
if ts, ok := sessionCache.Get(sessionId); ok { if ts, ok := sessionCache.Get(token); ok {
session = ts.(*model.Session) session = ts.(*model.Session)
} }
if session == nil { if session == nil {
if sessionResult := <-Srv.Store.Session().Get(sessionId); sessionResult.Err != nil { if sessionResult := <-Srv.Store.Session().Get(token); sessionResult.Err != nil {
c.LogError(model.NewAppError("ServeHTTP", "Invalid session", "id="+sessionId+", err="+sessionResult.Err.DetailedError)) c.LogError(model.NewAppError("ServeHTTP", "Invalid session", "token="+token+", err="+sessionResult.Err.DetailedError))
} else { } else {
session = sessionResult.Data.(*model.Session) session = sessionResult.Data.(*model.Session)
} }
@@ -142,7 +152,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if session == nil || session.IsExpired() { if session == nil || session.IsExpired() {
c.RemoveSessionCookie(w) c.RemoveSessionCookie(w)
c.Err = model.NewAppError("ServeHTTP", "Invalid or expired session, please login again.", "id="+sessionId) c.Err = model.NewAppError("ServeHTTP", "Invalid or expired session, please login again.", "token="+token)
c.Err.StatusCode = http.StatusUnauthorized
} else if !session.IsOAuth && isTokenFromQueryString {
c.Err = model.NewAppError("ServeHTTP", "Session is not OAuth but token was provided in the query string", "token="+token)
c.Err.StatusCode = http.StatusUnauthorized c.Err.StatusCode = http.StatusUnauthorized
} else { } else {
c.Session = *session c.Session = *session
@@ -166,10 +179,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c.SystemAdminRequired() c.SystemAdminRequired()
} }
if c.Err == nil && h.isUserActivity && sessionId != "" && len(c.Session.UserId) > 0 { if c.Err == nil && h.isUserActivity && token != "" && len(c.Session.UserId) > 0 {
go func() { go func() {
if err := (<-Srv.Store.User().UpdateUserAndSessionActivity(c.Session.UserId, sessionId, model.GetMillis())).Err; err != nil { if err := (<-Srv.Store.User().UpdateUserAndSessionActivity(c.Session.UserId, c.Session.Id, model.GetMillis())).Err; err != nil {
l4g.Error("Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v", c.Session.UserId, sessionId, err) l4g.Error("Failed to update LastActivityAt for user_id=%v and session_id=%v, err=%v", c.Session.UserId, c.Session.Id, err)
} }
}() }()
} }
@@ -197,7 +210,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
func (c *Context) LogAudit(extraInfo string) { func (c *Context) LogAudit(extraInfo string) {
audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.AltId} audit := &model.Audit{UserId: c.Session.UserId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
if r := <-Srv.Store.Audit().Save(audit); r.Err != nil { if r := <-Srv.Store.Audit().Save(audit); r.Err != nil {
c.LogError(r.Err) c.LogError(r.Err)
} }
@@ -209,7 +222,7 @@ func (c *Context) LogAuditWithUserId(userId, extraInfo string) {
extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId) extraInfo = strings.TrimSpace(extraInfo + " session_user=" + c.Session.UserId)
} }
audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.AltId} audit := &model.Audit{UserId: userId, IpAddress: c.IpAddress, Action: c.Path, ExtraInfo: extraInfo, SessionId: c.Session.Id}
if r := <-Srv.Store.Audit().Save(audit); r.Err != nil { if r := <-Srv.Store.Audit().Save(audit); r.Err != nil {
c.LogError(r.Err) c.LogError(r.Err)
} }
@@ -315,7 +328,7 @@ func (c *Context) IsTeamAdmin(userId string) bool {
func (c *Context) RemoveSessionCookie(w http.ResponseWriter) { func (c *Context) RemoveSessionCookie(w http.ResponseWriter) {
sessionCache.Remove(c.Session.Id) sessionCache.Remove(c.Session.Token)
cookie := &http.Cookie{ cookie := &http.Cookie{
Name: model.SESSION_TOKEN, Name: model.SESSION_TOKEN,
@@ -471,3 +484,7 @@ func Handle404(w http.ResponseWriter, r *http.Request) {
l4g.Error("%v: code=404 ip=%v", r.URL.Path, GetIpAddress(r)) l4g.Error("%v: code=404 ip=%v", r.URL.Path, GetIpAddress(r))
RenderWebError(err, w, r) RenderWebError(err, w, r)
} }
func AddSessionToCache(session *model.Session) {
sessionCache.Add(session.Token, session)
}

165
api/oauth.go Обычный файл
Просмотреть файл

@@ -0,0 +1,165 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
l4g "code.google.com/p/log4go"
"fmt"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"net/http"
"net/url"
)
func InitOAuth(r *mux.Router) {
l4g.Debug("Initializing oauth api routes")
sr := r.PathPrefix("/oauth").Subrouter()
sr.Handle("/register", ApiUserRequired(registerOAuthApp)).Methods("POST")
sr.Handle("/allow", ApiUserRequired(allowOAuth)).Methods("GET")
}
func registerOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
c.Err = model.NewAppError("registerOAuthApp", "The system admin has turned off OAuth service providing.", "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
app := model.OAuthAppFromJson(r.Body)
if app == nil {
c.SetInvalidParam("registerOAuthApp", "app")
return
}
secret := model.NewId()
app.ClientSecret = secret
app.CreatorId = c.Session.UserId
if result := <-Srv.Store.OAuth().SaveApp(app); result.Err != nil {
c.Err = result.Err
return
} else {
app = result.Data.(*model.OAuthApp)
app.ClientSecret = secret
c.LogAudit("client_id=" + app.Id)
w.Write([]byte(app.ToJson()))
return
}
}
func allowOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
c.Err = model.NewAppError("allowOAuth", "The system admin has turned off OAuth service providing.", "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
c.LogAudit("attempt")
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
responseData := map[string]string{}
responseType := r.URL.Query().Get("response_type")
if len(responseType) == 0 {
c.Err = model.NewAppError("allowOAuth", "invalid_request: Bad response_type", "")
return
}
clientId := r.URL.Query().Get("client_id")
if len(clientId) != 26 {
c.Err = model.NewAppError("allowOAuth", "invalid_request: Bad client_id", "")
return
}
redirectUri := r.URL.Query().Get("redirect_uri")
if len(redirectUri) == 0 {
c.Err = model.NewAppError("allowOAuth", "invalid_request: Missing or bad redirect_uri", "")
return
}
scope := r.URL.Query().Get("scope")
state := r.URL.Query().Get("state")
var app *model.OAuthApp
if result := <-Srv.Store.OAuth().GetApp(clientId); result.Err != nil {
c.Err = model.NewAppError("allowOAuth", "server_error: Error accessing the database", "")
return
} else {
app = result.Data.(*model.OAuthApp)
}
if !app.IsValidRedirectURL(redirectUri) {
c.LogAudit("fail - redirect_uri did not match registered callback")
c.Err = model.NewAppError("allowOAuth", "invalid_request: Supplied redirect_uri did not match registered callback_url", "")
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))
if result := <-Srv.Store.OAuth().SaveAuthData(authData); result.Err != nil {
responseData["redirect"] = redirectUri + "?error=server_error&state=" + state
w.Write([]byte(model.MapToJson(responseData)))
return
}
c.LogAudit("success")
responseData["redirect"] = redirectUri + "?code=" + url.QueryEscape(authData.Code) + "&state=" + url.QueryEscape(authData.State)
w.Write([]byte(model.MapToJson(responseData)))
}
func RevokeAccessToken(token string) *model.AppError {
schan := Srv.Store.Session().Remove(token)
sessionCache.Remove(token)
var accessData *model.AccessData
if result := <-Srv.Store.OAuth().GetAccessData(token); result.Err != nil {
return model.NewAppError("RevokeAccessToken", "Error getting access token from DB before deletion", "")
} else {
accessData = result.Data.(*model.AccessData)
}
tchan := Srv.Store.OAuth().RemoveAccessData(token)
cchan := Srv.Store.OAuth().RemoveAuthData(accessData.AuthCode)
if result := <-tchan; result.Err != nil {
return model.NewAppError("RevokeAccessToken", "Error deleting access token from DB", "")
}
if result := <-cchan; result.Err != nil {
return model.NewAppError("RevokeAccessToken", "Error deleting authorization code from DB", "")
}
if result := <-schan; result.Err != nil {
return model.NewAppError("RevokeAccessToken", "Error deleting session from DB", "")
}
return nil
}
func GetAuthData(code string) *model.AuthData {
if result := <-Srv.Store.OAuth().GetAuthData(code); result.Err != nil {
l4g.Error("Couldn't find auth code for code=%s", code)
return nil
} else {
return result.Data.(*model.AuthData)
}
}

157
api/oauth_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,157 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package api
import (
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
"net/url"
"strings"
"testing"
)
func TestRegisterApp(t *testing.T) {
Setup()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
if _, err := Client.RegisterApp(app); err == nil {
t.Fatal("should have failed - oauth providing turned off")
}
} else {
Client.Logout()
if _, err := Client.RegisterApp(app); err == nil {
t.Fatal("not logged in - should have failed")
}
Client.Must(Client.LoginById(ruser.Id, "pwd"))
if result, err := Client.RegisterApp(app); err != nil {
t.Fatal(err)
} else {
rapp := result.Data.(*model.OAuthApp)
if len(rapp.Id) != 26 {
t.Fatal("clientid didn't return properly")
}
if len(rapp.ClientSecret) != 26 {
t.Fatal("client secret didn't return properly")
}
}
app = &model.OAuthApp{Name: "", Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
if _, err := Client.RegisterApp(app); err == nil {
t.Fatal("missing name - should have failed")
}
app = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
if _, err := Client.RegisterApp(app); err == nil {
t.Fatal("missing homepage - should have failed")
}
app = &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{}}
if _, err := Client.RegisterApp(app); err == nil {
t.Fatal("missing callback url - should have failed")
}
}
}
func TestAllowOAuth(t *testing.T) {
Setup()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := Client.CreateTeam(&team)
user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Password: "pwd"}
ruser := Client.Must(Client.CreateUser(&user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(ruser.Id))
app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
Client.Must(Client.LoginById(ruser.Id, "pwd"))
state := "123"
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "12345678901234567890123456", app.CallbackUrls[0], "all", state); err == nil {
t.Fatal("should have failed - oauth service providing turned off")
}
} else {
app = Client.Must(Client.RegisterApp(app)).Data.(*model.OAuthApp)
if result, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, app.CallbackUrls[0], "all", state); err != nil {
t.Fatal(err)
} else {
redirect := result.Data.(map[string]string)["redirect"]
if len(redirect) == 0 {
t.Fatal("redirect url should be set")
}
ru, _ := url.Parse(redirect)
if ru == nil {
t.Fatal("redirect url unparseable")
} else {
if len(ru.Query().Get("code")) == 0 {
t.Fatal("authorization code not returned")
}
if ru.Query().Get("state") != state {
t.Fatal("returned state doesn't match")
}
}
}
if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "", "all", state); err == nil {
t.Fatal("should have failed - no redirect_url given")
}
if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "", "", state); err == nil {
t.Fatal("should have failed - no redirect_url given")
}
if result, err := Client.AllowOAuth("junk", app.Id, app.CallbackUrls[0], "all", state); err != nil {
t.Fatal(err)
} else {
redirect := result.Data.(map[string]string)["redirect"]
if len(redirect) == 0 {
t.Fatal("redirect url should be set")
}
ru, _ := url.Parse(redirect)
if ru == nil {
t.Fatal("redirect url unparseable")
} else {
if ru.Query().Get("error") != "unsupported_response_type" {
t.Fatal("wrong error returned")
}
if ru.Query().Get("state") != state {
t.Fatal("returned state doesn't match")
}
}
}
if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "", app.CallbackUrls[0], "all", state); err == nil {
t.Fatal("should have failed - empty client id")
}
if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, "junk", app.CallbackUrls[0], "all", state); err == nil {
t.Fatal("should have failed - bad client id")
}
if _, err := Client.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, "https://somewhereelse.com", "all", state); err == nil {
t.Fatal("should have failed - redirect uri host does not match app host")
}
}
}

Просмотреть файл

@@ -118,7 +118,7 @@ func TestCreatePost(t *testing.T) {
t.Fatal("Should have been forbidden") t.Fatal("Should have been forbidden")
} }
if _, err = Client.DoPost("/channels/"+channel3.Id+"/create", "garbage"); err == nil { if _, err = Client.DoApiPost("/channels/"+channel3.Id+"/create", "garbage"); err == nil {
t.Fatal("should have been an error") t.Fatal("should have been an error")
} }
} }
@@ -203,7 +203,7 @@ func TestCreateValetPost(t *testing.T) {
t.Fatal("Should have been forbidden") t.Fatal("Should have been forbidden")
} }
if _, err = Client.DoPost("/channels/"+channel3.Id+"/create", "garbage"); err == nil { if _, err = Client.DoApiPost("/channels/"+channel3.Id+"/create", "garbage"); err == nil {
t.Fatal("should have been an error") t.Fatal("should have been an error")
} }
} else { } else {

Просмотреть файл

@@ -103,7 +103,7 @@ func TestCreateTeam(t *testing.T) {
} }
} }
if _, err := Client.DoPost("/teams/create", "garbage"); err == nil { if _, err := Client.DoApiPost("/teams/create", "garbage"); err == nil {
t.Fatal("should have been an error") t.Fatal("should have been an error")
} }
} }

Просмотреть файл

@@ -336,7 +336,7 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
return return
} }
session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, DeviceId: deviceId} session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, DeviceId: deviceId, IsOAuth: false}
maxAge := model.SESSION_TIME_WEB_IN_SECS maxAge := model.SESSION_TIME_WEB_IN_SECS
@@ -378,13 +378,13 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
return return
} else { } else {
session = result.Data.(*model.Session) session = result.Data.(*model.Session)
sessionCache.Add(session.Id, session) AddSessionToCache(session)
} }
w.Header().Set(model.HEADER_TOKEN, session.Id) w.Header().Set(model.HEADER_TOKEN, session.Token)
sessionCookie := &http.Cookie{ sessionCookie := &http.Cookie{
Name: model.SESSION_TOKEN, Name: model.SESSION_TOKEN,
Value: session.Id, Value: session.Token,
Path: "/", Path: "/",
MaxAge: maxAge, MaxAge: maxAge,
HttpOnly: true, HttpOnly: true,
@@ -430,25 +430,27 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) { func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body) props := model.MapFromJson(r.Body)
altId := props["id"] id := props["id"]
if result := <-Srv.Store.Session().GetSessions(c.Session.UserId); result.Err != nil { if result := <-Srv.Store.Session().Get(id); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return
} else { } else {
sessions := result.Data.([]*model.Session) session := result.Data.(*model.Session)
for _, session := range sessions { c.LogAudit("session_id=" + session.Id)
if session.AltId == altId {
c.LogAudit("session_id=" + session.AltId) if session.IsOAuth {
sessionCache.Remove(session.Id) RevokeAccessToken(session.Token)
if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil { } else {
c.Err = result.Err sessionCache.Remove(session.Token)
return
} else { if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
w.Write([]byte(model.MapToJson(props))) c.Err = result.Err
return return
} } else {
w.Write([]byte(model.MapToJson(props)))
return
} }
} }
} }
@@ -462,8 +464,8 @@ func RevokeAllSession(c *Context, userId string) {
sessions := result.Data.([]*model.Session) sessions := result.Data.([]*model.Session)
for _, session := range sessions { for _, session := range sessions {
c.LogAuditWithUserId(userId, "session_id="+session.AltId) c.LogAuditWithUserId(userId, "session_id="+session.Id)
sessionCache.Remove(session.Id) sessionCache.Remove(session.Token)
if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil { if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
c.Err = result.Err c.Err = result.Err
return return

Просмотреть файл

@@ -68,7 +68,7 @@ func TestCreateUser(t *testing.T) {
} }
} }
if _, err := Client.DoPost("/users/create", "garbage"); err == nil { if _, err := Client.DoApiPost("/users/create", "garbage"); err == nil {
t.Fatal("should have been an error") t.Fatal("should have been an error")
} }
} }
@@ -190,11 +190,11 @@ func TestSessions(t *testing.T) {
for _, session := range sessions { for _, session := range sessions {
if session.DeviceId == deviceId { if session.DeviceId == deviceId {
otherSession = session.AltId otherSession = session.Id
} }
if len(session.Id) != 0 { if len(session.Token) != 0 {
t.Fatal("shouldn't return sessions") t.Fatal("shouldn't return session tokens")
} }
} }
@@ -212,11 +212,6 @@ func TestSessions(t *testing.T) {
if len(sessions2) != 1 { if len(sessions2) != 1 {
t.Fatal("invalid number of sessions") t.Fatal("invalid number of sessions")
} }
if _, err := Client.RevokeSession(otherSession); err != nil {
t.Fatal(err)
}
} }
func TestGetUser(t *testing.T) { func TestGetUser(t *testing.T) {
@@ -355,7 +350,7 @@ func TestUserCreateImage(t *testing.T) {
Client.LoginByEmail(team.Name, user.Email, "pwd") Client.LoginByEmail(team.Name, user.Email, "pwd")
Client.DoGet("/users/"+user.Id+"/image", "", "") Client.DoApiGet("/users/"+user.Id+"/image", "", "")
if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
var auth aws.Auth var auth aws.Auth
@@ -453,7 +448,7 @@ func TestUserUploadProfileImage(t *testing.T) {
t.Fatal(upErr) t.Fatal(upErr)
} }
Client.DoGet("/users/"+user.Id+"/image", "", "") Client.DoApiGet("/users/"+user.Id+"/image", "", "")
if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
var auth aws.Auth var auth aws.Auth

Просмотреть файл

@@ -23,7 +23,8 @@
"UseLocalStorage": true, "UseLocalStorage": true,
"StorageDirectory": "./data/", "StorageDirectory": "./data/",
"AllowedLoginAttempts": 10, "AllowedLoginAttempts": 10,
"DisableEmailSignUp": false "DisableEmailSignUp": false,
"EnableOAuthServiceProvider": false
}, },
"SSOSettings": { "SSOSettings": {
"gitlab": { "gitlab": {

Просмотреть файл

@@ -23,7 +23,8 @@
"UseLocalStorage": true, "UseLocalStorage": true,
"StorageDirectory": "/mattermost/data/", "StorageDirectory": "/mattermost/data/",
"AllowedLoginAttempts": 10, "AllowedLoginAttempts": 10,
"DisableEmailSignUp": false "DisableEmailSignUp": false,
"EnableOAuthServiceProvider": false
}, },
"SSOSettings": { "SSOSettings": {
"gitlab": { "gitlab": {

Просмотреть файл

@@ -23,7 +23,8 @@
"UseLocalStorage": true, "UseLocalStorage": true,
"StorageDirectory": "/mattermost/data/", "StorageDirectory": "/mattermost/data/",
"AllowedLoginAttempts": 10, "AllowedLoginAttempts": 10,
"DisableEmailSignUp": false "DisableEmailSignUp": false,
"EnableOAuthServiceProvider": false
}, },
"SSOSettings": { "SSOSettings": {
"gitlab": { "gitlab": {

Просмотреть файл

@@ -53,7 +53,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
} }
// Create a client for tests to use // Create a client for tests to use
client := model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port + "/api/v1") client := model.NewClient("http://localhost:" + utils.Cfg.ServiceSettings.Port)
// Check for username parameter and create a user if present // Check for username parameter and create a user if present
username, ok1 := params["username"] username, ok1 := params["username"]
@@ -65,7 +65,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
// Create team for testing // Create team for testing
team := &model.Team{ team := &model.Team{
DisplayName: teamDisplayName[0], DisplayName: teamDisplayName[0],
Name: utils.RandomName(utils.Range{20, 20}, utils.LOWERCASE), Name: utils.RandomName(utils.Range{20, 20}, utils.LOWERCASE),
Email: utils.RandomEmail(utils.Range{20, 20}, utils.LOWERCASE), Email: utils.RandomEmail(utils.Range{20, 20}, utils.LOWERCASE),
Type: model.TEAM_OPEN, Type: model.TEAM_OPEN,
} }

Просмотреть файл

@@ -9,17 +9,69 @@ import (
) )
const ( const (
ACCESS_TOKEN_GRANT_TYPE = "authorization_code" ACCESS_TOKEN_GRANT_TYPE = "authorization_code"
ACCESS_TOKEN_TYPE = "bearer" ACCESS_TOKEN_TYPE = "bearer"
REFRESH_TOKEN_GRANT_TYPE = "refresh_token"
) )
type AccessData struct {
AuthCode string `json:"auth_code"`
Token string `json"token"`
RefreshToken string `json:"refresh_token"`
RedirectUri string `json:"redirect_uri"`
}
type AccessResponse struct { type AccessResponse struct {
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
TokenType string `json:"token_type"` TokenType string `json:"token_type"`
ExpiresIn int32 `json:"expires_in"` ExpiresIn int32 `json:"expires_in"`
Scope string `json:"scope"`
RefreshToken string `json:"refresh_token"` RefreshToken string `json:"refresh_token"`
} }
// IsValid validates the AccessData and returns an error if it isn't configured
// correctly.
func (ad *AccessData) IsValid() *AppError {
if len(ad.AuthCode) == 0 || len(ad.AuthCode) > 128 {
return NewAppError("AccessData.IsValid", "Invalid auth code", "")
}
if len(ad.Token) != 26 {
return NewAppError("AccessData.IsValid", "Invalid access token", "")
}
if len(ad.RefreshToken) > 26 {
return NewAppError("AccessData.IsValid", "Invalid refresh token", "")
}
if len(ad.RedirectUri) > 256 {
return NewAppError("AccessData.IsValid", "Invalid redirect uri", "")
}
return nil
}
func (ad *AccessData) ToJson() string {
b, err := json.Marshal(ad)
if err != nil {
return ""
} else {
return string(b)
}
}
func AccessDataFromJson(data io.Reader) *AccessData {
decoder := json.NewDecoder(data)
var ad AccessData
err := decoder.Decode(&ad)
if err == nil {
return &ad
} else {
return nil
}
}
func (ar *AccessResponse) ToJson() string { func (ar *AccessResponse) ToJson() string {
b, err := json.Marshal(ar) b, err := json.Marshal(ar)
if err != nil { if err != nil {

41
model/access_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,41 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestAccessJson(t *testing.T) {
a1 := AccessData{}
a1.AuthCode = NewId()
a1.Token = NewId()
a1.RefreshToken = NewId()
json := a1.ToJson()
ra1 := AccessDataFromJson(strings.NewReader(json))
if a1.Token != ra1.Token {
t.Fatal("tokens didn't match")
}
}
func TestAccessIsValid(t *testing.T) {
ad := AccessData{}
if err := ad.IsValid(); err == nil {
t.Fatal("should have failed")
}
ad.AuthCode = NewId()
if err := ad.IsValid(); err == nil {
t.Fatal("should have failed")
}
ad.Token = NewId()
if err := ad.IsValid(); err != nil {
t.Fatal(err)
}
}

103
model/authorize.go Обычный файл
Просмотреть файл

@@ -0,0 +1,103 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"io"
)
const (
AUTHCODE_EXPIRE_TIME = 60 * 10 // 10 minutes
AUTHCODE_RESPONSE_TYPE = "code"
)
type AuthData struct {
ClientId string `json:"client_id"`
UserId string `json:"user_id"`
Code string `json:"code"`
ExpiresIn int32 `json:"expires_in"`
CreateAt int64 `json:"create_at"`
RedirectUri string `json:"redirect_uri"`
State string `json:"state"`
Scope string `json:"scope"`
}
// IsValid validates the AuthData and returns an error if it isn't configured
// correctly.
func (ad *AuthData) IsValid() *AppError {
if len(ad.ClientId) != 26 {
return NewAppError("AuthData.IsValid", "Invalid client id", "")
}
if len(ad.UserId) != 26 {
return NewAppError("AuthData.IsValid", "Invalid user id", "")
}
if len(ad.Code) == 0 || len(ad.Code) > 128 {
return NewAppError("AuthData.IsValid", "Invalid authorization code", "client_id="+ad.ClientId)
}
if ad.ExpiresIn == 0 {
return NewAppError("AuthData.IsValid", "Expires in must be set", "")
}
if ad.CreateAt <= 0 {
return NewAppError("AuthData.IsValid", "Create at must be a valid time", "client_id="+ad.ClientId)
}
if len(ad.RedirectUri) > 256 {
return NewAppError("AuthData.IsValid", "Invalid redirect uri", "client_id="+ad.ClientId)
}
if len(ad.State) > 128 {
return NewAppError("AuthData.IsValid", "Invalid state", "client_id="+ad.ClientId)
}
if len(ad.Scope) > 128 {
return NewAppError("AuthData.IsValid", "Invalid scope", "client_id="+ad.ClientId)
}
return nil
}
func (ad *AuthData) PreSave() {
if ad.ExpiresIn == 0 {
ad.ExpiresIn = AUTHCODE_EXPIRE_TIME
}
if ad.CreateAt == 0 {
ad.CreateAt = GetMillis()
}
}
func (ad *AuthData) ToJson() string {
b, err := json.Marshal(ad)
if err != nil {
return ""
} else {
return string(b)
}
}
func AuthDataFromJson(data io.Reader) *AuthData {
decoder := json.NewDecoder(data)
var ad AuthData
err := decoder.Decode(&ad)
if err == nil {
return &ad
} else {
return nil
}
}
func (ad *AuthData) IsExpired() bool {
if GetMillis() > ad.CreateAt+int64(ad.ExpiresIn*1000) {
return true
}
return false
}

66
model/authorize_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,66 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestAuthJson(t *testing.T) {
a1 := AuthData{}
a1.ClientId = NewId()
a1.UserId = NewId()
a1.Code = NewId()
json := a1.ToJson()
ra1 := AuthDataFromJson(strings.NewReader(json))
if a1.Code != ra1.Code {
t.Fatal("codes didn't match")
}
}
func TestAuthPreSave(t *testing.T) {
a1 := AuthData{}
a1.ClientId = NewId()
a1.UserId = NewId()
a1.Code = NewId()
a1.PreSave()
a1.IsExpired()
}
func TestAuthIsValid(t *testing.T) {
ad := AuthData{}
if err := ad.IsValid(); err == nil {
t.Fatal()
}
ad.ClientId = NewId()
if err := ad.IsValid(); err == nil {
t.Fatal()
}
ad.UserId = NewId()
if err := ad.IsValid(); err == nil {
t.Fatal()
}
ad.Code = NewId()
if err := ad.IsValid(); err == nil {
t.Fatal()
}
ad.ExpiresIn = 1
if err := ad.IsValid(); err == nil {
t.Fatal()
}
ad.CreateAt = 1
if err := ad.IsValid(); err != nil {
t.Fatal()
}
}

Просмотреть файл

@@ -23,7 +23,9 @@ const (
HEADER_FORWARDED = "X-Forwarded-For" HEADER_FORWARDED = "X-Forwarded-For"
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto" HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
HEADER_TOKEN = "token" HEADER_TOKEN = "token"
HEADER_BEARER = "BEARER"
HEADER_AUTH = "Authorization" HEADER_AUTH = "Authorization"
API_URL_SUFFIX = "/api/v1"
) )
type Result struct { type Result struct {
@@ -33,22 +35,37 @@ type Result struct {
} }
type Client struct { type Client struct {
Url string // The location of the server like "http://localhost/api/v1" Url string // The location of the server like "http://localhost:8065"
ApiUrl string // The api location of the server like "http://localhost:8065/api/v1"
HttpClient *http.Client // The http client HttpClient *http.Client // The http client
AuthToken string AuthToken string
AuthType string
} }
// NewClient constructs a new client with convienence methods for talking to // NewClient constructs a new client with convienence methods for talking to
// the server. // the server.
func NewClient(url string) *Client { func NewClient(url string) *Client {
return &Client{url, &http.Client{}, ""} return &Client{url, url + API_URL_SUFFIX, &http.Client{}, "", ""}
} }
func (c *Client) DoPost(url string, data string) (*http.Response, *AppError) { func (c *Client) DoPost(url string, data, contentType string) (*http.Response, *AppError) {
rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data)) rq, _ := http.NewRequest("POST", c.Url+url, strings.NewReader(data))
rq.Header.Set("Content-Type", contentType)
if rp, err := c.HttpClient.Do(rq); err != nil {
return nil, NewAppError(url, "We encountered an error while connecting to the server", err.Error())
} else if rp.StatusCode >= 300 {
return nil, AppErrorFromJson(rp.Body)
} else {
return rp, nil
}
}
func (c *Client) DoApiPost(url string, data string) (*http.Response, *AppError) {
rq, _ := http.NewRequest("POST", c.ApiUrl+url, strings.NewReader(data))
if len(c.AuthToken) > 0 { if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
} }
if rp, err := c.HttpClient.Do(rq); err != nil { if rp, err := c.HttpClient.Do(rq); err != nil {
@@ -60,15 +77,15 @@ func (c *Client) DoPost(url string, data string) (*http.Response, *AppError) {
} }
} }
func (c *Client) DoGet(url string, data string, etag string) (*http.Response, *AppError) { func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) {
rq, _ := http.NewRequest("GET", c.Url+url, strings.NewReader(data)) rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data))
if len(etag) > 0 { if len(etag) > 0 {
rq.Header.Set(HEADER_ETAG_CLIENT, etag) rq.Header.Set(HEADER_ETAG_CLIENT, etag)
} }
if len(c.AuthToken) > 0 { if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) rq.Header.Set(HEADER_AUTH, c.AuthType+" "+c.AuthToken)
} }
if rp, err := c.HttpClient.Do(rq); err != nil { if rp, err := c.HttpClient.Do(rq); err != nil {
@@ -106,7 +123,7 @@ func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppErro
m := make(map[string]string) m := make(map[string]string)
m["email"] = email m["email"] = email
m["display_name"] = displayName m["display_name"] = displayName
if r, err := c.DoPost("/teams/signup", MapToJson(m)); err != nil { if r, err := c.DoApiPost("/teams/signup", MapToJson(m)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -115,7 +132,7 @@ func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppErro
} }
func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) { func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppError) {
if r, err := c.DoPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil { if r, err := c.DoApiPost("/teams/create_from_signup", teamSignup.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -124,7 +141,7 @@ func (c *Client) CreateTeamFromSignup(teamSignup *TeamSignup) (*Result, *AppErro
} }
func (c *Client) CreateTeam(team *Team) (*Result, *AppError) { func (c *Client) CreateTeam(team *Team) (*Result, *AppError) {
if r, err := c.DoPost("/teams/create", team.ToJson()); err != nil { if r, err := c.DoApiPost("/teams/create", team.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -136,7 +153,7 @@ func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppErro
m := make(map[string]string) m := make(map[string]string)
m["name"] = name m["name"] = name
m["all"] = fmt.Sprintf("%v", allServers) m["all"] = fmt.Sprintf("%v", allServers)
if r, err := c.DoPost("/teams/find_team_by_name", MapToJson(m)); err != nil { if r, err := c.DoApiPost("/teams/find_team_by_name", MapToJson(m)); err != nil {
return nil, err return nil, err
} else { } else {
val := false val := false
@@ -152,7 +169,7 @@ func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppErro
func (c *Client) FindTeams(email string) (*Result, *AppError) { func (c *Client) FindTeams(email string) (*Result, *AppError) {
m := make(map[string]string) m := make(map[string]string)
m["email"] = email m["email"] = email
if r, err := c.DoPost("/teams/find_teams", MapToJson(m)); err != nil { if r, err := c.DoApiPost("/teams/find_teams", MapToJson(m)); err != nil {
return nil, err return nil, err
} else { } else {
@@ -164,7 +181,7 @@ func (c *Client) FindTeams(email string) (*Result, *AppError) {
func (c *Client) FindTeamsSendEmail(email string) (*Result, *AppError) { func (c *Client) FindTeamsSendEmail(email string) (*Result, *AppError) {
m := make(map[string]string) m := make(map[string]string)
m["email"] = email m["email"] = email
if r, err := c.DoPost("/teams/email_teams", MapToJson(m)); err != nil { if r, err := c.DoApiPost("/teams/email_teams", MapToJson(m)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -173,7 +190,7 @@ func (c *Client) FindTeamsSendEmail(email string) (*Result, *AppError) {
} }
func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) { func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) {
if r, err := c.DoPost("/teams/invite_members", invites.ToJson()); err != nil { if r, err := c.DoApiPost("/teams/invite_members", invites.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -182,7 +199,7 @@ func (c *Client) InviteMembers(invites *Invites) (*Result, *AppError) {
} }
func (c *Client) UpdateTeamDisplayName(data map[string]string) (*Result, *AppError) { func (c *Client) UpdateTeamDisplayName(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/teams/update_name", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/teams/update_name", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -191,7 +208,7 @@ func (c *Client) UpdateTeamDisplayName(data map[string]string) (*Result, *AppErr
} }
func (c *Client) UpdateValetFeature(data map[string]string) (*Result, *AppError) { func (c *Client) UpdateValetFeature(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/teams/update_valet_feature", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/teams/update_valet_feature", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -200,7 +217,7 @@ func (c *Client) UpdateValetFeature(data map[string]string) (*Result, *AppError)
} }
func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) { func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) {
if r, err := c.DoPost("/users/create", user.ToJson()); err != nil { if r, err := c.DoApiPost("/users/create", user.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -209,7 +226,7 @@ func (c *Client) CreateUser(user *User, hash string) (*Result, *AppError) {
} }
func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Result, *AppError) { func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Result, *AppError) {
if r, err := c.DoPost("/users/create?d="+data+"&h="+hash, user.ToJson()); err != nil { if r, err := c.DoApiPost("/users/create?d="+data+"&h="+hash, user.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -218,7 +235,7 @@ func (c *Client) CreateUserFromSignup(user *User, data string, hash string) (*Re
} }
func (c *Client) GetUser(id string, etag string) (*Result, *AppError) { func (c *Client) GetUser(id string, etag string) (*Result, *AppError) {
if r, err := c.DoGet("/users/"+id, "", etag); err != nil { if r, err := c.DoApiGet("/users/"+id, "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -227,7 +244,7 @@ func (c *Client) GetUser(id string, etag string) (*Result, *AppError) {
} }
func (c *Client) GetMe(etag string) (*Result, *AppError) { func (c *Client) GetMe(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/users/me", "", etag); err != nil { if r, err := c.DoApiGet("/users/me", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -236,7 +253,7 @@ func (c *Client) GetMe(etag string) (*Result, *AppError) {
} }
func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) { func (c *Client) GetProfiles(teamId string, etag string) (*Result, *AppError) {
if r, err := c.DoGet("/users/profiles", "", etag); err != nil { if r, err := c.DoApiGet("/users/profiles", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -269,13 +286,14 @@ func (c *Client) LoginByEmailWithDevice(name string, email string, password stri
} }
func (c *Client) login(m map[string]string) (*Result, *AppError) { func (c *Client) login(m map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/login", MapToJson(m)); err != nil { if r, err := c.DoApiPost("/users/login", MapToJson(m)); err != nil {
return nil, err return nil, err
} else { } else {
c.AuthToken = r.Header.Get(HEADER_TOKEN) c.AuthToken = r.Header.Get(HEADER_TOKEN)
sessionId := getCookie(SESSION_TOKEN, r) c.AuthType = HEADER_BEARER
sessionToken := getCookie(SESSION_TOKEN, r)
if c.AuthToken != sessionId.Value { if c.AuthToken != sessionToken.Value {
NewAppError("/users/login", "Authentication tokens didn't match", "") NewAppError("/users/login", "Authentication tokens didn't match", "")
} }
@@ -285,21 +303,32 @@ func (c *Client) login(m map[string]string) (*Result, *AppError) {
} }
func (c *Client) Logout() (*Result, *AppError) { func (c *Client) Logout() (*Result, *AppError) {
if r, err := c.DoPost("/users/logout", ""); err != nil { if r, err := c.DoApiPost("/users/logout", ""); err != nil {
return nil, err return nil, err
} else { } else {
c.AuthToken = "" c.AuthToken = ""
c.AuthType = HEADER_BEARER
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
} }
} }
func (c *Client) SetOAuthToken(token string) {
c.AuthToken = token
c.AuthType = HEADER_TOKEN
}
func (c *Client) ClearOAuthToken() {
c.AuthToken = ""
c.AuthType = HEADER_BEARER
}
func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) { func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) {
m := make(map[string]string) m := make(map[string]string)
m["id"] = sessionAltId m["id"] = sessionAltId
if r, err := c.DoPost("/users/revoke_session", MapToJson(m)); err != nil { if r, err := c.DoApiPost("/users/revoke_session", MapToJson(m)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -308,7 +337,7 @@ func (c *Client) RevokeSession(sessionAltId string) (*Result, *AppError) {
} }
func (c *Client) GetSessions(id string) (*Result, *AppError) { func (c *Client) GetSessions(id string) (*Result, *AppError) {
if r, err := c.DoGet("/users/"+id+"/sessions", "", ""); err != nil { if r, err := c.DoApiGet("/users/"+id+"/sessions", "", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -321,7 +350,7 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul
m["command"] = command m["command"] = command
m["channelId"] = channelId m["channelId"] = channelId
m["suggest"] = strconv.FormatBool(suggest) m["suggest"] = strconv.FormatBool(suggest)
if r, err := c.DoPost("/command", MapToJson(m)); err != nil { if r, err := c.DoApiPost("/command", MapToJson(m)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -330,7 +359,7 @@ func (c *Client) Command(channelId string, command string, suggest bool) (*Resul
} }
func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) { func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) {
if r, err := c.DoGet("/users/"+id+"/audits", "", etag); err != nil { if r, err := c.DoApiGet("/users/"+id+"/audits", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -339,7 +368,7 @@ func (c *Client) GetAudits(id string, etag string) (*Result, *AppError) {
} }
func (c *Client) GetLogs() (*Result, *AppError) { func (c *Client) GetLogs() (*Result, *AppError) {
if r, err := c.DoGet("/admin/logs", "", ""); err != nil { if r, err := c.DoApiGet("/admin/logs", "", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -348,7 +377,7 @@ func (c *Client) GetLogs() (*Result, *AppError) {
} }
func (c *Client) GetClientProperties() (*Result, *AppError) { func (c *Client) GetClientProperties() (*Result, *AppError) {
if r, err := c.DoGet("/admin/client_props", "", ""); err != nil { if r, err := c.DoApiGet("/admin/client_props", "", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -357,7 +386,7 @@ func (c *Client) GetClientProperties() (*Result, *AppError) {
} }
func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) { func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
if r, err := c.DoPost("/channels/create", channel.ToJson()); err != nil { if r, err := c.DoApiPost("/channels/create", channel.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -366,7 +395,7 @@ func (c *Client) CreateChannel(channel *Channel) (*Result, *AppError) {
} }
func (c *Client) CreateDirectChannel(data map[string]string) (*Result, *AppError) { func (c *Client) CreateDirectChannel(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/create_direct", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/channels/create_direct", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -375,7 +404,7 @@ func (c *Client) CreateDirectChannel(data map[string]string) (*Result, *AppError
} }
func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) { func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) {
if r, err := c.DoPost("/channels/update", channel.ToJson()); err != nil { if r, err := c.DoApiPost("/channels/update", channel.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -384,7 +413,7 @@ func (c *Client) UpdateChannel(channel *Channel) (*Result, *AppError) {
} }
func (c *Client) UpdateChannelDesc(data map[string]string) (*Result, *AppError) { func (c *Client) UpdateChannelDesc(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/update_desc", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/channels/update_desc", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -393,7 +422,7 @@ func (c *Client) UpdateChannelDesc(data map[string]string) (*Result, *AppError)
} }
func (c *Client) UpdateNotifyLevel(data map[string]string) (*Result, *AppError) { func (c *Client) UpdateNotifyLevel(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/update_notify_level", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/channels/update_notify_level", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -402,7 +431,7 @@ func (c *Client) UpdateNotifyLevel(data map[string]string) (*Result, *AppError)
} }
func (c *Client) GetChannels(etag string) (*Result, *AppError) { func (c *Client) GetChannels(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/", "", etag); err != nil { if r, err := c.DoApiGet("/channels/", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -411,7 +440,7 @@ func (c *Client) GetChannels(etag string) (*Result, *AppError) {
} }
func (c *Client) GetChannel(id, etag string) (*Result, *AppError) { func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/"+id+"/", "", etag); err != nil { if r, err := c.DoApiGet("/channels/"+id+"/", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -420,7 +449,7 @@ func (c *Client) GetChannel(id, etag string) (*Result, *AppError) {
} }
func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) { func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/more", "", etag); err != nil { if r, err := c.DoApiGet("/channels/more", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -429,7 +458,7 @@ func (c *Client) GetMoreChannels(etag string) (*Result, *AppError) {
} }
func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) { func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/counts", "", etag); err != nil { if r, err := c.DoApiGet("/channels/counts", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -438,7 +467,7 @@ func (c *Client) GetChannelCounts(etag string) (*Result, *AppError) {
} }
func (c *Client) JoinChannel(id string) (*Result, *AppError) { func (c *Client) JoinChannel(id string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+id+"/join", ""); err != nil { if r, err := c.DoApiPost("/channels/"+id+"/join", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -447,7 +476,7 @@ func (c *Client) JoinChannel(id string) (*Result, *AppError) {
} }
func (c *Client) LeaveChannel(id string) (*Result, *AppError) { func (c *Client) LeaveChannel(id string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+id+"/leave", ""); err != nil { if r, err := c.DoApiPost("/channels/"+id+"/leave", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -456,7 +485,7 @@ func (c *Client) LeaveChannel(id string) (*Result, *AppError) {
} }
func (c *Client) DeleteChannel(id string) (*Result, *AppError) { func (c *Client) DeleteChannel(id string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+id+"/delete", ""); err != nil { if r, err := c.DoApiPost("/channels/"+id+"/delete", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -467,7 +496,7 @@ func (c *Client) DeleteChannel(id string) (*Result, *AppError) {
func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) { func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) {
data := make(map[string]string) data := make(map[string]string)
data["user_id"] = user_id data["user_id"] = user_id
if r, err := c.DoPost("/channels/"+id+"/add", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/channels/"+id+"/add", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -478,7 +507,7 @@ func (c *Client) AddChannelMember(id, user_id string) (*Result, *AppError) {
func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) { func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
data := make(map[string]string) data := make(map[string]string)
data["user_id"] = user_id data["user_id"] = user_id
if r, err := c.DoPost("/channels/"+id+"/remove", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/channels/"+id+"/remove", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -487,7 +516,7 @@ func (c *Client) RemoveChannelMember(id, user_id string) (*Result, *AppError) {
} }
func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) { func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+channelId+"/update_last_viewed_at", ""); err != nil { if r, err := c.DoApiPost("/channels/"+channelId+"/update_last_viewed_at", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -496,7 +525,7 @@ func (c *Client) UpdateLastViewedAt(channelId string) (*Result, *AppError) {
} }
func (c *Client) GetChannelExtraInfo(id string, etag string) (*Result, *AppError) { func (c *Client) GetChannelExtraInfo(id string, etag string) (*Result, *AppError) {
if r, err := c.DoGet("/channels/"+id+"/extra_info", "", etag); err != nil { if r, err := c.DoApiGet("/channels/"+id+"/extra_info", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -505,7 +534,7 @@ func (c *Client) GetChannelExtraInfo(id string, etag string) (*Result, *AppError
} }
func (c *Client) CreatePost(post *Post) (*Result, *AppError) { func (c *Client) CreatePost(post *Post) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+post.ChannelId+"/create", post.ToJson()); err != nil { if r, err := c.DoApiPost("/channels/"+post.ChannelId+"/create", post.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -514,7 +543,7 @@ func (c *Client) CreatePost(post *Post) (*Result, *AppError) {
} }
func (c *Client) CreateValetPost(post *Post) (*Result, *AppError) { func (c *Client) CreateValetPost(post *Post) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+post.ChannelId+"/valet_create", post.ToJson()); err != nil { if r, err := c.DoApiPost("/channels/"+post.ChannelId+"/valet_create", post.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -523,7 +552,7 @@ func (c *Client) CreateValetPost(post *Post) (*Result, *AppError) {
} }
func (c *Client) UpdatePost(post *Post) (*Result, *AppError) { func (c *Client) UpdatePost(post *Post) (*Result, *AppError) {
if r, err := c.DoPost("/channels/"+post.ChannelId+"/update", post.ToJson()); err != nil { if r, err := c.DoApiPost("/channels/"+post.ChannelId+"/update", post.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -532,7 +561,7 @@ func (c *Client) UpdatePost(post *Post) (*Result, *AppError) {
} }
func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) (*Result, *AppError) { func (c *Client) GetPosts(channelId string, offset int, limit int, etag string) (*Result, *AppError) {
if r, err := c.DoGet(fmt.Sprintf("/channels/%v/posts/%v/%v", channelId, offset, limit), "", etag); err != nil { if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/posts/%v/%v", channelId, offset, limit), "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -541,7 +570,7 @@ func (c *Client) GetPosts(channelId string, offset int, limit int, etag string)
} }
func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError) { func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError) {
if r, err := c.DoGet(fmt.Sprintf("/channels/%v/posts/%v", channelId, time), "", ""); err != nil { if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/posts/%v", channelId, time), "", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -550,7 +579,7 @@ func (c *Client) GetPostsSince(channelId string, time int64) (*Result, *AppError
} }
func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) { func (c *Client) GetPost(channelId string, postId string, etag string) (*Result, *AppError) {
if r, err := c.DoGet(fmt.Sprintf("/channels/%v/post/%v", channelId, postId), "", etag); err != nil { if r, err := c.DoApiGet(fmt.Sprintf("/channels/%v/post/%v", channelId, postId), "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -559,7 +588,7 @@ func (c *Client) GetPost(channelId string, postId string, etag string) (*Result,
} }
func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) { func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError) {
if r, err := c.DoPost(fmt.Sprintf("/channels/%v/post/%v/delete", channelId, postId), ""); err != nil { if r, err := c.DoApiPost(fmt.Sprintf("/channels/%v/post/%v/delete", channelId, postId), ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -568,7 +597,7 @@ func (c *Client) DeletePost(channelId string, postId string) (*Result, *AppError
} }
func (c *Client) SearchPosts(terms string) (*Result, *AppError) { func (c *Client) SearchPosts(terms string) (*Result, *AppError) {
if r, err := c.DoGet("/posts/search?terms="+url.QueryEscape(terms), "", ""); err != nil { if r, err := c.DoApiGet("/posts/search?terms="+url.QueryEscape(terms), "", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -577,7 +606,7 @@ func (c *Client) SearchPosts(terms string) (*Result, *AppError) {
} }
func (c *Client) UploadFile(url string, data []byte, contentType string) (*Result, *AppError) { func (c *Client) UploadFile(url string, data []byte, contentType string) (*Result, *AppError) {
rq, _ := http.NewRequest("POST", c.Url+url, bytes.NewReader(data)) rq, _ := http.NewRequest("POST", c.ApiUrl+url, bytes.NewReader(data))
rq.Header.Set("Content-Type", contentType) rq.Header.Set("Content-Type", contentType)
if len(c.AuthToken) > 0 { if len(c.AuthToken) > 0 {
@@ -599,7 +628,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
if isFullUrl { if isFullUrl {
rq, _ = http.NewRequest("GET", url, nil) rq, _ = http.NewRequest("GET", url, nil)
} else { } else {
rq, _ = http.NewRequest("GET", c.Url+"/files/get"+url, nil) rq, _ = http.NewRequest("GET", c.ApiUrl+"/files/get"+url, nil)
} }
if len(c.AuthToken) > 0 { if len(c.AuthToken) > 0 {
@@ -618,7 +647,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
func (c *Client) GetFileInfo(url string) (*Result, *AppError) { func (c *Client) GetFileInfo(url string) (*Result, *AppError) {
var rq *http.Request var rq *http.Request
rq, _ = http.NewRequest("GET", c.Url+"/files/get_info"+url, nil) rq, _ = http.NewRequest("GET", c.ApiUrl+"/files/get_info"+url, nil)
if len(c.AuthToken) > 0 { if len(c.AuthToken) > 0 {
rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken) rq.Header.Set(HEADER_AUTH, "BEARER "+c.AuthToken)
@@ -635,7 +664,7 @@ func (c *Client) GetFileInfo(url string) (*Result, *AppError) {
} }
func (c *Client) GetPublicLink(data map[string]string) (*Result, *AppError) { func (c *Client) GetPublicLink(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/files/get_public_link", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/files/get_public_link", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -644,7 +673,7 @@ func (c *Client) GetPublicLink(data map[string]string) (*Result, *AppError) {
} }
func (c *Client) UpdateUser(user *User) (*Result, *AppError) { func (c *Client) UpdateUser(user *User) (*Result, *AppError) {
if r, err := c.DoPost("/users/update", user.ToJson()); err != nil { if r, err := c.DoApiPost("/users/update", user.ToJson()); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -653,7 +682,7 @@ func (c *Client) UpdateUser(user *User) (*Result, *AppError) {
} }
func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) { func (c *Client) UpdateUserRoles(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/update_roles", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/users/update_roles", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -665,7 +694,7 @@ func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) {
data := make(map[string]string) data := make(map[string]string)
data["user_id"] = userId data["user_id"] = userId
data["active"] = strconv.FormatBool(active) data["active"] = strconv.FormatBool(active)
if r, err := c.DoPost("/users/update_active", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/users/update_active", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -674,7 +703,7 @@ func (c *Client) UpdateActive(userId string, active bool) (*Result, *AppError) {
} }
func (c *Client) UpdateUserNotify(data map[string]string) (*Result, *AppError) { func (c *Client) UpdateUserNotify(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/update_notify", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/users/update_notify", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -688,7 +717,7 @@ func (c *Client) UpdateUserPassword(userId, currentPassword, newPassword string)
data["new_password"] = newPassword data["new_password"] = newPassword
data["user_id"] = userId data["user_id"] = userId
if r, err := c.DoPost("/users/newpassword", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/users/newpassword", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -697,7 +726,7 @@ func (c *Client) UpdateUserPassword(userId, currentPassword, newPassword string)
} }
func (c *Client) SendPasswordReset(data map[string]string) (*Result, *AppError) { func (c *Client) SendPasswordReset(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/send_password_reset", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/users/send_password_reset", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -706,7 +735,7 @@ func (c *Client) SendPasswordReset(data map[string]string) (*Result, *AppError)
} }
func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) { func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) {
if r, err := c.DoPost("/users/reset_password", MapToJson(data)); err != nil { if r, err := c.DoApiPost("/users/reset_password", MapToJson(data)); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -715,7 +744,7 @@ func (c *Client) ResetPassword(data map[string]string) (*Result, *AppError) {
} }
func (c *Client) GetStatuses() (*Result, *AppError) { func (c *Client) GetStatuses() (*Result, *AppError) {
if r, err := c.DoGet("/users/status", "", ""); err != nil { if r, err := c.DoApiGet("/users/status", "", ""); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -724,7 +753,7 @@ func (c *Client) GetStatuses() (*Result, *AppError) {
} }
func (c *Client) GetMyTeam(etag string) (*Result, *AppError) { func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
if r, err := c.DoGet("/teams/me", "", etag); err != nil { if r, err := c.DoApiGet("/teams/me", "", etag); err != nil {
return nil, err return nil, err
} else { } else {
return &Result{r.Header.Get(HEADER_REQUEST_ID), return &Result{r.Header.Get(HEADER_REQUEST_ID),
@@ -732,6 +761,33 @@ func (c *Client) GetMyTeam(etag string) (*Result, *AppError) {
} }
} }
func (c *Client) RegisterApp(app *OAuthApp) (*Result, *AppError) {
if r, err := c.DoApiPost("/oauth/register", app.ToJson()); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), OAuthAppFromJson(r.Body)}, nil
}
}
func (c *Client) AllowOAuth(rspType, clientId, redirect, scope, state string) (*Result, *AppError) {
if r, err := c.DoApiGet("/oauth/allow?response_type="+rspType+"&client_id="+clientId+"&redirect_uri="+url.QueryEscape(redirect)+"&scope="+scope+"&state="+url.QueryEscape(state), "", ""); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), MapFromJson(r.Body)}, nil
}
}
func (c *Client) GetAccessToken(data url.Values) (*Result, *AppError) {
if r, err := c.DoPost("/oauth/access_token", data.Encode(), "application/x-www-form-urlencoded"); err != nil {
return nil, err
} else {
return &Result{r.Header.Get(HEADER_REQUEST_ID),
r.Header.Get(HEADER_ETAG_SERVER), AccessResponseFromJson(r.Body)}, nil
}
}
func (c *Client) MockSession(sessionToken string) { func (c *Client) MockSession(sessionToken string) {
c.AuthToken = sessionToken c.AuthToken = sessionToken
} }

151
model/oauth.go Обычный файл
Просмотреть файл

@@ -0,0 +1,151 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"encoding/json"
"fmt"
"io"
)
type OAuthApp struct {
Id string `json:"id"`
CreatorId string `json:"creator_id"`
CreateAt int64 `json:"update_at"`
UpdateAt int64 `json:"update_at"`
ClientSecret string `json:"client_secret"`
Name string `json:"name"`
Description string `json:"description"`
CallbackUrls StringArray `json:"callback_urls"`
Homepage string `json:"homepage"`
}
// IsValid validates the app and returns an error if it isn't configured
// correctly.
func (a *OAuthApp) IsValid() *AppError {
if len(a.Id) != 26 {
return NewAppError("OAuthApp.IsValid", "Invalid app id", "")
}
if a.CreateAt == 0 {
return NewAppError("OAuthApp.IsValid", "Create at must be a valid time", "app_id="+a.Id)
}
if a.UpdateAt == 0 {
return NewAppError("OAuthApp.IsValid", "Update at must be a valid time", "app_id="+a.Id)
}
if len(a.CreatorId) != 26 {
return NewAppError("OAuthApp.IsValid", "Invalid creator id", "app_id="+a.Id)
}
if len(a.ClientSecret) == 0 || len(a.ClientSecret) > 128 {
return NewAppError("OAuthApp.IsValid", "Invalid client secret", "app_id="+a.Id)
}
if len(a.Name) == 0 || len(a.Name) > 64 {
return NewAppError("OAuthApp.IsValid", "Invalid name", "app_id="+a.Id)
}
if len(a.CallbackUrls) == 0 || len(fmt.Sprintf("%s", a.CallbackUrls)) > 1024 {
return NewAppError("OAuthApp.IsValid", "Invalid callback urls", "app_id="+a.Id)
}
if len(a.Homepage) == 0 || len(a.Homepage) > 256 {
return NewAppError("OAuthApp.IsValid", "Invalid homepage", "app_id="+a.Id)
}
if len(a.Description) > 512 {
return NewAppError("OAuthApp.IsValid", "Invalid description", "app_id="+a.Id)
}
return nil
}
// PreSave will set the Id and ClientSecret if missing. It will also fill
// in the CreateAt, UpdateAt times. It should be run before saving the app to the db.
func (a *OAuthApp) PreSave() {
if a.Id == "" {
a.Id = NewId()
}
if a.ClientSecret == "" {
a.ClientSecret = NewId()
}
a.CreateAt = GetMillis()
a.UpdateAt = a.CreateAt
if len(a.ClientSecret) > 0 {
a.ClientSecret = HashPassword(a.ClientSecret)
}
}
// PreUpdate should be run before updating the app in the db.
func (a *OAuthApp) PreUpdate() {
a.UpdateAt = GetMillis()
}
// ToJson convert a User to a json string
func (a *OAuthApp) ToJson() string {
b, err := json.Marshal(a)
if err != nil {
return ""
} else {
return string(b)
}
}
// Generate a valid strong etag so the browser can cache the results
func (a *OAuthApp) Etag() string {
return Etag(a.Id, a.UpdateAt)
}
// Remove any private data from the app object
func (a *OAuthApp) Sanitize() {
a.ClientSecret = ""
}
func (a *OAuthApp) IsValidRedirectURL(url string) bool {
for _, u := range a.CallbackUrls {
if u == url {
return true
}
}
return false
}
// OAuthAppFromJson will decode the input and return a User
func OAuthAppFromJson(data io.Reader) *OAuthApp {
decoder := json.NewDecoder(data)
var app OAuthApp
err := decoder.Decode(&app)
if err == nil {
return &app
} else {
return nil
}
}
func OAuthAppMapToJson(a map[string]*OAuthApp) string {
b, err := json.Marshal(a)
if err != nil {
return ""
} else {
return string(b)
}
}
func OAuthAppMapFromJson(data io.Reader) map[string]*OAuthApp {
decoder := json.NewDecoder(data)
var apps map[string]*OAuthApp
err := decoder.Decode(&apps)
if err == nil {
return apps
} else {
return nil
}
}

95
model/oauth_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,95 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package model
import (
"strings"
"testing"
)
func TestOAuthAppJson(t *testing.T) {
a1 := OAuthApp{}
a1.Id = NewId()
a1.Name = "TestOAuthApp" + NewId()
a1.CallbackUrls = []string{"https://nowhere.com"}
a1.Homepage = "https://nowhere.com"
a1.ClientSecret = NewId()
json := a1.ToJson()
ra1 := OAuthAppFromJson(strings.NewReader(json))
if a1.Id != ra1.Id {
t.Fatal("ids did not match")
}
}
func TestOAuthAppPreSave(t *testing.T) {
a1 := OAuthApp{}
a1.Id = NewId()
a1.Name = "TestOAuthApp" + NewId()
a1.CallbackUrls = []string{"https://nowhere.com"}
a1.Homepage = "https://nowhere.com"
a1.ClientSecret = NewId()
a1.PreSave()
a1.Etag()
a1.Sanitize()
}
func TestOAuthAppPreUpdate(t *testing.T) {
a1 := OAuthApp{}
a1.Id = NewId()
a1.Name = "TestOAuthApp" + NewId()
a1.CallbackUrls = []string{"https://nowhere.com"}
a1.Homepage = "https://nowhere.com"
a1.ClientSecret = NewId()
a1.PreUpdate()
}
func TestOAuthAppIsValid(t *testing.T) {
app := OAuthApp{}
if err := app.IsValid(); err == nil {
t.Fatal()
}
app.Id = NewId()
if err := app.IsValid(); err == nil {
t.Fatal()
}
app.CreateAt = 1
if err := app.IsValid(); err == nil {
t.Fatal()
}
app.UpdateAt = 1
if err := app.IsValid(); err == nil {
t.Fatal()
}
app.CreatorId = NewId()
if err := app.IsValid(); err == nil {
t.Fatal()
}
app.ClientSecret = NewId()
if err := app.IsValid(); err == nil {
t.Fatal()
}
app.Name = "TestOAuthApp"
if err := app.IsValid(); err == nil {
t.Fatal()
}
app.CallbackUrls = []string{"https://nowhere.com"}
if err := app.IsValid(); err == nil {
t.Fatal()
}
app.Homepage = "https://nowhere.com"
if err := app.IsValid(); err != nil {
t.Fatal()
}
}

Просмотреть файл

@@ -14,6 +14,8 @@ const (
SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS SESSION_TIME_WEB_IN_SECS = 60 * 60 * 24 * SESSION_TIME_WEB_IN_DAYS
SESSION_TIME_MOBILE_IN_DAYS = 30 SESSION_TIME_MOBILE_IN_DAYS = 30
SESSION_TIME_MOBILE_IN_SECS = 60 * 60 * 24 * SESSION_TIME_MOBILE_IN_DAYS SESSION_TIME_MOBILE_IN_SECS = 60 * 60 * 24 * SESSION_TIME_MOBILE_IN_DAYS
SESSION_TIME_OAUTH_IN_DAYS = 365
SESSION_TIME_OAUTH_IN_SECS = 60 * 60 * 24 * SESSION_TIME_OAUTH_IN_DAYS
SESSION_CACHE_IN_SECS = 60 * 10 SESSION_CACHE_IN_SECS = 60 * 10
SESSION_CACHE_SIZE = 10000 SESSION_CACHE_SIZE = 10000
SESSION_PROP_PLATFORM = "platform" SESSION_PROP_PLATFORM = "platform"
@@ -23,7 +25,7 @@ const (
type Session struct { type Session struct {
Id string `json:"id"` Id string `json:"id"`
AltId string `json:"alt_id"` Token string `json:"token"`
CreateAt int64 `json:"create_at"` CreateAt int64 `json:"create_at"`
ExpiresAt int64 `json:"expires_at"` ExpiresAt int64 `json:"expires_at"`
LastActivityAt int64 `json:"last_activity_at"` LastActivityAt int64 `json:"last_activity_at"`
@@ -31,6 +33,7 @@ type Session struct {
TeamId string `json:"team_id"` TeamId string `json:"team_id"`
DeviceId string `json:"device_id"` DeviceId string `json:"device_id"`
Roles string `json:"roles"` Roles string `json:"roles"`
IsOAuth bool `json:"is_oauth"`
Props StringMap `json:"props"` Props StringMap `json:"props"`
} }
@@ -59,7 +62,7 @@ func (me *Session) PreSave() {
me.Id = NewId() me.Id = NewId()
} }
me.AltId = NewId() me.Token = NewId()
me.CreateAt = GetMillis() me.CreateAt = GetMillis()
me.LastActivityAt = me.CreateAt me.LastActivityAt = me.CreateAt
@@ -70,7 +73,7 @@ func (me *Session) PreSave() {
} }
func (me *Session) Sanitize() { func (me *Session) Sanitize() {
me.Id = "" me.Token = ""
} }
func (me *Session) IsExpired() bool { func (me *Session) IsExpired() bool {

Просмотреть файл

@@ -32,6 +32,7 @@ type AppError struct {
RequestId string `json:"request_id"` // The RequestId that's also set in the header RequestId string `json:"request_id"` // The RequestId that's also set in the header
StatusCode int `json:"status_code"` // The http status code StatusCode int `json:"status_code"` // The http status code
Where string `json:"-"` // The function where it happened in the form of Struct.Func Where string `json:"-"` // The function where it happened in the form of Struct.Func
IsOAuth bool `json:"is_oauth"` // Whether the error is OAuth specific
} }
func (er *AppError) Error() string { func (er *AppError) Error() string {
@@ -65,6 +66,7 @@ func NewAppError(where string, message string, details string) *AppError {
ap.Where = where ap.Where = where
ap.DetailedError = details ap.DetailedError = details
ap.StatusCode = 500 ap.StatusCode = 500
ap.IsOAuth = false
return ap return ap
} }

334
store/sql_oauth_store.go Обычный файл
Просмотреть файл

@@ -0,0 +1,334 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"strings"
)
type SqlOAuthStore struct {
*SqlStore
}
func NewSqlOAuthStore(sqlStore *SqlStore) OAuthStore {
as := &SqlOAuthStore{sqlStore}
for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.OAuthApp{}, "OAuthApps").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26)
table.ColMap("CreatorId").SetMaxSize(26)
table.ColMap("ClientSecret").SetMaxSize(128)
table.ColMap("Name").SetMaxSize(64)
table.ColMap("Description").SetMaxSize(512)
table.ColMap("CallbackUrls").SetMaxSize(1024)
table.ColMap("Homepage").SetMaxSize(256)
tableAuth := db.AddTableWithName(model.AuthData{}, "OAuthAuthData").SetKeys(false, "Code")
tableAuth.ColMap("UserId").SetMaxSize(26)
tableAuth.ColMap("ClientId").SetMaxSize(26)
tableAuth.ColMap("Code").SetMaxSize(128)
tableAuth.ColMap("RedirectUri").SetMaxSize(256)
tableAuth.ColMap("State").SetMaxSize(128)
tableAuth.ColMap("Scope").SetMaxSize(128)
tableAccess := db.AddTableWithName(model.AccessData{}, "OAuthAccessData").SetKeys(false, "Token")
tableAccess.ColMap("AuthCode").SetMaxSize(128)
tableAccess.ColMap("Token").SetMaxSize(26)
tableAccess.ColMap("RefreshToken").SetMaxSize(26)
tableAccess.ColMap("RedirectUri").SetMaxSize(256)
}
return as
}
func (as SqlOAuthStore) UpgradeSchemaIfNeeded() {
}
func (as SqlOAuthStore) CreateIndexesIfNotExists() {
as.CreateIndexIfNotExists("idx_oauthapps_creator_id", "OAuthApps", "CreatorId")
as.CreateIndexIfNotExists("idx_oauthaccessdata_auth_code", "OAuthAccessData", "AuthCode")
as.CreateIndexIfNotExists("idx_oauthauthdata_client_id", "OAuthAuthData", "Code")
}
func (as SqlOAuthStore) SaveApp(app *model.OAuthApp) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if len(app.Id) > 0 {
result.Err = model.NewAppError("SqlOAuthStore.SaveApp", "Must call update for exisiting app", "app_id="+app.Id)
storeChannel <- result
close(storeChannel)
return
}
app.PreSave()
if result.Err = app.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if err := as.GetMaster().Insert(app); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.SaveApp", "We couldn't save the app.", "app_id="+app.Id+", "+err.Error())
} else {
result.Data = app
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) UpdateApp(app *model.OAuthApp) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
app.PreUpdate()
if result.Err = app.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if oldAppResult, err := as.GetMaster().Get(model.OAuthApp{}, app.Id); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "We encounted an error finding the app", "app_id="+app.Id+", "+err.Error())
} else if oldAppResult == nil {
result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "We couldn't find the existing app to update", "app_id="+app.Id)
} else {
oldApp := oldAppResult.(*model.OAuthApp)
app.CreateAt = oldApp.CreateAt
app.ClientSecret = oldApp.ClientSecret
app.CreatorId = oldApp.CreatorId
if count, err := as.GetMaster().Update(app); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "We encounted an error updating the app", "app_id="+app.Id+", "+err.Error())
} else if count != 1 {
result.Err = model.NewAppError("SqlOAuthStore.UpdateApp", "We couldn't update the app", "app_id="+app.Id)
} else {
result.Data = [2]*model.OAuthApp{app, oldApp}
}
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) GetApp(id string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if obj, err := as.GetReplica().Get(model.OAuthApp{}, id); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.GetApp", "We encounted an error finding the app", "app_id="+id+", "+err.Error())
} else if obj == nil {
result.Err = model.NewAppError("SqlOAuthStore.GetApp", "We couldn't find the existing app", "app_id="+id)
} else {
result.Data = obj.(*model.OAuthApp)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) GetAppByUser(userId string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
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.NewAppError("SqlOAuthStore.GetAppByUser", "We couldn't find any existing apps", "user_id="+userId+", "+err.Error())
}
result.Data = apps
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) SaveAccessData(accessData *model.AccessData) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if result.Err = accessData.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if err := as.GetMaster().Insert(accessData); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.SaveAccessData", "We couldn't save the access token.", err.Error())
} else {
result.Data = accessData
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) GetAccessData(token string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
accessData := model.AccessData{}
if err := as.GetReplica().SelectOne(&accessData, "SELECT * FROM OAuthAccessData WHERE Token = :Token", map[string]interface{}{"Token": token}); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.GetAccessData", "We encounted an error finding the access token", err.Error())
} else {
result.Data = &accessData
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) GetAccessDataByAuthCode(authCode string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
accessData := model.AccessData{}
if err := as.GetReplica().SelectOne(&accessData, "SELECT * FROM OAuthAccessData WHERE AuthCode = :AuthCode", map[string]interface{}{"AuthCode": authCode}); err != nil {
if strings.Contains(err.Error(), "no rows") {
result.Data = nil
} else {
result.Err = model.NewAppError("SqlOAuthStore.GetAccessDataByAuthCode", "We encountered an error finding the access token", err.Error())
}
} else {
result.Data = &accessData
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) RemoveAccessData(token string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if _, err := as.GetMaster().Exec("DELETE FROM OAuthAccessData WHERE Token = :Token", map[string]interface{}{"Token": token}); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.RemoveAccessData", "We couldn't remove the access token", "err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) SaveAuthData(authData *model.AuthData) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
authData.PreSave()
if result.Err = authData.IsValid(); result.Err != nil {
storeChannel <- result
close(storeChannel)
return
}
if err := as.GetMaster().Insert(authData); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.SaveAuthData", "We couldn't save the authorization code.", err.Error())
} else {
result.Data = authData
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) GetAuthData(code string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
if obj, err := as.GetReplica().Get(model.AuthData{}, code); err != nil {
result.Err = model.NewAppError("SqlOAuthStore.GetAuthData", "We encounted an error finding the authorization code", err.Error())
} else if obj == nil {
result.Err = model.NewAppError("SqlOAuthStore.GetAuthData", "We couldn't find the existing authorization code", "")
} else {
result.Data = obj.(*model.AuthData)
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}
func (as SqlOAuthStore) RemoveAuthData(code string) StoreChannel {
storeChannel := make(StoreChannel)
go func() {
result := StoreResult{}
_, err := as.GetMaster().Exec("DELETE FROM OAuthAuthData WHERE Code = :Code", map[string]interface{}{"Code": code})
if err != nil {
result.Err = model.NewAppError("SqlOAuthStore.RemoveAuthData", "We couldn't remove the authorization code", "err="+err.Error())
}
storeChannel <- result
close(storeChannel)
}()
return storeChannel
}

182
store/sql_oauth_store_test.go Обычный файл
Просмотреть файл

@@ -0,0 +1,182 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package store
import (
"github.com/mattermost/platform/model"
"testing"
)
func TestOAuthStoreSaveApp(t *testing.T) {
Setup()
a1 := model.OAuthApp{}
a1.CreatorId = model.NewId()
a1.Name = "TestApp" + model.NewId()
a1.CallbackUrls = []string{"https://nowhere.com"}
a1.Homepage = "https://nowhere.com"
if err := (<-store.OAuth().SaveApp(&a1)).Err; err != nil {
t.Fatal(err)
}
}
func TestOAuthStoreGetApp(t *testing.T) {
Setup()
a1 := model.OAuthApp{}
a1.CreatorId = model.NewId()
a1.Name = "TestApp" + model.NewId()
a1.CallbackUrls = []string{"https://nowhere.com"}
a1.Homepage = "https://nowhere.com"
Must(store.OAuth().SaveApp(&a1))
if err := (<-store.OAuth().GetApp(a1.Id)).Err; err != nil {
t.Fatal(err)
}
if err := (<-store.OAuth().GetAppByUser(a1.CreatorId)).Err; err != nil {
t.Fatal(err)
}
}
func TestOAuthStoreUpdateApp(t *testing.T) {
Setup()
a1 := model.OAuthApp{}
a1.CreatorId = model.NewId()
a1.Name = "TestApp" + model.NewId()
a1.CallbackUrls = []string{"https://nowhere.com"}
a1.Homepage = "https://nowhere.com"
Must(store.OAuth().SaveApp(&a1))
a1.CreateAt = 1
a1.ClientSecret = "pwd"
a1.CreatorId = "12345678901234567890123456"
a1.Name = "NewName"
if result := <-store.OAuth().UpdateApp(&a1); result.Err != nil {
t.Fatal(result.Err)
} else {
ua1 := (result.Data.([2]*model.OAuthApp)[0])
if ua1.Name != "NewName" {
t.Fatal("name did not update")
}
if ua1.CreateAt == 1 {
t.Fatal("create at should not have updated")
}
if ua1.ClientSecret == "pwd" {
t.Fatal("client secret should not have updated")
}
if ua1.CreatorId == "12345678901234567890123456" {
t.Fatal("creator id should not have updated")
}
}
}
func TestOAuthStoreSaveAccessData(t *testing.T) {
Setup()
a1 := model.AccessData{}
a1.AuthCode = model.NewId()
a1.Token = model.NewId()
a1.RefreshToken = model.NewId()
if err := (<-store.OAuth().SaveAccessData(&a1)).Err; err != nil {
t.Fatal(err)
}
}
func TestOAuthStoreGetAccessData(t *testing.T) {
Setup()
a1 := model.AccessData{}
a1.AuthCode = model.NewId()
a1.Token = model.NewId()
a1.RefreshToken = model.NewId()
Must(store.OAuth().SaveAccessData(&a1))
if result := <-store.OAuth().GetAccessData(a1.Token); result.Err != nil {
t.Fatal(result.Err)
} else {
ra1 := result.Data.(*model.AccessData)
if a1.Token != ra1.Token {
t.Fatal("tokens didn't match")
}
}
if err := (<-store.OAuth().GetAccessDataByAuthCode(a1.AuthCode)).Err; err != nil {
t.Fatal(err)
}
if err := (<-store.OAuth().GetAccessDataByAuthCode("junk")).Err; err != nil {
t.Fatal(err)
}
}
func TestOAuthStoreRemoveAccessData(t *testing.T) {
Setup()
a1 := model.AccessData{}
a1.AuthCode = model.NewId()
a1.Token = model.NewId()
a1.RefreshToken = model.NewId()
Must(store.OAuth().SaveAccessData(&a1))
if err := (<-store.OAuth().RemoveAccessData(a1.Token)).Err; err != nil {
t.Fatal(err)
}
if result := <-store.OAuth().GetAccessDataByAuthCode(a1.AuthCode); result.Err != nil {
t.Fatal(result.Err)
} else {
if result.Data != nil {
t.Fatal("did not delete access token")
}
}
}
func TestOAuthStoreSaveAuthData(t *testing.T) {
Setup()
a1 := model.AuthData{}
a1.ClientId = model.NewId()
a1.UserId = model.NewId()
a1.Code = model.NewId()
if err := (<-store.OAuth().SaveAuthData(&a1)).Err; err != nil {
t.Fatal(err)
}
}
func TestOAuthStoreGetAuthData(t *testing.T) {
Setup()
a1 := model.AuthData{}
a1.ClientId = model.NewId()
a1.UserId = model.NewId()
a1.Code = model.NewId()
Must(store.OAuth().SaveAuthData(&a1))
if err := (<-store.OAuth().GetAuthData(a1.Code)).Err; err != nil {
t.Fatal(err)
}
}
func TestOAuthStoreRemoveAuthData(t *testing.T) {
Setup()
a1 := model.AuthData{}
a1.ClientId = model.NewId()
a1.UserId = model.NewId()
a1.Code = model.NewId()
Must(store.OAuth().SaveAuthData(&a1))
if err := (<-store.OAuth().RemoveAuthData(a1.Code)).Err; err != nil {
t.Fatal(err)
}
if err := (<-store.OAuth().GetAuthData(a1.Code)).Err; err == nil {
t.Fatal("should have errored - auth code removed")
}
}

Просмотреть файл

@@ -18,7 +18,7 @@ func NewSqlSessionStore(sqlStore *SqlStore) SessionStore {
for _, db := range sqlStore.GetAllConns() { for _, db := range sqlStore.GetAllConns() {
table := db.AddTableWithName(model.Session{}, "Sessions").SetKeys(false, "Id") table := db.AddTableWithName(model.Session{}, "Sessions").SetKeys(false, "Id")
table.ColMap("Id").SetMaxSize(26) table.ColMap("Id").SetMaxSize(26)
table.ColMap("AltId").SetMaxSize(26) table.ColMap("Token").SetMaxSize(26)
table.ColMap("UserId").SetMaxSize(26) table.ColMap("UserId").SetMaxSize(26)
table.ColMap("TeamId").SetMaxSize(26) table.ColMap("TeamId").SetMaxSize(26)
table.ColMap("DeviceId").SetMaxSize(128) table.ColMap("DeviceId").SetMaxSize(128)
@@ -34,7 +34,7 @@ func (me SqlSessionStore) UpgradeSchemaIfNeeded() {
func (me SqlSessionStore) CreateIndexesIfNotExists() { func (me SqlSessionStore) CreateIndexesIfNotExists() {
me.CreateIndexIfNotExists("idx_sessions_user_id", "Sessions", "UserId") me.CreateIndexIfNotExists("idx_sessions_user_id", "Sessions", "UserId")
me.CreateIndexIfNotExists("idx_sessions_alt_id", "Sessions", "AltId") me.CreateIndexIfNotExists("idx_sessions_token", "Sessions", "Token")
} }
func (me SqlSessionStore) Save(session *model.Session) StoreChannel { func (me SqlSessionStore) Save(session *model.Session) StoreChannel {
@@ -70,19 +70,21 @@ func (me SqlSessionStore) Save(session *model.Session) StoreChannel {
return storeChannel return storeChannel
} }
func (me SqlSessionStore) Get(id string) StoreChannel { func (me SqlSessionStore) Get(sessionIdOrToken string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)
go func() { go func() {
result := StoreResult{} result := StoreResult{}
if obj, err := me.GetReplica().Get(model.Session{}, id); err != nil { var sessions []*model.Session
result.Err = model.NewAppError("SqlSessionStore.Get", "We encounted an error finding the session", "id="+id+", "+err.Error())
} else if obj == nil { if _, err := me.GetReplica().Select(&sessions, "SELECT * FROM Sessions WHERE Token = :Token OR Id = :Id LIMIT 1", map[string]interface{}{"Token": sessionIdOrToken, "Id": sessionIdOrToken}); err != nil {
result.Err = model.NewAppError("SqlSessionStore.Get", "We couldn't find the existing session", "id="+id) result.Err = model.NewAppError("SqlSessionStore.Get", "We encounted an error finding the session", "sessionIdOrToken="+sessionIdOrToken+", "+err.Error())
} else if sessions == nil || len(sessions) == 0 {
result.Err = model.NewAppError("SqlSessionStore.Get", "We encounted an error finding the session", "sessionIdOrToken="+sessionIdOrToken)
} else { } else {
result.Data = obj.(*model.Session) result.Data = sessions[0]
} }
storeChannel <- result storeChannel <- result
@@ -120,15 +122,15 @@ func (me SqlSessionStore) GetSessions(userId string) StoreChannel {
return storeChannel return storeChannel
} }
func (me SqlSessionStore) Remove(sessionIdOrAlt string) StoreChannel { func (me SqlSessionStore) Remove(sessionIdOrToken string) StoreChannel {
storeChannel := make(StoreChannel) storeChannel := make(StoreChannel)
go func() { go func() {
result := StoreResult{} result := StoreResult{}
_, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE Id = :Id Or AltId = :AltId", map[string]interface{}{"Id": sessionIdOrAlt, "AltId": sessionIdOrAlt}) _, err := me.GetMaster().Exec("DELETE FROM Sessions WHERE Id = :Id Or Token = :Token", map[string]interface{}{"Id": sessionIdOrToken, "Token": sessionIdOrToken})
if err != nil { if err != nil {
result.Err = model.NewAppError("SqlSessionStore.RemoveSession", "We couldn't remove the session", "id="+sessionIdOrAlt+", err="+err.Error()) result.Err = model.NewAppError("SqlSessionStore.RemoveSession", "We couldn't remove the session", "id="+sessionIdOrToken+", err="+err.Error())
} }
storeChannel <- result storeChannel <- result
@@ -181,7 +183,6 @@ func (me SqlSessionStore) UpdateRoles(userId, roles string) StoreChannel {
go func() { go func() {
result := StoreResult{} result := StoreResult{}
if _, err := me.GetMaster().Exec("UPDATE Sessions SET Roles = :Roles WHERE UserId = :UserId", map[string]interface{}{"Roles": roles, "UserId": userId}); err != nil { if _, err := me.GetMaster().Exec("UPDATE Sessions SET Roles = :Roles WHERE UserId = :UserId", map[string]interface{}{"Roles": roles, "UserId": userId}); err != nil {
result.Err = model.NewAppError("SqlSessionStore.UpdateRoles", "We couldn't update the roles", "userId="+userId) result.Err = model.NewAppError("SqlSessionStore.UpdateRoles", "We couldn't update the roles", "userId="+userId)
} else { } else {

Просмотреть файл

@@ -80,7 +80,7 @@ func TestSessionRemove(t *testing.T) {
} }
} }
func TestSessionRemoveAlt(t *testing.T) { func TestSessionRemoveToken(t *testing.T) {
Setup() Setup()
s1 := model.Session{} s1 := model.Session{}
@@ -96,7 +96,7 @@ func TestSessionRemoveAlt(t *testing.T) {
} }
} }
Must(store.Session().Remove(s1.AltId)) Must(store.Session().Remove(s1.Token))
if rs2 := (<-store.Session().Get(s1.Id)); rs2.Err == nil { if rs2 := (<-store.Session().Get(s1.Id)); rs2.Err == nil {
t.Fatal("should have been removed") t.Fatal("should have been removed")

Просмотреть файл

@@ -38,6 +38,7 @@ type SqlStore struct {
user UserStore user UserStore
audit AuditStore audit AuditStore
session SessionStore session SessionStore
oauth OAuthStore
} }
func NewSqlStore() Store { func NewSqlStore() Store {
@@ -55,28 +56,36 @@ func NewSqlStore() Store {
utils.Cfg.SqlSettings.Trace) utils.Cfg.SqlSettings.Trace)
} }
// Temporary upgrade code, remove after 0.8.0 release
if sqlStore.DoesColumnExist("Sessions", "AltId") {
sqlStore.GetMaster().Exec("DROP TABLE IF EXISTS Sessions")
}
sqlStore.team = NewSqlTeamStore(sqlStore) sqlStore.team = NewSqlTeamStore(sqlStore)
sqlStore.channel = NewSqlChannelStore(sqlStore) sqlStore.channel = NewSqlChannelStore(sqlStore)
sqlStore.post = NewSqlPostStore(sqlStore) sqlStore.post = NewSqlPostStore(sqlStore)
sqlStore.user = NewSqlUserStore(sqlStore) sqlStore.user = NewSqlUserStore(sqlStore)
sqlStore.audit = NewSqlAuditStore(sqlStore) sqlStore.audit = NewSqlAuditStore(sqlStore)
sqlStore.session = NewSqlSessionStore(sqlStore) sqlStore.session = NewSqlSessionStore(sqlStore)
sqlStore.oauth = NewSqlOAuthStore(sqlStore)
sqlStore.master.CreateTablesIfNotExists() sqlStore.master.CreateTablesIfNotExists()
sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists()
sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
sqlStore.post.(*SqlPostStore).CreateIndexesIfNotExists()
sqlStore.user.(*SqlUserStore).CreateIndexesIfNotExists()
sqlStore.audit.(*SqlAuditStore).CreateIndexesIfNotExists()
sqlStore.session.(*SqlSessionStore).CreateIndexesIfNotExists()
sqlStore.team.(*SqlTeamStore).UpgradeSchemaIfNeeded() sqlStore.team.(*SqlTeamStore).UpgradeSchemaIfNeeded()
sqlStore.channel.(*SqlChannelStore).UpgradeSchemaIfNeeded() sqlStore.channel.(*SqlChannelStore).UpgradeSchemaIfNeeded()
sqlStore.post.(*SqlPostStore).UpgradeSchemaIfNeeded() sqlStore.post.(*SqlPostStore).UpgradeSchemaIfNeeded()
sqlStore.user.(*SqlUserStore).UpgradeSchemaIfNeeded() sqlStore.user.(*SqlUserStore).UpgradeSchemaIfNeeded()
sqlStore.audit.(*SqlAuditStore).UpgradeSchemaIfNeeded() sqlStore.audit.(*SqlAuditStore).UpgradeSchemaIfNeeded()
sqlStore.session.(*SqlSessionStore).UpgradeSchemaIfNeeded() sqlStore.session.(*SqlSessionStore).UpgradeSchemaIfNeeded()
sqlStore.oauth.(*SqlOAuthStore).UpgradeSchemaIfNeeded()
sqlStore.team.(*SqlTeamStore).CreateIndexesIfNotExists()
sqlStore.channel.(*SqlChannelStore).CreateIndexesIfNotExists()
sqlStore.post.(*SqlPostStore).CreateIndexesIfNotExists()
sqlStore.user.(*SqlUserStore).CreateIndexesIfNotExists()
sqlStore.audit.(*SqlAuditStore).CreateIndexesIfNotExists()
sqlStore.session.(*SqlSessionStore).CreateIndexesIfNotExists()
sqlStore.oauth.(*SqlOAuthStore).CreateIndexesIfNotExists()
return sqlStore return sqlStore
} }
@@ -363,6 +372,10 @@ func (ss SqlStore) Audit() AuditStore {
return ss.audit return ss.audit
} }
func (ss SqlStore) OAuth() OAuthStore {
return ss.oauth
}
type mattermConverter struct{} type mattermConverter struct{}
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) { func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {

Просмотреть файл

@@ -34,6 +34,7 @@ type Store interface {
User() UserStore User() UserStore
Audit() AuditStore Audit() AuditStore
Session() SessionStore Session() SessionStore
OAuth() OAuthStore
Close() Close()
} }
@@ -104,9 +105,9 @@ type UserStore interface {
type SessionStore interface { type SessionStore interface {
Save(session *model.Session) StoreChannel Save(session *model.Session) StoreChannel
Get(id string) StoreChannel Get(sessionIdOrToken string) StoreChannel
GetSessions(userId string) StoreChannel GetSessions(userId string) StoreChannel
Remove(sessionIdOrAlt string) StoreChannel Remove(sessionIdOrToken string) StoreChannel
UpdateLastActivityAt(sessionId string, time int64) StoreChannel UpdateLastActivityAt(sessionId string, time int64) StoreChannel
UpdateRoles(userId string, roles string) StoreChannel UpdateRoles(userId string, roles string) StoreChannel
} }
@@ -115,3 +116,17 @@ type AuditStore interface {
Save(audit *model.Audit) StoreChannel Save(audit *model.Audit) StoreChannel
Get(user_id string, limit int) StoreChannel Get(user_id string, limit int) StoreChannel
} }
type OAuthStore interface {
SaveApp(app *model.OAuthApp) StoreChannel
UpdateApp(app *model.OAuthApp) StoreChannel
GetApp(id string) StoreChannel
GetAppByUser(userId string) StoreChannel
SaveAuthData(authData *model.AuthData) StoreChannel
GetAuthData(code string) StoreChannel
RemoveAuthData(code string) StoreChannel
SaveAccessData(accessData *model.AccessData) StoreChannel
GetAccessData(token string) StoreChannel
GetAccessDataByAuthCode(authCode string) StoreChannel
RemoveAccessData(token string) StoreChannel
}

Просмотреть файл

@@ -21,20 +21,21 @@ const (
) )
type ServiceSettings struct { type ServiceSettings struct {
SiteName string SiteName string
Mode string Mode string
AllowTesting bool AllowTesting bool
UseSSL bool UseSSL bool
Port string Port string
Version string Version string
InviteSalt string InviteSalt string
PublicLinkSalt string PublicLinkSalt string
ResetSalt string ResetSalt string
AnalyticsUrl string AnalyticsUrl string
UseLocalStorage bool UseLocalStorage bool
StorageDirectory string StorageDirectory string
AllowedLoginAttempts int AllowedLoginAttempts int
DisableEmailSignUp bool DisableEmailSignUp bool
EnableOAuthServiceProvider bool
} }
type SSOSetting struct { type SSOSetting struct {
@@ -286,6 +287,7 @@ func getClientProperties(c *Config) map[string]string {
props["ProfileHeight"] = fmt.Sprintf("%v", c.ImageSettings.ProfileHeight) props["ProfileHeight"] = fmt.Sprintf("%v", c.ImageSettings.ProfileHeight)
props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth) props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth)
props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth) props["ProfileWidth"] = fmt.Sprintf("%v", c.ImageSettings.ProfileWidth)
props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
return props return props
} }

72
web/react/components/authorize.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,72 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Client = require('../utils/client.jsx');
export default class Authorize extends React.Component {
constructor(props) {
super(props);
this.handleAllow = this.handleAllow.bind(this);
this.handleDeny = this.handleDeny.bind(this);
this.state = {};
}
handleAllow() {
const responseType = this.props.responseType;
const clientId = this.props.clientId;
const redirectUri = this.props.redirectUri;
const state = this.props.state;
const scope = this.props.scope;
Client.allowOAuth2(responseType, clientId, redirectUri, state, scope,
(data) => {
if (data.redirect) {
window.location.replace(data.redirect);
}
},
() => {}
);
}
handleDeny() {
window.location.replace(this.props.redirectUri + '?error=access_denied');
}
render() {
return (
<div className='authorize-box'>
<div className='authorize-inner'>
<h3>{'An application would like to connect to your '}{this.props.teamName}{' account'}</h3>
<label>{'The app '}{this.props.appName}{' would like the ability to access and modify your basic information.'}</label>
<br/>
<br/>
<label>{'Allow '}{this.props.appName}{' access?'}</label>
<br/>
<button
type='submit'
className='btn authorize-btn'
onClick={this.handleDeny}
>
{'Deny'}
</button>
<button
type='submit'
className='btn btn-primary authorize-btn'
onClick={this.handleAllow}
>
{'Allow'}
</button>
</div>
</div>
);
}
}
Authorize.propTypes = {
appName: React.PropTypes.string,
teamName: React.PropTypes.string,
responseType: React.PropTypes.string,
clientId: React.PropTypes.string,
redirectUri: React.PropTypes.string,
state: React.PropTypes.string,
scope: React.PropTypes.string
};

Просмотреть файл

@@ -25,7 +25,7 @@ export default class PopoverListMembers extends React.Component {
$('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true}); $('#member_popover').popover({placement: 'bottom', trigger: 'click', html: true});
$('body').on('click', function onClick(e) { $('body').on('click', function onClick(e) {
if ($(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) { if (e.target.parentNode && $(e.target.parentNode.parentNode)[0] !== $('#member_popover')[0] && $(e.target).parents('.popover.in').length === 0) {
$('#member_popover').popover('hide'); $('#member_popover').popover('hide');
} }
}); });

249
web/react/components/register_app_modal.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,249 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Client = require('../utils/client.jsx');
export default class RegisterAppModal extends React.Component {
constructor() {
super();
this.register = this.register.bind(this);
this.onHide = this.onHide.bind(this);
this.save = this.save.bind(this);
this.state = {clientId: '', clientSecret: '', saved: false};
}
componentDidMount() {
$(React.findDOMNode(this)).on('hide.bs.modal', this.onHide);
}
register() {
var state = this.state;
state.serverError = null;
var app = {};
var name = this.refs.name.getDOMNode().value;
if (!name || name.length === 0) {
state.nameError = 'Application name must be filled in.';
this.setState(state);
return;
}
state.nameError = null;
app.name = name;
var homepage = this.refs.homepage.getDOMNode().value;
if (!homepage || homepage.length === 0) {
state.homepageError = 'Homepage must be filled in.';
this.setState(state);
return;
}
state.homepageError = null;
app.homepage = homepage;
var desc = this.refs.desc.getDOMNode().value;
app.description = desc;
var rawCallbacks = this.refs.callback.getDOMNode().value.trim();
if (!rawCallbacks || rawCallbacks.length === 0) {
state.callbackError = 'At least one callback URL must be filled in.';
this.setState(state);
return;
}
state.callbackError = null;
app.callback_urls = rawCallbacks.split('\n');
Client.registerOAuthApp(app,
(data) => {
state.clientId = data.id;
state.clientSecret = data.client_secret;
this.setState(state);
},
(err) => {
state.serverError = err.message;
this.setState(state);
}
);
}
onHide(e) {
if (!this.state.saved && this.state.clientId !== '') {
e.preventDefault();
return;
}
this.setState({clientId: '', clientSecret: '', saved: false});
}
save() {
this.setState({saved: this.refs.save.getDOMNode().checked});
}
render() {
var nameError;
if (this.state.nameError) {
nameError = <div className='form-group has-error'><label className='control-label'>{this.state.nameError}</label></div>;
}
var homepageError;
if (this.state.homepageError) {
homepageError = <div className='form-group has-error'><label className='control-label'>{this.state.homepageError}</label></div>;
}
var callbackError;
if (this.state.callbackError) {
callbackError = <div className='form-group has-error'><label className='control-label'>{this.state.callbackError}</label></div>;
}
var serverError;
if (this.state.serverError) {
serverError = <div className='form-group has-error'><label className='control-label'>{this.state.serverError}</label></div>;
}
var body = '';
if (this.state.clientId === '') {
body = (
<div className='form-group user-settings'>
<h3>{'Register a New Application'}</h3>
<br/>
<label className='col-sm-4 control-label'>{'Application Name'}</label>
<div className='col-sm-7'>
<input
ref='name'
className='form-control'
type='text'
placeholder='Required'
/>
{nameError}
</div>
<br/>
<br/>
<label className='col-sm-4 control-label'>{'Homepage URL'}</label>
<div className='col-sm-7'>
<input
ref='homepage'
className='form-control'
type='text'
placeholder='Required'
/>
{homepageError}
</div>
<br/>
<br/>
<label className='col-sm-4 control-label'>{'Description'}</label>
<div className='col-sm-7'>
<input
ref='desc'
className='form-control'
type='text'
placeholder='Optional'
/>
</div>
<br/>
<br/>
<label className='col-sm-4 control-label'>{'Callback URL'}</label>
<div className='col-sm-7'>
<textarea
ref='callback'
className='form-control'
type='text'
placeholder='Required'
rows='5'
/>
{callbackError}
</div>
<br/>
<br/>
<br/>
<br/>
<br/>
{serverError}
<a
className='btn btn-sm theme pull-right'
href='#'
data-dismiss='modal'
aria-label='Close'
>
{'Cancel'}
</a>
<a
className='btn btn-sm btn-primary pull-right'
onClick={this.register}
>
{'Register'}
</a>
</div>
);
} else {
var btnClass = ' disabled';
if (this.state.saved) {
btnClass = '';
}
body = (
<div className='form-group user-settings'>
<h3>{'Your Application Credentials'}</h3>
<br/>
<br/>
<label className='col-sm-12 control-label'>{'Client ID: '}{this.state.clientId}</label>
<label className='col-sm-12 control-label'>{'Client Secret: '}{this.state.clientSecret}</label>
<br/>
<br/>
<br/>
<br/>
<strong>{'Save these somewhere SAFE and SECURE. We can retrieve your Client Id if you lose it, but your Client Secret will be lost forever if you were to lose it.'}</strong>
<br/>
<br/>
<div className='checkbox'>
<label>
<input
ref='save'
type='checkbox'
checked={this.state.saved}
onClick={this.save}
>
{'I have saved both my Client Id and Client Secret somewhere safe'}
</input>
</label>
</div>
<a
className={'btn btn-sm btn-primary pull-right' + btnClass}
href='#'
data-dismiss='modal'
aria-label='Close'
>
{'Close'}
</a>
</div>
);
}
return (
<div
className='modal fade'
ref='modal'
id='register_app'
role='dialog'
aria-hidden='true'
>
<div className='modal-dialog'>
<div className='modal-content'>
<div className='modal-header'>
<button
type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>{'x'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
{'Developer Applications'}
</h4>
</div>
<div className='modal-body'>
{body}
</div>
</div>
</div>
</div>
);
}
}

Просмотреть файл

@@ -7,6 +7,7 @@ var NotificationsTab = require('./user_settings_notifications.jsx');
var SecurityTab = require('./user_settings_security.jsx'); var SecurityTab = require('./user_settings_security.jsx');
var GeneralTab = require('./user_settings_general.jsx'); var GeneralTab = require('./user_settings_general.jsx');
var AppearanceTab = require('./user_settings_appearance.jsx'); var AppearanceTab = require('./user_settings_appearance.jsx');
var DeveloperTab = require('./user_settings_developer.jsx');
export default class UserSettings extends React.Component { export default class UserSettings extends React.Component {
constructor(props) { constructor(props) {
@@ -76,6 +77,15 @@ export default class UserSettings extends React.Component {
/> />
</div> </div>
); );
} else if (this.props.activeTab === 'developer') {
return (
<div>
<DeveloperTab
activeSection={this.props.activeSection}
updateSection={this.props.updateSection}
/>
</div>
);
} }
return <div/>; return <div/>;

Просмотреть файл

@@ -0,0 +1,93 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var SettingItemMin = require('./setting_item_min.jsx');
var SettingItemMax = require('./setting_item_max.jsx');
export default class DeveloperTab extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
register() {
$('#user_settings1').modal('hide');
$('#register_app').modal('show');
}
render() {
var appSection;
var self = this;
if (this.props.activeSection === 'app') {
var inputs = [];
inputs.push(
<div className='form-group'>
<div className='col-sm-7'>
<a
className='btn btn-sm btn-primary'
onClick={this.register}
>
{'Register New Application'}
</a>
</div>
</div>
);
appSection = (
<SettingItemMax
title='Applications (Preview)'
inputs={inputs}
updateSection={function updateSection(e) {
self.props.updateSection('');
e.preventDefault();
}}
/>
);
} else {
appSection = (
<SettingItemMin
title='Applications (Preview)'
describe='Open to register a new third-party application'
updateSection={function updateSection() {
self.props.updateSection('app');
}}
/>
);
}
return (
<div>
<div className='modal-header'>
<button
type='button'
className='close'
data-dismiss='modal'
aria-label='Close'
>
<span aria-hidden='true'>{'x'}</span>
</button>
<h4
className='modal-title'
ref='title'
>
<i className='modal-back'></i>{'Developer Settings'}
</h4>
</div>
<div className='user-settings'>
<h3 className='tab-header'>{'Developer Settings'}</h3>
<div className='divider-dark first'/>
{appSection}
<div className='divider-dark'/>
</div>
</div>
);
}
}
DeveloperTab.defaultProps = {
activeSection: ''
};
DeveloperTab.propTypes = {
activeSection: React.PropTypes.string,
updateSection: React.PropTypes.func
};

Просмотреть файл

@@ -17,8 +17,8 @@ export default class UserSettingsModal extends React.Component {
$('body').on('click', '.modal-back', function changeDisplay() { $('body').on('click', '.modal-back', function changeDisplay() {
$(this).closest('.modal-dialog').removeClass('display--content'); $(this).closest('.modal-dialog').removeClass('display--content');
}); });
$('body').on('click', '.modal-header .close', function closeModal() { $('body').on('click', '.modal-header .close', () => {
setTimeout(function finishClose() { setTimeout(() => {
$('.modal-dialog.display--content').removeClass('display--content'); $('.modal-dialog.display--content').removeClass('display--content');
}, 500); }, 500);
}); });
@@ -35,6 +35,9 @@ export default class UserSettingsModal extends React.Component {
tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'}); tabs.push({name: 'security', uiName: 'Security', icon: 'glyphicon glyphicon-lock'});
tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'}); tabs.push({name: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'}); tabs.push({name: 'appearance', uiName: 'Appearance', icon: 'glyphicon glyphicon-wrench'});
if (global.window.config.EnableOAuthServiceProvider) {
tabs.push({name: 'developer', uiName: 'Developer', icon: 'glyphicon glyphicon-th'});
}
return ( return (
<div <div
@@ -54,13 +57,13 @@ export default class UserSettingsModal extends React.Component {
data-dismiss='modal' data-dismiss='modal'
aria-label='Close' aria-label='Close'
> >
<span aria-hidden='true'>&times;</span> <span aria-hidden='true'>{'x'}</span>
</button> </button>
<h4 <h4
className='modal-title' className='modal-title'
ref='title' ref='title'
> >
Account Settings {'Account Settings'}
</h4> </h4>
</div> </div>
<div className='modal-body'> <div className='modal-body'>

21
web/react/pages/authorize.jsx Обычный файл
Просмотреть файл

@@ -0,0 +1,21 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
var Authorize = require('../components/authorize.jsx');
function setupAuthorizePage(teamName, appName, responseType, clientId, redirectUri, scope, state) {
React.render(
<Authorize
teamName={teamName}
appName={appName}
responseType={responseType}
clientId={clientId}
redirectUri={redirectUri}
scope={scope}
state={state}
/>,
document.getElementById('authorize')
);
}
global.window.setup_authorize_page = setupAuthorizePage;

Просмотреть файл

@@ -33,6 +33,7 @@ var AccessHistoryModal = require('../components/access_history_modal.jsx');
var ActivityLogModal = require('../components/activity_log_modal.jsx'); var ActivityLogModal = require('../components/activity_log_modal.jsx');
var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx'); var RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
var FileUploadOverlay = require('../components/file_upload_overlay.jsx'); var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
var RegisterAppModal = require('../components/register_app_modal.jsx');
var Constants = require('../utils/constants.jsx'); var Constants = require('../utils/constants.jsx');
var ActionTypes = Constants.ActionTypes; var ActionTypes = Constants.ActionTypes;
@@ -222,6 +223,11 @@ function setupChannelPage(props) {
/>, />,
document.getElementById('file_upload_overlay') document.getElementById('file_upload_overlay')
); );
React.render(
<RegisterAppModal />,
document.getElementById('register_app_modal')
);
} }
global.window.setup_channel_page = setupChannelPage; global.window.setup_channel_page = setupChannelPage;

Просмотреть файл

@@ -987,3 +987,36 @@ export function updateValetFeature(data, success, error) {
track('api', 'api_teams_update_valet_feature'); track('api', 'api_teams_update_valet_feature');
} }
export function registerOAuthApp(app, success, error) {
$.ajax({
url: '/api/v1/oauth/register',
dataType: 'json',
contentType: 'application/json',
type: 'POST',
data: JSON.stringify(app),
success: success,
error: (xhr, status, err) => {
const e = handleError('registerApp', xhr, status, err);
error(e);
}
});
module.exports.track('api', 'api_apps_register');
}
export function allowOAuth2(responseType, clientId, redirectUri, state, scope, success, error) {
$.ajax({
url: '/api/v1/oauth/allow?response_type=' + responseType + '&client_id=' + clientId + '&redirect_uri=' + redirectUri + '&scope=' + scope + '&state=' + state,
dataType: 'json',
contentType: 'application/json',
type: 'GET',
success: success,
error: (xhr, status, err) => {
const e = handleError('allowOAuth2', xhr, status, err);
error(e);
}
});
module.exports.track('api', 'api_users_allow_oauth2');
}

Просмотреть файл

@@ -315,3 +315,18 @@
} }
} }
.authorize-box {
margin: 100px auto;
width:500px;
height:280px;
border: 1px solid black;
}
.authorize-inner {
padding: 20px;
}
.authorize-btn {
margin-right: 6px;
}

26
web/templates/authorize.html Обычный файл
Просмотреть файл

@@ -0,0 +1,26 @@
{{define "authorize"}}
<html>
{{template "head" . }}
<body class="white">
<div class="container-fluid">
<div class="inner__wrap">
<div class="row content">
<div class="signup-header">
{{.Props.TeamName}}
</div>
<div class="col-sm-12">
<div id="authorize"></div>
</div>
<div class="footer-push"></div>
</div>
<div class="row footer">
{{template "footer" . }}
</div>
</div>
</div>
<script>
window.setup_authorize_page('{{ .Props.TeamName }}', '{{ .Props.AppName }}', '{{ .Props.ResponseType }}', '{{ .Props.ClientId }}', '{{ .Props.RedirectUri }}', '{{ .Props.Scope }}', '{{ .Props.State }}' );
</script>
</body>
</html>
{{end}}

Просмотреть файл

@@ -49,6 +49,7 @@
<div id="access_history_modal"></div> <div id="access_history_modal"></div>
<div id="activity_log_modal"></div> <div id="activity_log_modal"></div>
<div id="removed_from_channel_modal"></div> <div id="removed_from_channel_modal"></div>
<div id="register_app_modal"></div>
<script> <script>
window.setup_channel_page({{ .Props }}); window.setup_channel_page({{ .Props }});
$('body').tooltip( {selector: '[data-toggle=tooltip]'} ); $('body').tooltip( {selector: '[data-toggle=tooltip]'} );

Просмотреть файл

@@ -4,19 +4,18 @@
package web package web
import ( import (
"fmt"
"html/template"
"net/http"
"strconv"
"strings"
l4g "code.google.com/p/log4go" l4g "code.google.com/p/log4go"
"fmt"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/platform/api" "github.com/mattermost/platform/api"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
"github.com/mssola/user_agent" "github.com/mssola/user_agent"
"gopkg.in/fsnotify.v1" "gopkg.in/fsnotify.v1"
"html/template"
"net/http"
"strconv"
"strings"
) )
var Templates *template.Template var Templates *template.Template
@@ -50,6 +49,8 @@ func InitWeb() {
mainrouter.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir)))) mainrouter.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
mainrouter.Handle("/", api.AppHandlerIndependent(root)).Methods("GET") mainrouter.Handle("/", api.AppHandlerIndependent(root)).Methods("GET")
mainrouter.Handle("/oauth/authorize", api.UserRequired(authorizeOAuth)).Methods("GET")
mainrouter.Handle("/oauth/access_token", api.ApiAppHandler(getAccessToken)).Methods("POST")
mainrouter.Handle("/signup_team_complete/", api.AppHandlerIndependent(signupTeamComplete)).Methods("GET") mainrouter.Handle("/signup_team_complete/", api.AppHandlerIndependent(signupTeamComplete)).Methods("GET")
mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET") mainrouter.Handle("/signup_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET")
@@ -63,7 +64,7 @@ func InitWeb() {
mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET") mainrouter.Handle("/admin_console", api.UserRequired(adminConsole)).Methods("GET")
// ---------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------
// *ANYTHING* team spefic should go below this line // *ANYTHING* team specific should go below this line
// ---------------------------------------------------------------------------------------------- // ----------------------------------------------------------------------------------------------
mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}", api.AppHandler(login)).Methods("GET") mainrouter.Handle("/{team:[A-Za-z0-9-]+(__)?[A-Za-z0-9-]+}", api.AppHandler(login)).Methods("GET")
@@ -648,3 +649,192 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
page := NewHtmlTemplatePage("admin_console", "Admin Console") page := NewHtmlTemplatePage("admin_console", "Admin Console")
page.Render(c, w) page.Render(c, w)
} }
func authorizeOAuth(c *api.Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
c.Err = model.NewAppError("authorizeOAuth", "The system admin has turned off OAuth service providing.", "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
if !CheckBrowserCompatability(c, r) {
return
}
responseType := r.URL.Query().Get("response_type")
clientId := r.URL.Query().Get("client_id")
redirect := r.URL.Query().Get("redirect_uri")
scope := r.URL.Query().Get("scope")
state := r.URL.Query().Get("state")
if len(responseType) == 0 || len(clientId) == 0 || len(redirect) == 0 {
c.Err = model.NewAppError("authorizeOAuth", "Missing one or more of response_type, client_id, or redirect_uri", "")
return
}
var app *model.OAuthApp
if result := <-api.Srv.Store.OAuth().GetApp(clientId); result.Err != nil {
c.Err = result.Err
return
} else {
app = result.Data.(*model.OAuthApp)
}
var team *model.Team
if result := <-api.Srv.Store.Team().Get(c.Session.TeamId); result.Err != nil {
c.Err = result.Err
return
} else {
team = result.Data.(*model.Team)
}
page := NewHtmlTemplatePage("authorize", "Authorize Application")
page.Props["TeamName"] = team.Name
page.Props["AppName"] = app.Name
page.Props["ResponseType"] = responseType
page.Props["ClientId"] = clientId
page.Props["RedirectUri"] = redirect
page.Props["Scope"] = scope
page.Props["State"] = state
page.Render(c, w)
}
func getAccessToken(c *api.Context, w http.ResponseWriter, r *http.Request) {
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
c.Err = model.NewAppError("getAccessToken", "The system admin has turned off OAuth service providing.", "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
c.LogAudit("attempt")
r.ParseForm()
grantType := r.FormValue("grant_type")
if grantType != model.ACCESS_TOKEN_GRANT_TYPE {
c.Err = model.NewAppError("getAccessToken", "invalid_request: Bad grant_type", "")
return
}
clientId := r.FormValue("client_id")
if len(clientId) != 26 {
c.Err = model.NewAppError("getAccessToken", "invalid_request: Bad client_id", "")
return
}
secret := r.FormValue("client_secret")
if len(secret) == 0 {
c.Err = model.NewAppError("getAccessToken", "invalid_request: Missing client_secret", "")
return
}
code := r.FormValue("code")
if len(code) == 0 {
c.Err = model.NewAppError("getAccessToken", "invalid_request: Missing code", "")
return
}
redirectUri := r.FormValue("redirect_uri")
achan := api.Srv.Store.OAuth().GetApp(clientId)
tchan := api.Srv.Store.OAuth().GetAccessDataByAuthCode(code)
authData := api.GetAuthData(code)
if authData == nil {
c.LogAudit("fail - invalid auth code")
c.Err = model.NewAppError("getAccessToken", "invalid_grant: Invalid or expired authorization code", "")
return
}
uchan := api.Srv.Store.User().Get(authData.UserId)
if authData.IsExpired() {
c.LogAudit("fail - auth code expired")
c.Err = model.NewAppError("getAccessToken", "invalid_grant: Invalid or expired authorization code", "")
return
}
if authData.RedirectUri != redirectUri {
c.LogAudit("fail - redirect uri provided did not match previous redirect uri")
c.Err = model.NewAppError("getAccessToken", "invalid_request: Supplied redirect_uri does not match authorization code redirect_uri", "")
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.NewAppError("getAccessToken", "invalid_grant: Invalid or expired authorization code", "")
return
}
var app *model.OAuthApp
if result := <-achan; result.Err != nil {
c.Err = model.NewAppError("getAccessToken", "invalid_client: Invalid client credentials", "")
return
} else {
app = result.Data.(*model.OAuthApp)
}
if !model.ComparePassword(app.ClientSecret, secret) {
c.LogAudit("fail - invalid client credentials")
c.Err = model.NewAppError("getAccessToken", "invalid_client: Invalid client credentials", "")
return
}
callback := redirectUri
if len(callback) == 0 {
callback = app.CallbackUrls[0]
}
if result := <-tchan; result.Err != nil {
c.Err = model.NewAppError("getAccessToken", "server_error: Encountered internal server error while accessing database", "")
return
} else if result.Data != nil {
c.LogAudit("fail - auth code has been used previously")
accessData := result.Data.(*model.AccessData)
// Revoke access token, related auth code, and session from DB as well as from cache
if err := api.RevokeAccessToken(accessData.Token); err != nil {
l4g.Error("Encountered an error revoking an access token, err=" + err.Message)
}
c.Err = model.NewAppError("getAccessToken", "invalid_grant: Authorization code already exchanged for an access token", "")
return
}
var user *model.User
if result := <-uchan; result.Err != nil {
c.Err = model.NewAppError("getAccessToken", "server_error: Encountered internal server error while pulling user from database", "")
return
} else {
user = result.Data.(*model.User)
}
session := &model.Session{UserId: user.Id, TeamId: user.TeamId, Roles: user.Roles, IsOAuth: true}
if result := <-api.Srv.Store.Session().Save(session); result.Err != nil {
c.Err = model.NewAppError("getAccessToken", "server_error: Encountered internal server error while saving session to database", "")
return
} else {
session = result.Data.(*model.Session)
api.AddSessionToCache(session)
}
accessData := &model.AccessData{AuthCode: authData.Code, Token: session.Token, RedirectUri: callback}
if result := <-api.Srv.Store.OAuth().SaveAccessData(accessData); result.Err != nil {
l4g.Error(result.Err)
c.Err = model.NewAppError("getAccessToken", "server_error: Encountered internal server error while saving access token to database", "")
return
}
accessRsp := &model.AccessResponse{AccessToken: session.Token, TokenType: model.ACCESS_TOKEN_TYPE, ExpiresIn: model.SESSION_TIME_OAUTH_IN_SECS}
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")
w.Write([]byte(accessRsp.ToJson()))
}

Просмотреть файл

@@ -6,8 +6,11 @@ package web
import ( import (
"github.com/mattermost/platform/api" "github.com/mattermost/platform/api"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils" "github.com/mattermost/platform/utils"
"net/http" "net/http"
"net/url"
"strings"
"testing" "testing"
"time" "time"
) )
@@ -23,7 +26,7 @@ func Setup() {
api.InitApi() api.InitApi()
InitWeb() InitWeb()
URL = "http://localhost:" + utils.Cfg.ServiceSettings.Port URL = "http://localhost:" + utils.Cfg.ServiceSettings.Port
ApiClient = model.NewClient(URL + "/api/v1") ApiClient = model.NewClient(URL)
} }
} }
@@ -48,6 +51,135 @@ func TestStatic(t *testing.T) {
} }
} }
func TestGetAccessToken(t *testing.T) {
Setup()
team := model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
rteam, _ := ApiClient.CreateTeam(&team)
user := model.User{TeamId: rteam.Data.(*model.Team).Id, Email: strings.ToLower(model.NewId()) + "corey@test.com", Password: "pwd"}
ruser := ApiClient.Must(ApiClient.CreateUser(&user, "")).Data.(*model.User)
store.Must(api.Srv.Store.User().VerifyEmail(ruser.Id))
app := &model.OAuthApp{Name: "TestApp" + model.NewId(), Homepage: "https://nowhere.com", Description: "test", CallbackUrls: []string{"https://nowhere.com"}}
if !utils.Cfg.ServiceSettings.EnableOAuthServiceProvider {
data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{"12345678901234567890123456"}, "client_secret": []string{"12345678901234567890123456"}, "code": []string{"junk"}, "redirect_uri": []string{app.CallbackUrls[0]}}
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - oauth providing turned off")
}
} else {
ApiClient.Must(ApiClient.LoginById(ruser.Id, "pwd"))
app = ApiClient.Must(ApiClient.RegisterApp(app)).Data.(*model.OAuthApp)
redirect := ApiClient.Must(ApiClient.AllowOAuth(model.AUTHCODE_RESPONSE_TYPE, app.Id, app.CallbackUrls[0], "all", "123")).Data.(map[string]string)["redirect"]
rurl, _ := url.Parse(redirect)
ApiClient.Logout()
data := url.Values{"grant_type": []string{"junk"}, "client_id": []string{app.Id}, "client_secret": []string{app.ClientSecret}, "code": []string{rurl.Query().Get("code")}, "redirect_uri": []string{app.CallbackUrls[0]}}
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - bad grant type")
}
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
data.Set("client_id", "")
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - missing client id")
}
data.Set("client_id", "junk")
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - bad client id")
}
data.Set("client_id", app.Id)
data.Set("client_secret", "")
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - missing client secret")
}
data.Set("client_secret", "junk")
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - bad client secret")
}
data.Set("client_secret", app.ClientSecret)
data.Set("code", "")
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - missing code")
}
data.Set("code", "junk")
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - bad code")
}
data.Set("code", rurl.Query().Get("code"))
data.Set("redirect_uri", "junk")
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - non-matching redirect uri")
}
// reset data for successful request
data.Set("grant_type", model.ACCESS_TOKEN_GRANT_TYPE)
data.Set("client_id", app.Id)
data.Set("client_secret", app.ClientSecret)
data.Set("code", rurl.Query().Get("code"))
data.Set("redirect_uri", app.CallbackUrls[0])
token := ""
if result, err := ApiClient.GetAccessToken(data); err != nil {
t.Fatal(err)
} else {
rsp := result.Data.(*model.AccessResponse)
if len(rsp.AccessToken) == 0 {
t.Fatal("access token not returned")
} else {
token = rsp.AccessToken
}
if rsp.TokenType != model.ACCESS_TOKEN_TYPE {
t.Fatal("access token type incorrect")
}
}
if result, err := ApiClient.DoApiGet("/users/profiles?access_token="+token, "", ""); err != nil {
t.Fatal(err)
} else {
userMap := model.UserMapFromJson(result.Body)
if len(userMap) == 0 {
t.Fatal("user map empty - did not get results correctly")
}
}
if _, err := ApiClient.DoApiGet("/users/profiles", "", ""); err == nil {
t.Fatal("should have failed - no access token provided")
}
if _, err := ApiClient.DoApiGet("/users/profiles?access_token=junk", "", ""); err == nil {
t.Fatal("should have failed - bad access token provided")
}
ApiClient.SetOAuthToken(token)
if result, err := ApiClient.DoApiGet("/users/profiles", "", ""); err != nil {
t.Fatal(err)
} else {
userMap := model.UserMapFromJson(result.Body)
if len(userMap) == 0 {
t.Fatal("user map empty - did not get results correctly")
}
}
if _, err := ApiClient.GetAccessToken(data); err == nil {
t.Fatal("should have failed - tried to reuse auth code")
}
ApiClient.ClearOAuthToken()
}
}
func TestZZWebTearDown(t *testing.T) { func TestZZWebTearDown(t *testing.T) {
// *IMPORTANT* // *IMPORTANT*
// This should be the last function in any test file // This should be the last function in any test file