mirror of
https://github.com/mattermost/mattermost.git
synced 2025-02-25 18:55:24 -06:00
Merge pull request #693 from mattermost/mm-1419
Implement OAuth2 service provider functionality.
This commit is contained in:
@@ -43,6 +43,7 @@ func InitApi() {
|
||||
InitFile(r)
|
||||
InitCommand(r)
|
||||
InitAdmin(r)
|
||||
InitOAuth(r)
|
||||
|
||||
templatesDir := utils.FindDir("api/templates")
|
||||
l4g.Debug("Parsing server templates at %v", templatesDir)
|
||||
|
||||
@@ -17,7 +17,7 @@ func Setup() {
|
||||
NewServer()
|
||||
StartServer()
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -627,7 +627,7 @@ func TestGetChannelExtraInfo(t *testing.T) {
|
||||
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 = 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])
|
||||
}
|
||||
}
|
||||
client := model.NewClient(c.GetSiteURL() + "/api/v1")
|
||||
client := model.NewClient(c.GetSiteURL())
|
||||
|
||||
if doTeams {
|
||||
if err := CreateBasicUser(client); err != nil {
|
||||
@@ -375,7 +375,7 @@ func loadTestUsersCommand(c *Context, command *model.Command) bool {
|
||||
if err == false {
|
||||
usersr = utils.Range{10, 15}
|
||||
}
|
||||
client := model.NewClient(c.GetSiteURL() + "/api/v1")
|
||||
client := model.NewClient(c.GetSiteURL())
|
||||
userCreator := NewAutoUserCreator(client, c.Session.TeamId)
|
||||
userCreator.Fuzzy = doFuzz
|
||||
userCreator.CreateTestUsers(usersr)
|
||||
@@ -405,7 +405,7 @@ func loadTestChannelsCommand(c *Context, command *model.Command) bool {
|
||||
if err == false {
|
||||
channelsr = utils.Range{20, 30}
|
||||
}
|
||||
client := model.NewClient(c.GetSiteURL() + "/api/v1")
|
||||
client := model.NewClient(c.GetSiteURL())
|
||||
client.MockSession(c.Session.Id)
|
||||
channelCreator := NewAutoChannelCreator(client, c.Session.TeamId)
|
||||
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)
|
||||
testPoster := NewAutoPostCreator(client, command.ChannelId)
|
||||
testPoster.Fuzzy = doFuzz
|
||||
|
||||
@@ -80,9 +80,36 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
c.RequestId = model.NewId()
|
||||
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"
|
||||
|
||||
// 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
|
||||
if utils.Cfg.ServiceSettings.UseSSL {
|
||||
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("Content-Security-Policy", "frame-ancestors none")
|
||||
} 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")
|
||||
}
|
||||
|
||||
sessionId := ""
|
||||
|
||||
// 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 != "" {
|
||||
|
||||
if len(token) != 0 {
|
||||
var session *model.Session
|
||||
if ts, ok := sessionCache.Get(sessionId); ok {
|
||||
if ts, ok := sessionCache.Get(token); ok {
|
||||
session = ts.(*model.Session)
|
||||
}
|
||||
|
||||
if session == nil {
|
||||
if sessionResult := <-Srv.Store.Session().Get(sessionId); sessionResult.Err != nil {
|
||||
c.LogError(model.NewAppError("ServeHTTP", "Invalid session", "id="+sessionId+", err="+sessionResult.Err.DetailedError))
|
||||
if sessionResult := <-Srv.Store.Session().Get(token); sessionResult.Err != nil {
|
||||
c.LogError(model.NewAppError("ServeHTTP", "Invalid session", "token="+token+", err="+sessionResult.Err.DetailedError))
|
||||
} else {
|
||||
session = sessionResult.Data.(*model.Session)
|
||||
}
|
||||
@@ -142,7 +152,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
if session == nil || session.IsExpired() {
|
||||
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
|
||||
} else {
|
||||
c.Session = *session
|
||||
@@ -166,10 +179,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
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() {
|
||||
if err := (<-Srv.Store.User().UpdateUserAndSessionActivity(c.Session.UserId, sessionId, 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)
|
||||
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, c.Session.Id, err)
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -197,7 +210,7 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
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 {
|
||||
c.LogError(r.Err)
|
||||
}
|
||||
@@ -209,7 +222,7 @@ func (c *Context) LogAuditWithUserId(userId, extraInfo string) {
|
||||
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 {
|
||||
c.LogError(r.Err)
|
||||
}
|
||||
@@ -315,7 +328,7 @@ func (c *Context) IsTeamAdmin(userId string) bool {
|
||||
|
||||
func (c *Context) RemoveSessionCookie(w http.ResponseWriter) {
|
||||
|
||||
sessionCache.Remove(c.Session.Id)
|
||||
sessionCache.Remove(c.Session.Token)
|
||||
|
||||
cookie := &http.Cookie{
|
||||
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))
|
||||
RenderWebError(err, w, r)
|
||||
}
|
||||
|
||||
func AddSessionToCache(session *model.Session) {
|
||||
sessionCache.Add(session.Token, session)
|
||||
}
|
||||
|
||||
165
api/oauth.go
Normal file
165
api/oauth.go
Normal file
@@ -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
Normal file
157
api/oauth_test.go
Normal file
@@ -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")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func TestCreateValetPost(t *testing.T) {
|
||||
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")
|
||||
}
|
||||
} 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")
|
||||
}
|
||||
}
|
||||
|
||||
42
api/user.go
42
api/user.go
@@ -336,7 +336,7 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
|
||||
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
|
||||
|
||||
@@ -378,13 +378,13 @@ func Login(c *Context, w http.ResponseWriter, r *http.Request, user *model.User,
|
||||
return
|
||||
} else {
|
||||
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{
|
||||
Name: model.SESSION_TOKEN,
|
||||
Value: session.Id,
|
||||
Value: session.Token,
|
||||
Path: "/",
|
||||
MaxAge: maxAge,
|
||||
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) {
|
||||
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
|
||||
return
|
||||
} else {
|
||||
sessions := result.Data.([]*model.Session)
|
||||
session := result.Data.(*model.Session)
|
||||
|
||||
for _, session := range sessions {
|
||||
if session.AltId == altId {
|
||||
c.LogAudit("session_id=" + session.AltId)
|
||||
sessionCache.Remove(session.Id)
|
||||
if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte(model.MapToJson(props)))
|
||||
return
|
||||
}
|
||||
c.LogAudit("session_id=" + session.Id)
|
||||
|
||||
if session.IsOAuth {
|
||||
RevokeAccessToken(session.Token)
|
||||
} else {
|
||||
sessionCache.Remove(session.Token)
|
||||
|
||||
if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
return
|
||||
} else {
|
||||
w.Write([]byte(model.MapToJson(props)))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -462,8 +464,8 @@ func RevokeAllSession(c *Context, userId string) {
|
||||
sessions := result.Data.([]*model.Session)
|
||||
|
||||
for _, session := range sessions {
|
||||
c.LogAuditWithUserId(userId, "session_id="+session.AltId)
|
||||
sessionCache.Remove(session.Id)
|
||||
c.LogAuditWithUserId(userId, "session_id="+session.Id)
|
||||
sessionCache.Remove(session.Token)
|
||||
if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
|
||||
c.Err = result.Err
|
||||
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")
|
||||
}
|
||||
}
|
||||
@@ -190,11 +190,11 @@ func TestSessions(t *testing.T) {
|
||||
|
||||
for _, session := range sessions {
|
||||
if session.DeviceId == deviceId {
|
||||
otherSession = session.AltId
|
||||
otherSession = session.Id
|
||||
}
|
||||
|
||||
if len(session.Id) != 0 {
|
||||
t.Fatal("shouldn't return sessions")
|
||||
if len(session.Token) != 0 {
|
||||
t.Fatal("shouldn't return session tokens")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,11 +212,6 @@ func TestSessions(t *testing.T) {
|
||||
if len(sessions2) != 1 {
|
||||
t.Fatal("invalid number of sessions")
|
||||
}
|
||||
|
||||
if _, err := Client.RevokeSession(otherSession); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetUser(t *testing.T) {
|
||||
@@ -355,7 +350,7 @@ func TestUserCreateImage(t *testing.T) {
|
||||
|
||||
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 {
|
||||
var auth aws.Auth
|
||||
@@ -453,7 +448,7 @@ func TestUserUploadProfileImage(t *testing.T) {
|
||||
t.Fatal(upErr)
|
||||
}
|
||||
|
||||
Client.DoGet("/users/"+user.Id+"/image", "", "")
|
||||
Client.DoApiGet("/users/"+user.Id+"/image", "", "")
|
||||
|
||||
if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
|
||||
var auth aws.Auth
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"UseLocalStorage": true,
|
||||
"StorageDirectory": "./data/",
|
||||
"AllowedLoginAttempts": 10,
|
||||
"DisableEmailSignUp": false
|
||||
"DisableEmailSignUp": false,
|
||||
"EnableOAuthServiceProvider": false
|
||||
},
|
||||
"SSOSettings": {
|
||||
"gitlab": {
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"UseLocalStorage": true,
|
||||
"StorageDirectory": "/mattermost/data/",
|
||||
"AllowedLoginAttempts": 10,
|
||||
"DisableEmailSignUp": false
|
||||
"DisableEmailSignUp": false,
|
||||
"EnableOAuthServiceProvider": false
|
||||
},
|
||||
"SSOSettings": {
|
||||
"gitlab": {
|
||||
|
||||
@@ -23,7 +23,8 @@
|
||||
"UseLocalStorage": true,
|
||||
"StorageDirectory": "/mattermost/data/",
|
||||
"AllowedLoginAttempts": 10,
|
||||
"DisableEmailSignUp": false
|
||||
"DisableEmailSignUp": false,
|
||||
"EnableOAuthServiceProvider": false
|
||||
},
|
||||
"SSOSettings": {
|
||||
"gitlab": {
|
||||
|
||||
@@ -53,7 +53,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 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
|
||||
username, ok1 := params["username"]
|
||||
@@ -65,7 +65,7 @@ func manualTest(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
// Create team for testing
|
||||
team := &model.Team{
|
||||
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),
|
||||
Type: model.TEAM_OPEN,
|
||||
}
|
||||
|
||||
@@ -9,17 +9,69 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
ACCESS_TOKEN_GRANT_TYPE = "authorization_code"
|
||||
ACCESS_TOKEN_TYPE = "bearer"
|
||||
ACCESS_TOKEN_GRANT_TYPE = "authorization_code"
|
||||
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 {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int32 `json:"expires_in"`
|
||||
Scope string `json:"scope"`
|
||||
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 {
|
||||
b, err := json.Marshal(ar)
|
||||
if err != nil {
|
||||
|
||||
41
model/access_test.go
Normal file
41
model/access_test.go
Normal file
@@ -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
Normal file
103
model/authorize.go
Normal file
@@ -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
Normal file
66
model/authorize_test.go
Normal file
@@ -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()
|
||||
}
|
||||
}
|
||||
192
model/client.go
192
model/client.go
@@ -23,7 +23,9 @@ const (
|
||||
HEADER_FORWARDED = "X-Forwarded-For"
|
||||
HEADER_FORWARDED_PROTO = "X-Forwarded-Proto"
|
||||
HEADER_TOKEN = "token"
|
||||
HEADER_BEARER = "BEARER"
|
||||
HEADER_AUTH = "Authorization"
|
||||
API_URL_SUFFIX = "/api/v1"
|
||||
)
|
||||
|
||||
type Result struct {
|
||||
@@ -33,22 +35,37 @@ type Result 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
|
||||
AuthToken string
|
||||
AuthType string
|
||||
}
|
||||
|
||||
// NewClient constructs a new client with convienence methods for talking to
|
||||
// the server.
|
||||
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.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 {
|
||||
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 {
|
||||
@@ -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) {
|
||||
rq, _ := http.NewRequest("GET", c.Url+url, strings.NewReader(data))
|
||||
func (c *Client) DoApiGet(url string, data string, etag string) (*http.Response, *AppError) {
|
||||
rq, _ := http.NewRequest("GET", c.ApiUrl+url, strings.NewReader(data))
|
||||
|
||||
if len(etag) > 0 {
|
||||
rq.Header.Set(HEADER_ETAG_CLIENT, etag)
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -106,7 +123,7 @@ func (c *Client) SignupTeam(email string, displayName string) (*Result, *AppErro
|
||||
m := make(map[string]string)
|
||||
m["email"] = email
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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["name"] = name
|
||||
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
|
||||
} else {
|
||||
val := false
|
||||
@@ -152,7 +169,7 @@ func (c *Client) FindTeamByName(name string, allServers bool) (*Result, *AppErro
|
||||
func (c *Client) FindTeams(email string) (*Result, *AppError) {
|
||||
m := make(map[string]string)
|
||||
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
|
||||
} else {
|
||||
|
||||
@@ -164,7 +181,7 @@ func (c *Client) FindTeams(email string) (*Result, *AppError) {
|
||||
func (c *Client) FindTeamsSendEmail(email string) (*Result, *AppError) {
|
||||
m := make(map[string]string)
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/users/"+id, "", etag); err != nil {
|
||||
if r, err := c.DoApiGet("/users/"+id, "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/users/me", "", etag); err != nil {
|
||||
if r, err := c.DoApiGet("/users/me", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/users/profiles", "", etag); err != nil {
|
||||
if r, err := c.DoApiGet("/users/profiles", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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", "")
|
||||
}
|
||||
|
||||
@@ -285,21 +303,32 @@ func (c *Client) login(m map[string]string) (*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
|
||||
} else {
|
||||
c.AuthToken = ""
|
||||
c.AuthType = HEADER_BEARER
|
||||
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
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) {
|
||||
m := make(map[string]string)
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/users/"+id+"/sessions", "", ""); err != nil {
|
||||
if r, err := c.DoApiGet("/users/"+id+"/sessions", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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["channelId"] = channelId
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/admin/logs", "", ""); err != nil {
|
||||
if r, err := c.DoApiGet("/admin/logs", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return &Result{r.Header.Get(HEADER_REQUEST_ID),
|
||||
@@ -348,7 +377,7 @@ func (c *Client) GetLogs() (*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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/channels/", "", etag); err != nil {
|
||||
if r, err := c.DoApiGet("/channels/", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/channels/"+id+"/", "", etag); err != nil {
|
||||
if r, err := c.DoApiGet("/channels/"+id+"/", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/channels/more", "", etag); err != nil {
|
||||
if r, err := c.DoApiGet("/channels/more", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/channels/counts", "", etag); err != nil {
|
||||
if r, err := c.DoApiGet("/channels/counts", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoPost("/channels/"+id+"/join", ""); err != nil {
|
||||
if r, err := c.DoApiPost("/channels/"+id+"/join", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoPost("/channels/"+id+"/leave", ""); err != nil {
|
||||
if r, err := c.DoApiPost("/channels/"+id+"/leave", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoPost("/channels/"+id+"/delete", ""); err != nil {
|
||||
if r, err := c.DoApiPost("/channels/"+id+"/delete", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
data := make(map[string]string)
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
data := make(map[string]string)
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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)
|
||||
|
||||
if len(c.AuthToken) > 0 {
|
||||
@@ -599,7 +628,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
|
||||
if isFullUrl {
|
||||
rq, _ = http.NewRequest("GET", url, nil)
|
||||
} 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 {
|
||||
@@ -618,7 +647,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
|
||||
|
||||
func (c *Client) GetFileInfo(url string) (*Result, *AppError) {
|
||||
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 {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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["user_id"] = userId
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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["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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
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
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/users/status", "", ""); err != nil {
|
||||
if r, err := c.DoApiGet("/users/status", "", ""); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
if r, err := c.DoGet("/teams/me", "", etag); err != nil {
|
||||
if r, err := c.DoApiGet("/teams/me", "", etag); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
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) {
|
||||
c.AuthToken = sessionToken
|
||||
}
|
||||
|
||||
151
model/oauth.go
Normal file
151
model/oauth.go
Normal file
@@ -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
Normal file
95
model/oauth_test.go
Normal file
@@ -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_MOBILE_IN_DAYS = 30
|
||||
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_SIZE = 10000
|
||||
SESSION_PROP_PLATFORM = "platform"
|
||||
@@ -23,7 +25,7 @@ const (
|
||||
|
||||
type Session struct {
|
||||
Id string `json:"id"`
|
||||
AltId string `json:"alt_id"`
|
||||
Token string `json:"token"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
ExpiresAt int64 `json:"expires_at"`
|
||||
LastActivityAt int64 `json:"last_activity_at"`
|
||||
@@ -31,6 +33,7 @@ type Session struct {
|
||||
TeamId string `json:"team_id"`
|
||||
DeviceId string `json:"device_id"`
|
||||
Roles string `json:"roles"`
|
||||
IsOAuth bool `json:"is_oauth"`
|
||||
Props StringMap `json:"props"`
|
||||
}
|
||||
|
||||
@@ -59,7 +62,7 @@ func (me *Session) PreSave() {
|
||||
me.Id = NewId()
|
||||
}
|
||||
|
||||
me.AltId = NewId()
|
||||
me.Token = NewId()
|
||||
|
||||
me.CreateAt = GetMillis()
|
||||
me.LastActivityAt = me.CreateAt
|
||||
@@ -70,7 +73,7 @@ func (me *Session) PreSave() {
|
||||
}
|
||||
|
||||
func (me *Session) Sanitize() {
|
||||
me.Id = ""
|
||||
me.Token = ""
|
||||
}
|
||||
|
||||
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
|
||||
StatusCode int `json:"status_code"` // The http status code
|
||||
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 {
|
||||
@@ -65,6 +66,7 @@ func NewAppError(where string, message string, details string) *AppError {
|
||||
ap.Where = where
|
||||
ap.DetailedError = details
|
||||
ap.StatusCode = 500
|
||||
ap.IsOAuth = false
|
||||
return ap
|
||||
}
|
||||
|
||||
|
||||
334
store/sql_oauth_store.go
Normal file
334
store/sql_oauth_store.go
Normal file
@@ -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
Normal file
182
store/sql_oauth_store_test.go
Normal file
@@ -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() {
|
||||
table := db.AddTableWithName(model.Session{}, "Sessions").SetKeys(false, "Id")
|
||||
table.ColMap("Id").SetMaxSize(26)
|
||||
table.ColMap("AltId").SetMaxSize(26)
|
||||
table.ColMap("Token").SetMaxSize(26)
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("TeamId").SetMaxSize(26)
|
||||
table.ColMap("DeviceId").SetMaxSize(128)
|
||||
@@ -34,7 +34,7 @@ func (me SqlSessionStore) UpgradeSchemaIfNeeded() {
|
||||
|
||||
func (me SqlSessionStore) CreateIndexesIfNotExists() {
|
||||
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 {
|
||||
@@ -70,19 +70,21 @@ func (me SqlSessionStore) Save(session *model.Session) StoreChannel {
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) Get(id string) StoreChannel {
|
||||
func (me SqlSessionStore) Get(sessionIdOrToken string) StoreChannel {
|
||||
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
if obj, err := me.GetReplica().Get(model.Session{}, id); err != nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.Get", "We encounted an error finding the session", "id="+id+", "+err.Error())
|
||||
} else if obj == nil {
|
||||
result.Err = model.NewAppError("SqlSessionStore.Get", "We couldn't find the existing session", "id="+id)
|
||||
var sessions []*model.Session
|
||||
|
||||
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 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 {
|
||||
result.Data = obj.(*model.Session)
|
||||
result.Data = sessions[0]
|
||||
}
|
||||
|
||||
storeChannel <- result
|
||||
@@ -120,15 +122,15 @@ func (me SqlSessionStore) GetSessions(userId string) StoreChannel {
|
||||
return storeChannel
|
||||
}
|
||||
|
||||
func (me SqlSessionStore) Remove(sessionIdOrAlt string) StoreChannel {
|
||||
func (me SqlSessionStore) Remove(sessionIdOrToken string) StoreChannel {
|
||||
storeChannel := make(StoreChannel)
|
||||
|
||||
go func() {
|
||||
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 {
|
||||
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
|
||||
@@ -181,7 +183,6 @@ func (me SqlSessionStore) UpdateRoles(userId, roles string) StoreChannel {
|
||||
|
||||
go func() {
|
||||
result := StoreResult{}
|
||||
|
||||
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)
|
||||
} else {
|
||||
|
||||
@@ -80,7 +80,7 @@ func TestSessionRemove(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSessionRemoveAlt(t *testing.T) {
|
||||
func TestSessionRemoveToken(t *testing.T) {
|
||||
Setup()
|
||||
|
||||
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 {
|
||||
t.Fatal("should have been removed")
|
||||
|
||||
@@ -38,6 +38,7 @@ type SqlStore struct {
|
||||
user UserStore
|
||||
audit AuditStore
|
||||
session SessionStore
|
||||
oauth OAuthStore
|
||||
}
|
||||
|
||||
func NewSqlStore() Store {
|
||||
@@ -55,28 +56,36 @@ func NewSqlStore() Store {
|
||||
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.channel = NewSqlChannelStore(sqlStore)
|
||||
sqlStore.post = NewSqlPostStore(sqlStore)
|
||||
sqlStore.user = NewSqlUserStore(sqlStore)
|
||||
sqlStore.audit = NewSqlAuditStore(sqlStore)
|
||||
sqlStore.session = NewSqlSessionStore(sqlStore)
|
||||
sqlStore.oauth = NewSqlOAuthStore(sqlStore)
|
||||
|
||||
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.channel.(*SqlChannelStore).UpgradeSchemaIfNeeded()
|
||||
sqlStore.post.(*SqlPostStore).UpgradeSchemaIfNeeded()
|
||||
sqlStore.user.(*SqlUserStore).UpgradeSchemaIfNeeded()
|
||||
sqlStore.audit.(*SqlAuditStore).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
|
||||
}
|
||||
@@ -363,6 +372,10 @@ func (ss SqlStore) Audit() AuditStore {
|
||||
return ss.audit
|
||||
}
|
||||
|
||||
func (ss SqlStore) OAuth() OAuthStore {
|
||||
return ss.oauth
|
||||
}
|
||||
|
||||
type mattermConverter struct{}
|
||||
|
||||
func (me mattermConverter) ToDb(val interface{}) (interface{}, error) {
|
||||
|
||||
@@ -34,6 +34,7 @@ type Store interface {
|
||||
User() UserStore
|
||||
Audit() AuditStore
|
||||
Session() SessionStore
|
||||
OAuth() OAuthStore
|
||||
Close()
|
||||
}
|
||||
|
||||
@@ -104,9 +105,9 @@ type UserStore interface {
|
||||
|
||||
type SessionStore interface {
|
||||
Save(session *model.Session) StoreChannel
|
||||
Get(id string) StoreChannel
|
||||
Get(sessionIdOrToken string) StoreChannel
|
||||
GetSessions(userId string) StoreChannel
|
||||
Remove(sessionIdOrAlt string) StoreChannel
|
||||
Remove(sessionIdOrToken string) StoreChannel
|
||||
UpdateLastActivityAt(sessionId string, time int64) StoreChannel
|
||||
UpdateRoles(userId string, roles string) StoreChannel
|
||||
}
|
||||
@@ -115,3 +116,17 @@ type AuditStore interface {
|
||||
Save(audit *model.Audit) 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 {
|
||||
SiteName string
|
||||
Mode string
|
||||
AllowTesting bool
|
||||
UseSSL bool
|
||||
Port string
|
||||
Version string
|
||||
InviteSalt string
|
||||
PublicLinkSalt string
|
||||
ResetSalt string
|
||||
AnalyticsUrl string
|
||||
UseLocalStorage bool
|
||||
StorageDirectory string
|
||||
AllowedLoginAttempts int
|
||||
DisableEmailSignUp bool
|
||||
SiteName string
|
||||
Mode string
|
||||
AllowTesting bool
|
||||
UseSSL bool
|
||||
Port string
|
||||
Version string
|
||||
InviteSalt string
|
||||
PublicLinkSalt string
|
||||
ResetSalt string
|
||||
AnalyticsUrl string
|
||||
UseLocalStorage bool
|
||||
StorageDirectory string
|
||||
AllowedLoginAttempts int
|
||||
DisableEmailSignUp bool
|
||||
EnableOAuthServiceProvider bool
|
||||
}
|
||||
|
||||
type SSOSetting struct {
|
||||
@@ -286,6 +287,7 @@ func getClientProperties(c *Config) map[string]string {
|
||||
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["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider)
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
72
web/react/components/authorize.jsx
Normal file
72
web/react/components/authorize.jsx
Normal file
@@ -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});
|
||||
$('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');
|
||||
}
|
||||
});
|
||||
|
||||
249
web/react/components/register_app_modal.jsx
Normal file
249
web/react/components/register_app_modal.jsx
Normal file
@@ -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 GeneralTab = require('./user_settings_general.jsx');
|
||||
var AppearanceTab = require('./user_settings_appearance.jsx');
|
||||
var DeveloperTab = require('./user_settings_developer.jsx');
|
||||
|
||||
export default class UserSettings extends React.Component {
|
||||
constructor(props) {
|
||||
@@ -76,6 +77,15 @@ export default class UserSettings extends React.Component {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
} else if (this.props.activeTab === 'developer') {
|
||||
return (
|
||||
<div>
|
||||
<DeveloperTab
|
||||
activeSection={this.props.activeSection}
|
||||
updateSection={this.props.updateSection}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div/>;
|
||||
|
||||
93
web/react/components/user_settings_developer.jsx
Normal file
93
web/react/components/user_settings_developer.jsx
Normal file
@@ -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() {
|
||||
$(this).closest('.modal-dialog').removeClass('display--content');
|
||||
});
|
||||
$('body').on('click', '.modal-header .close', function closeModal() {
|
||||
setTimeout(function finishClose() {
|
||||
$('body').on('click', '.modal-header .close', () => {
|
||||
setTimeout(() => {
|
||||
$('.modal-dialog.display--content').removeClass('display--content');
|
||||
}, 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: 'notifications', uiName: 'Notifications', icon: 'glyphicon glyphicon-exclamation-sign'});
|
||||
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 (
|
||||
<div
|
||||
@@ -54,13 +57,13 @@ export default class UserSettingsModal extends React.Component {
|
||||
data-dismiss='modal'
|
||||
aria-label='Close'
|
||||
>
|
||||
<span aria-hidden='true'>×</span>
|
||||
<span aria-hidden='true'>{'x'}</span>
|
||||
</button>
|
||||
<h4
|
||||
className='modal-title'
|
||||
ref='title'
|
||||
>
|
||||
Account Settings
|
||||
{'Account Settings'}
|
||||
</h4>
|
||||
</div>
|
||||
<div className='modal-body'>
|
||||
|
||||
21
web/react/pages/authorize.jsx
Normal file
21
web/react/pages/authorize.jsx
Normal file
@@ -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 RemovedFromChannelModal = require('../components/removed_from_channel_modal.jsx');
|
||||
var FileUploadOverlay = require('../components/file_upload_overlay.jsx');
|
||||
var RegisterAppModal = require('../components/register_app_modal.jsx');
|
||||
|
||||
var Constants = require('../utils/constants.jsx');
|
||||
var ActionTypes = Constants.ActionTypes;
|
||||
@@ -222,6 +223,11 @@ function setupChannelPage(props) {
|
||||
/>,
|
||||
document.getElementById('file_upload_overlay')
|
||||
);
|
||||
|
||||
React.render(
|
||||
<RegisterAppModal />,
|
||||
document.getElementById('register_app_modal')
|
||||
);
|
||||
}
|
||||
|
||||
global.window.setup_channel_page = setupChannelPage;
|
||||
|
||||
@@ -987,3 +987,36 @@ export function updateValetFeature(data, success, error) {
|
||||
|
||||
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
Normal file
26
web/templates/authorize.html
Normal file
@@ -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="activity_log_modal"></div>
|
||||
<div id="removed_from_channel_modal"></div>
|
||||
<div id="register_app_modal"></div>
|
||||
<script>
|
||||
window.setup_channel_page({{ .Props }});
|
||||
$('body').tooltip( {selector: '[data-toggle=tooltip]'} );
|
||||
|
||||
204
web/web.go
204
web/web.go
@@ -4,19 +4,18 @@
|
||||
package web
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
l4g "code.google.com/p/log4go"
|
||||
"fmt"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/mattermost/platform/api"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"github.com/mssola/user_agent"
|
||||
"gopkg.in/fsnotify.v1"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var Templates *template.Template
|
||||
@@ -50,6 +49,8 @@ func InitWeb() {
|
||||
mainrouter.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(staticDir))))
|
||||
|
||||
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_user_complete/", api.AppHandlerIndependent(signupUserComplete)).Methods("GET")
|
||||
@@ -63,7 +64,7 @@ func InitWeb() {
|
||||
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")
|
||||
@@ -648,3 +649,192 @@ func adminConsole(c *api.Context, w http.ResponseWriter, r *http.Request) {
|
||||
page := NewHtmlTemplatePage("admin_console", "Admin Console")
|
||||
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()))
|
||||
}
|
||||
|
||||
134
web/web_test.go
134
web/web_test.go
@@ -6,8 +6,11 @@ package web
|
||||
import (
|
||||
"github.com/mattermost/platform/api"
|
||||
"github.com/mattermost/platform/model"
|
||||
"github.com/mattermost/platform/store"
|
||||
"github.com/mattermost/platform/utils"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
@@ -23,7 +26,7 @@ func Setup() {
|
||||
api.InitApi()
|
||||
InitWeb()
|
||||
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) {
|
||||
// *IMPORTANT*
|
||||
// This should be the last function in any test file
|
||||
|
||||
Reference in New Issue
Block a user