summaryrefslogtreecommitdiffstats
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/channel.go2
-rw-r--r--api/command.go112
-rw-r--r--api/command_test.go37
-rw-r--r--api/context.go21
-rw-r--r--api/file.go75
-rw-r--r--api/file_test.go9
-rw-r--r--api/post.go38
-rw-r--r--api/post_test.go74
-rw-r--r--api/slackimport.go28
-rw-r--r--api/team.go122
-rw-r--r--api/templates/error.html1
-rw-r--r--api/templates/find_teams_body.html2
-rw-r--r--api/user.go22
-rw-r--r--api/web_conn.go5
-rw-r--r--api/web_hub.go8
-rw-r--r--api/web_team_hub.go9
16 files changed, 471 insertions, 94 deletions
diff --git a/api/channel.go b/api/channel.go
index 5f3282072..b40366719 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -391,6 +391,8 @@ func JoinChannel(c *Context, channelId string, role string) {
c.Err = model.NewAppError("joinChannel", "Failed to send join request", "")
return
}
+
+ UpdateChannelAccessCacheAndForget(c.Session.TeamId, c.Session.UserId, channel.Id)
} else {
c.Err = model.NewAppError("joinChannel", "You do not have the appropriate permissions", "")
c.Err.StatusCode = http.StatusForbidden
diff --git a/api/command.go b/api/command.go
index f051bd42e..749cbf790 100644
--- a/api/command.go
+++ b/api/command.go
@@ -4,15 +4,15 @@
package api
import (
+ "net/http"
+ "strconv"
+ "strings"
+ "time"
+
l4g "code.google.com/p/log4go"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
- "net/http"
- "reflect"
- "runtime"
- "strconv"
- "strings"
)
type commandHandler func(c *Context, command *model.Command) bool
@@ -24,6 +24,8 @@ var commands = []commandHandler{
echoCommand,
}
+var echoSem chan bool
+
func InitCommand(r *mux.Router) {
l4g.Debug("Initializing command api routes")
r.Handle("/command", ApiUserRequired(command)).Methods("POST")
@@ -41,7 +43,6 @@ func command(c *Context, w http.ResponseWriter, r *http.Request) {
}
checkCommand(c, command)
-
if c.Err != nil {
return
} else {
@@ -56,8 +57,6 @@ func checkCommand(c *Context, command *model.Command) bool {
return false
}
- tchan := Srv.Store.Team().Get(c.Session.TeamId)
-
if len(command.ChannelId) > 0 {
cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, command.ChannelId, c.Session.UserId)
@@ -66,24 +65,9 @@ func checkCommand(c *Context, command *model.Command) bool {
}
}
- allowValet := false
- if tResult := <-tchan; tResult.Err != nil {
- c.Err = model.NewAppError("checkCommand", "Could not find the team for this session, team_id="+c.Session.TeamId, "")
- return false
- } else {
- allowValet = tResult.Data.(*model.Team).AllowValet
- }
-
- ec := runtime.FuncForPC(reflect.ValueOf(echoCommand).Pointer()).Name()
-
for _, v := range commands {
- if !allowValet && ec == runtime.FuncForPC(reflect.ValueOf(v).Pointer()).Name() {
- continue
- }
- if v(c, command) {
- return true
- } else if c.Err != nil {
+ if v(c, command) || c.Err != nil {
return true
}
}
@@ -112,55 +96,65 @@ func logoutCommand(c *Context, command *model.Command) bool {
}
func echoCommand(c *Context, command *model.Command) bool {
-
cmd := "/echo"
+ maxThreads := 100
- if strings.Index(command.Command, cmd) == 0 {
- parts := strings.SplitN(command.Command, " ", 3)
-
- channelName := ""
- if len(parts) >= 2 {
- channelName = parts[1]
+ if !command.Suggest && strings.Index(command.Command, cmd) == 0 {
+ parameters := strings.SplitN(command.Command, " ", 2)
+ if len(parameters) != 2 || len(parameters[1]) == 0 {
+ return false
}
-
- message := ""
- if len(parts) >= 3 {
- message = parts[2]
+ message := strings.Trim(parameters[1], " ")
+ delay := 0
+ if endMsg := strings.LastIndex(message, "\""); string(message[0]) == "\"" && endMsg > 1 {
+ if checkDelay, err := strconv.Atoi(strings.Trim(message[endMsg:], " \"")); err == nil {
+ delay = checkDelay
+ }
+ message = message[1:endMsg]
+ } else if strings.Index(message, " ") > -1 {
+ delayIdx := strings.LastIndex(message, " ")
+ delayStr := strings.Trim(message[delayIdx:], " ")
+
+ if checkDelay, err := strconv.Atoi(delayStr); err == nil {
+ delay = checkDelay
+ message = message[:delayIdx]
+ }
}
- if result := <-Srv.Store.Channel().GetChannels(c.Session.TeamId, c.Session.UserId); result.Err != nil {
- c.Err = result.Err
+ if delay > 10000 {
+ c.Err = model.NewAppError("echoCommand", "Delays must be under 10000 seconds", "")
return false
- } else {
- channels := result.Data.(*model.ChannelList)
+ }
- for _, v := range channels.Channels {
- if v.Type == model.CHANNEL_DIRECT {
- continue
- }
+ if echoSem == nil {
+ // We want one additional thread allowed so we never reach channel lockup
+ echoSem = make(chan bool, maxThreads+1)
+ }
- if v.Name == channelName && !command.Suggest {
- post := &model.Post{}
- post.ChannelId = v.Id
- post.Message = message
+ if len(echoSem) >= maxThreads {
+ c.Err = model.NewAppError("echoCommand", "High volume of echo request, cannot process request", "")
+ return false
+ }
- if _, err := CreateValetPost(c, post); err != nil {
- c.Err = err
- return false
- }
+ echoSem <- true
+ go func() {
+ defer func() { <-echoSem }()
+ post := &model.Post{}
+ post.ChannelId = command.ChannelId
+ post.Message = message
- command.Response = model.RESP_EXECUTED
- return true
- }
+ time.Sleep(time.Duration(delay) * time.Second)
- if len(channelName) == 0 || (strings.Index(v.Name, channelName) == 0 && len(parts) < 3) {
- command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd + " " + v.Name, Description: "Echo a message using Valet in a channel"})
- }
+ if _, err := CreatePost(c, post, false); err != nil {
+ l4g.Error("Unable to create /echo post, err=%v", err)
}
- }
+ }()
+
+ command.Response = model.RESP_EXECUTED
+ return true
} else if strings.Index(cmd, command.Command) == 0 {
- command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Echo a message using Valet in a channel"})
+ command.AddSuggestion(&model.SuggestCommand{Suggestion: cmd, Description: "Echo back text from your account, /echo \"message\" [delay in seoncds]"})
}
return false
diff --git a/api/command_test.go b/api/command_test.go
index a58ef9be5..fe52dd41b 100644
--- a/api/command_test.go
+++ b/api/command_test.go
@@ -4,9 +4,10 @@
package api
import (
+ "testing"
+
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
- "testing"
)
func TestSuggestRootCommands(t *testing.T) {
@@ -50,6 +51,12 @@ func TestSuggestRootCommands(t *testing.T) {
if rs3.Suggestions[0].Suggestion != "/join" {
t.Fatal("should have join cmd")
}
+
+ rs4 := Client.Must(Client.Command("", "/ech", true)).Data.(*model.Command)
+
+ if rs4.Suggestions[0].Suggestion != "/echo" {
+ t.Fatal("should have echo cmd")
+ }
}
func TestLogoutCommands(t *testing.T) {
@@ -145,3 +152,31 @@ func TestJoinCommands(t *testing.T) {
t.Fatal("didn't join channel")
}
}
+
+func TestEchoCommand(t *testing.T) {
+ Setup()
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user1.Id))
+
+ Client.LoginByEmail(team.Name, user1.Email, "pwd")
+
+ channel1 := &model.Channel{DisplayName: "AA", Name: "aa" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+
+ echoTestString := "/echo test"
+
+ r1 := Client.Must(Client.Command(channel1.Id, echoTestString, false)).Data.(*model.Command)
+ if r1.Response != model.RESP_EXECUTED {
+ t.Fatal("Echo command failed to execute")
+ }
+
+ p1 := Client.Must(Client.GetPosts(channel1.Id, 0, 2, "")).Data.(*model.PostList)
+ if len(p1.Order) != 1 {
+ t.Fatal("Echo command failed to send")
+ }
+}
diff --git a/api/context.go b/api/context.go
index 8babf85f2..aaf304e2c 100644
--- a/api/context.go
+++ b/api/context.go
@@ -4,14 +4,15 @@
package api
import (
- l4g "code.google.com/p/log4go"
- "github.com/mattermost/platform/model"
- "github.com/mattermost/platform/store"
- "github.com/mattermost/platform/utils"
"net"
"net/http"
"net/url"
"strings"
+
+ l4g "code.google.com/p/log4go"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/store"
+ "github.com/mattermost/platform/utils"
)
var sessionCache *utils.Cache = utils.NewLru(model.SESSION_CACHE_SIZE)
@@ -431,10 +432,22 @@ func IsPrivateIpAddress(ipAddress string) bool {
}
func RenderWebError(err *model.AppError, w http.ResponseWriter, r *http.Request) {
+
+ protocol := "http"
+ if utils.Cfg.ServiceSettings.UseSSL {
+ forwardProto := r.Header.Get(model.HEADER_FORWARDED_PROTO)
+ if forwardProto != "http" {
+ protocol = "https"
+ }
+ }
+
+ SiteURL := protocol + "://" + r.Host
+
m := make(map[string]string)
m["Message"] = err.Message
m["Details"] = err.DetailedError
m["SiteName"] = utils.Cfg.ServiceSettings.SiteName
+ m["SiteURL"] = SiteURL
w.WriteHeader(err.StatusCode)
ServerTemplates.ExecuteTemplate(w, "error.html", m)
diff --git a/api/file.go b/api/file.go
index 558f9357e..800c512c5 100644
--- a/api/file.go
+++ b/api/file.go
@@ -30,12 +30,15 @@ import (
"time"
)
+var fileInfoCache *utils.Cache = utils.NewLru(1000)
+
func InitFile(r *mux.Router) {
l4g.Debug("Initializing file api routes")
sr := r.PathPrefix("/files").Subrouter()
sr.Handle("/upload", ApiUserRequired(uploadFile)).Methods("POST")
- sr.Handle("/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandler(getFile)).Methods("GET", "HEAD")
+ sr.Handle("/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandler(getFile)).Methods("GET")
+ sr.Handle("/get_info/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}", ApiAppHandler(getFileInfo)).Methods("GET")
sr.Handle("/get_public_link", ApiUserRequired(getPublicLink)).Methods("POST")
}
@@ -213,9 +216,72 @@ type ImageGetResult struct {
ImageData []byte
}
+func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
+ c.Err = model.NewAppError("getFileInfo", "Unable to get file info. Amazon S3 not configured and local server storage turned off. ", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ params := mux.Vars(r)
+
+ channelId := params["channel_id"]
+ if len(channelId) != 26 {
+ c.SetInvalidParam("getFileInfo", "channel_id")
+ return
+ }
+
+ userId := params["user_id"]
+ if len(userId) != 26 {
+ c.SetInvalidParam("getFileInfo", "user_id")
+ return
+ }
+
+ filename := params["filename"]
+ if len(filename) == 0 {
+ c.SetInvalidParam("getFileInfo", "filename")
+ return
+ }
+
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
+
+ path := "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
+ size := ""
+
+ if s, ok := fileInfoCache.Get(path); ok {
+ size = s.(string)
+ } else {
+
+ fileData := make(chan []byte)
+ asyncGetFile(path, fileData)
+
+ f := <-fileData
+
+ if f == nil {
+ c.Err = model.NewAppError("getFileInfo", "Could not find file.", "path="+path)
+ c.Err.StatusCode = http.StatusNotFound
+ return
+ }
+
+ size = strconv.Itoa(len(f))
+ fileInfoCache.Add(path, size)
+ }
+
+ if !c.HasPermissionsToChannel(cchan, "getFileInfo") {
+ return
+ }
+
+ w.Header().Set("Cache-Control", "max-age=2592000, public")
+
+ result := make(map[string]string)
+ result["filename"] = filename
+ result["size"] = size
+ w.Write([]byte(model.MapToJson(result)))
+}
+
func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
- c.Err = model.NewAppError("getFile", "Unable to upload file. Amazon S3 not configured and local server storage turned off. ", "")
+ c.Err = model.NewAppError("getFile", "Unable to get file. Amazon S3 not configured and local server storage turned off. ", "")
c.Err.StatusCode = http.StatusNotImplemented
return
}
@@ -282,10 +348,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=2592000, public")
w.Header().Set("Content-Length", strconv.Itoa(len(f)))
-
- if r.Method != "HEAD" {
- w.Write(f)
- }
+ w.Write(f)
}
func asyncGetFile(path string, fileData chan []byte) {
diff --git a/api/file_test.go b/api/file_test.go
index 566fd69d0..eb1fcf2ce 100644
--- a/api/file_test.go
+++ b/api/file_test.go
@@ -201,6 +201,15 @@ func TestGetFile(t *testing.T) {
t.Fatal(downErr)
}
+ if resp, downErr := Client.GetFileInfo(filenames[0]); downErr != nil {
+ t.Fatal(downErr)
+ } else {
+ m := resp.Data.(map[string]string)
+ if len(m["size"]) == 0 {
+ t.Fail()
+ }
+ }
+
team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team)
diff --git a/api/post.go b/api/post.go
index c013df87f..5363fdf79 100644
--- a/api/post.go
+++ b/api/post.go
@@ -28,6 +28,7 @@ func InitPost(r *mux.Router) {
sr.Handle("/valet_create", ApiUserRequired(createValetPost)).Methods("POST")
sr.Handle("/update", ApiUserRequired(updatePost)).Methods("POST")
sr.Handle("/posts/{offset:[0-9]+}/{limit:[0-9]+}", ApiUserRequiredActivity(getPosts, false)).Methods("GET")
+ sr.Handle("/posts/{time:[0-9]+}", ApiUserRequiredActivity(getPostsSince, false)).Methods("GET")
sr.Handle("/post/{post_id:[A-Za-z0-9]+}", ApiUserRequired(getPost)).Methods("GET")
sr.Handle("/post/{post_id:[A-Za-z0-9]+}/delete", ApiUserRequired(deletePost)).Methods("POST")
}
@@ -545,9 +546,7 @@ func updatePost(c *Context, w http.ResponseWriter, r *http.Request) {
rpost := result.Data.(*model.Post)
message := model.NewMessage(c.Session.TeamId, rpost.ChannelId, c.Session.UserId, model.ACTION_POST_EDITED)
- message.Add("post_id", rpost.Id)
- message.Add("channel_id", rpost.ChannelId)
- message.Add("message", rpost.Message)
+ message.Add("post", rpost.ToJson())
PublishAndForget(message)
@@ -603,6 +602,39 @@ func getPosts(c *Context, w http.ResponseWriter, r *http.Request) {
}
+func getPostsSince(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+
+ id := params["id"]
+ if len(id) != 26 {
+ c.SetInvalidParam("getPostsSince", "channelId")
+ return
+ }
+
+ time, err := strconv.ParseInt(params["time"], 10, 64)
+ if err != nil {
+ c.SetInvalidParam("getPostsSince", "time")
+ return
+ }
+
+ cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, id, c.Session.UserId)
+ pchan := Srv.Store.Post().GetPostsSince(id, time)
+
+ if !c.HasPermissionsToChannel(cchan, "getPostsSince") {
+ return
+ }
+
+ if result := <-pchan; result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ list := result.Data.(*model.PostList)
+
+ w.Write([]byte(list.ToJson()))
+ }
+
+}
+
func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
diff --git a/api/post_test.go b/api/post_test.go
index cbba83af6..85d92de3a 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -331,7 +331,7 @@ func TestGetPosts(t *testing.T) {
t.Fatal("wrong order")
}
- if len(r1.Posts) != 4 {
+ if len(r1.Posts) != 2 { // 3a1 and 3; 3a1's parent already there
t.Fatal("wrong size")
}
@@ -345,12 +345,82 @@ func TestGetPosts(t *testing.T) {
t.Fatal("wrong order")
}
- if len(r2.Posts) != 4 {
+ if len(r2.Posts) != 3 { // 2 and 1a1; + 1a1's parent
t.Log(r2.Posts)
t.Fatal("wrong size")
}
}
+func TestGetPostsSince(t *testing.T) {
+ Setup()
+
+ team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
+ team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team)
+
+ user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "corey@test.com", Nickname: "Corey Hulen", Password: "pwd"}
+ user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User)
+ store.Must(Srv.Store.User().VerifyEmail(user1.Id))
+
+ Client.LoginByEmail(team.Name, user1.Email, "pwd")
+
+ channel1 := &model.Channel{DisplayName: "TestGetPosts", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
+ channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
+
+ time.Sleep(10 * time.Millisecond)
+ post0 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post0 = Client.Must(Client.CreatePost(post0)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post1 = Client.Must(Client.CreatePost(post1)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post1a1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", RootId: post1.Id}
+ post1a1 = Client.Must(Client.CreatePost(post1a1)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post2 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post2 = Client.Must(Client.CreatePost(post2)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post3 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a"}
+ post3 = Client.Must(Client.CreatePost(post3)).Data.(*model.Post)
+
+ time.Sleep(10 * time.Millisecond)
+ post3a1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", RootId: post3.Id}
+ post3a1 = Client.Must(Client.CreatePost(post3a1)).Data.(*model.Post)
+
+ r1 := Client.Must(Client.GetPostsSince(channel1.Id, post1.CreateAt)).Data.(*model.PostList)
+
+ if r1.Order[0] != post3a1.Id {
+ t.Fatal("wrong order")
+ }
+
+ if r1.Order[1] != post3.Id {
+ t.Fatal("wrong order")
+ }
+
+ if len(r1.Posts) != 5 {
+ t.Fatal("wrong size")
+ }
+
+ now := model.GetMillis()
+ r2 := Client.Must(Client.GetPostsSince(channel1.Id, now)).Data.(*model.PostList)
+
+ if len(r2.Posts) != 0 {
+ t.Fatal("should have been empty")
+ }
+
+ post2.Message = "new message"
+ Client.Must(Client.UpdatePost(post2))
+
+ r3 := Client.Must(Client.GetPostsSince(channel1.Id, now)).Data.(*model.PostList)
+
+ if len(r3.Order) != 2 { // 2 because deleted post is returned as well
+ t.Fatal("missing post update")
+ }
+}
+
func TestSearchPosts(t *testing.T) {
Setup()
diff --git a/api/slackimport.go b/api/slackimport.go
index ca3fdf3d1..1d037a934 100644
--- a/api/slackimport.go
+++ b/api/slackimport.go
@@ -82,8 +82,8 @@ func SlackParsePosts(data io.Reader) []SlackPost {
func SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Buffer) map[string]*model.User {
// Log header
- log.WriteString("\n Users Created\n")
- log.WriteString("===============\n\n")
+ log.WriteString("\r\n Users Created\r\n")
+ log.WriteString("===============\r\n\r\n")
addedUsers := make(map[string]*model.User)
for _, sUser := range slackusers {
@@ -109,9 +109,9 @@ func SlackAddUsers(teamId string, slackusers []SlackUser, log *bytes.Buffer) map
if mUser := ImportUser(&newUser); mUser != nil {
addedUsers[sUser.Id] = mUser
- log.WriteString("Email, Password: " + newUser.Email + ", " + password + "\n")
+ log.WriteString("Email, Password: " + newUser.Email + ", " + password + "\r\n")
} else {
- log.WriteString("Unable to import user: " + sUser.Username)
+ log.WriteString("Unable to import user: " + sUser.Username + "\r\n")
}
}
@@ -163,8 +163,8 @@ func SlackAddPosts(channel *model.Channel, posts []SlackPost, users map[string]*
func SlackAddChannels(teamId string, slackchannels []SlackChannel, posts map[string][]SlackPost, users map[string]*model.User, log *bytes.Buffer) map[string]*model.Channel {
// Write Header
- log.WriteString("\n Channels Added \n")
- log.WriteString("=================\n\n")
+ log.WriteString("\r\n Channels Added \r\n")
+ log.WriteString("=================\r\n\r\n")
addedChannels := make(map[string]*model.Channel)
for _, sChannel := range slackchannels {
@@ -180,14 +180,14 @@ func SlackAddChannels(teamId string, slackchannels []SlackChannel, posts map[str
// Maybe it already exists?
if result := <-Srv.Store.Channel().GetByName(teamId, sChannel.Name); result.Err != nil {
l4g.Debug("Failed to import: %s", newChannel.DisplayName)
- log.WriteString("Failed to import: " + newChannel.DisplayName + "\n")
+ log.WriteString("Failed to import: " + newChannel.DisplayName + "\r\n")
continue
} else {
mChannel = result.Data.(*model.Channel)
- log.WriteString("Merged with existing channel: " + newChannel.DisplayName + "\n")
+ log.WriteString("Merged with existing channel: " + newChannel.DisplayName + "\r\n")
}
}
- log.WriteString(newChannel.DisplayName + "\n")
+ log.WriteString(newChannel.DisplayName + "\r\n")
addedChannels[sChannel.Id] = mChannel
SlackAddPosts(mChannel, posts[sChannel.Name], users)
}
@@ -202,7 +202,7 @@ func SlackImport(fileData multipart.File, fileSize int64, teamID string) (*model
}
// Create log file
- log := bytes.NewBufferString("Mattermost Slack Import Log\n")
+ log := bytes.NewBufferString("Mattermost Slack Import Log\r\n")
var channels []SlackChannel
var users []SlackUser
@@ -234,11 +234,11 @@ func SlackImport(fileData multipart.File, fileSize int64, teamID string) (*model
addedUsers := SlackAddUsers(teamID, users, log)
SlackAddChannels(teamID, channels, posts, addedUsers, log)
- log.WriteString("\n Notes \n")
- log.WriteString("=======\n\n")
+ log.WriteString("\r\n Notes \r\n")
+ log.WriteString("=======\r\n\r\n")
- log.WriteString("- Some posts may not have been imported because they where not supported by this importer.\n")
- log.WriteString("- Slack bot posts are currently not supported.\n")
+ log.WriteString("- Some posts may not have been imported because they where not supported by this importer.\r\n")
+ log.WriteString("- Slack bot posts are currently not supported.\r\n")
return nil, log
}
diff --git a/api/team.go b/api/team.go
index a331e9e34..e34b3a610 100644
--- a/api/team.go
+++ b/api/team.go
@@ -23,6 +23,7 @@ func InitTeam(r *mux.Router) {
sr := r.PathPrefix("/teams").Subrouter()
sr.Handle("/create", ApiAppHandler(createTeam)).Methods("POST")
sr.Handle("/create_from_signup", ApiAppHandler(createTeamFromSignup)).Methods("POST")
+ sr.Handle("/create_with_sso/{service:[A-Za-z]+}", ApiAppHandler(createTeamFromSSO)).Methods("POST")
sr.Handle("/signup", ApiAppHandler(signupTeam)).Methods("POST")
sr.Handle("/find_team_by_name", ApiAppHandler(findTeamByName)).Methods("POST")
sr.Handle("/find_teams", ApiAppHandler(findTeams)).Methods("POST")
@@ -35,6 +36,11 @@ func InitTeam(r *mux.Router) {
}
func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.AllowEmailSignUp {
+ c.Err = model.NewAppError("signupTeam", "Team sign-up with email is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
m := model.MapFromJson(r.Body)
email := strings.ToLower(strings.TrimSpace(m["email"]))
@@ -44,6 +50,10 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if !isTreamCreationAllowed(c, email) {
+ return
+ }
+
subjectPage := NewServerTemplatePage("signup_team_subject", c.GetSiteURL())
bodyPage := NewServerTemplatePage("signup_team_body", c.GetSiteURL())
bodyPage.Props["TourUrl"] = utils.Cfg.TeamSettings.TourLink
@@ -70,7 +80,70 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(m)))
}
+func createTeamFromSSO(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ service := params["service"]
+
+ if !utils.IsServiceAllowed(service) {
+ c.SetInvalidParam("createTeamFromSSO", "service")
+ return
+ }
+
+ team := model.TeamFromJson(r.Body)
+
+ if team == nil {
+ c.SetInvalidParam("createTeamFromSSO", "team")
+ return
+ }
+
+ team.PreSave()
+
+ team.Name = model.CleanTeamName(team.Name)
+
+ if err := team.IsValid(); err != nil {
+ c.Err = err
+ return
+ }
+
+ team.Id = ""
+
+ found := true
+ count := 0
+ for found {
+ if found = FindTeamByName(c, team.Name, "true"); c.Err != nil {
+ return
+ } else if found {
+ team.Name = team.Name + strconv.Itoa(count)
+ count += 1
+ }
+ }
+
+ team.AllowValet = utils.Cfg.TeamSettings.AllowValetDefault
+
+ if result := <-Srv.Store.Team().Save(team); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ rteam := result.Data.(*model.Team)
+
+ if _, err := CreateDefaultChannels(c, rteam.Id); err != nil {
+ c.Err = nil
+ return
+ }
+
+ data := map[string]string{"follow_link": c.GetSiteURL() + "/" + rteam.Name + "/signup/" + service}
+ w.Write([]byte(model.MapToJson(data)))
+
+ }
+
+}
+
func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.AllowEmailSignUp {
+ c.Err = model.NewAppError("createTeamFromSignup", "Team sign-up with email is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
teamSignup := model.TeamSignupFromJson(r.Body)
@@ -89,6 +162,11 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = err
return
}
+
+ if !isTreamCreationAllowed(c, teamSignup.Team.Email) {
+ return
+ }
+
teamSignup.Team.Id = ""
password := teamSignup.User.Password
@@ -161,6 +239,11 @@ func createTeamFromSignup(c *Context, w http.ResponseWriter, r *http.Request) {
}
func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.AllowEmailSignUp {
+ c.Err = model.NewAppError("createTeam", "Team sign-up with email is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
team := model.TeamFromJson(r.Body)
@@ -169,6 +252,10 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
+ if !isTreamCreationAllowed(c, team.Email) {
+ return
+ }
+
if utils.Cfg.ServiceSettings.Mode != utils.MODE_DEV {
c.Err = model.NewAppError("createTeam", "The mode does not allow network creation without a valid invite", "")
return
@@ -181,7 +268,7 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
rteam := result.Data.(*model.Team)
if _, err := CreateDefaultChannels(c, rteam.Id); err != nil {
- c.Err = nil
+ c.Err = err
return
}
@@ -196,6 +283,35 @@ func createTeam(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+func isTreamCreationAllowed(c *Context, email string) bool {
+
+ email = strings.ToLower(email)
+
+ if utils.Cfg.TeamSettings.DisableTeamCreation {
+ c.Err = model.NewAppError("isTreamCreationAllowed", "Team creation has been disabled. Please ask your systems administrator for details.", "")
+ return false
+ }
+
+ // commas and @ signs are optional
+ // can be in the form of "@corp.mattermost.com, mattermost.com mattermost.org" -> corp.mattermost.com mattermost.com mattermost.org
+ domains := strings.Fields(strings.TrimSpace(strings.ToLower(strings.Replace(strings.Replace(utils.Cfg.TeamSettings.RestrictCreationToDomains, "@", " ", -1), ",", " ", -1))))
+
+ matched := false
+ for _, d := range domains {
+ if strings.HasSuffix(email, "@"+d) {
+ matched = true
+ break
+ }
+ }
+
+ if len(utils.Cfg.TeamSettings.RestrictCreationToDomains) > 0 && !matched {
+ c.Err = model.NewAppError("isTreamCreationAllowed", "Email must be from a specific domain (e.g. @example.com). Please ask your systems administrator for details.", "")
+ return false
+ }
+
+ return true
+}
+
func findTeamByName(c *Context, w http.ResponseWriter, r *http.Request) {
m := model.MapFromJson(r.Body)
@@ -283,10 +399,10 @@ func emailTeams(c *Context, w http.ResponseWriter, r *http.Request) {
} else {
teams := result.Data.([]*model.Team)
- // the template expects Props to be a map with team names as the keys
+ // the template expects Props to be a map with team names as the keys and the team url as the value
props := make(map[string]string)
for _, team := range teams {
- props[team.Name] = team.Name
+ props[team.Name] = c.GetTeamURLFromTeam(team)
}
bodyPage.Props = props
diff --git a/api/templates/error.html b/api/templates/error.html
index f38bb81a1..3474c9e1e 100644
--- a/api/templates/error.html
+++ b/api/templates/error.html
@@ -14,6 +14,7 @@
<div class="error__icon"><i class="fa fa-exclamation-triangle"></i></div>
<h2>{{ .SiteName }} needs your help:</h2>
<p>{{.Message}}</p>
+ <a href="{{.SiteURL}}">Go back to team site</a>
</div>
</div>
</body>
diff --git a/api/templates/find_teams_body.html b/api/templates/find_teams_body.html
index bd151a819..64bff8126 100644
--- a/api/templates/find_teams_body.html
+++ b/api/templates/find_teams_body.html
@@ -21,7 +21,7 @@
<p>{{ if .Props }}
The following teams were found:<br>
{{range $index, $element := .Props}}
- {{ $index }}<br>
+ <a href="{{ $element }}" style="text-decoration: none; color:#2389D7;">{{ $index }}</a><br>
{{ end }}
{{ else }}
We could not find any teams for the given email.
diff --git a/api/user.go b/api/user.go
index 05ccd03e8..3796dde2a 100644
--- a/api/user.go
+++ b/api/user.go
@@ -58,6 +58,11 @@ func InitUser(r *mux.Router) {
}
func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !utils.Cfg.ServiceSettings.AllowEmailSignUp {
+ c.Err = model.NewAppError("signupTeam", "User sign-up with email is disabled.", "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
user := model.UserFromJson(r.Body)
@@ -181,7 +186,7 @@ func CreateUser(c *Context, team *model.Team, user *model.User) *model.User {
if result := <-Srv.Store.User().Save(user); result.Err != nil {
c.Err = result.Err
- l4g.Error("Filae err=%v", result.Err)
+ l4g.Error("Couldn't save the user err=%v", result.Err)
return nil
} else {
ruser := result.Data.(*model.User)
@@ -1426,3 +1431,18 @@ func AuthorizeOAuthUser(service, code, state, redirectUri string) (io.ReadCloser
}
}
+
+func IsUsernameTaken(name string, teamId string) bool {
+
+ if !model.IsValidUsername(name) {
+ return false
+ }
+
+ if result := <-Srv.Store.User().GetByUsername(teamId, name); result.Err != nil {
+ return false
+ } else {
+ return true
+ }
+
+ return false
+}
diff --git a/api/web_conn.go b/api/web_conn.go
index 0990de8ef..4315f5650 100644
--- a/api/web_conn.go
+++ b/api/web_conn.go
@@ -121,6 +121,11 @@ func (c *WebConn) writePump() {
}
}
+func (c *WebConn) updateChannelAccessCache(channelId string) {
+ allowed := hasPermissionsToChannel(Srv.Store.Channel().CheckPermissionsTo(c.TeamId, channelId, c.UserId))
+ c.ChannelAccessCache[channelId] = allowed
+}
+
func hasPermissionsToChannel(sc store.StoreChannel) bool {
if cresult := <-sc; cresult.Err != nil {
return false
diff --git a/api/web_hub.go b/api/web_hub.go
index c7be19cac..44d405283 100644
--- a/api/web_hub.go
+++ b/api/web_hub.go
@@ -30,6 +30,14 @@ func PublishAndForget(message *model.Message) {
}()
}
+func UpdateChannelAccessCacheAndForget(teamId, userId, channelId string) {
+ go func() {
+ if nh, ok := hub.teamHubs[teamId]; ok {
+ nh.UpdateChannelAccessCache(userId, channelId)
+ }
+ }()
+}
+
func (h *Hub) Register(webConn *WebConn) {
h.register <- webConn
}
diff --git a/api/web_team_hub.go b/api/web_team_hub.go
index 7a63b84d1..31c8dfedf 100644
--- a/api/web_team_hub.go
+++ b/api/web_team_hub.go
@@ -77,3 +77,12 @@ func (h *TeamHub) Start() {
}
}()
}
+
+func (h *TeamHub) UpdateChannelAccessCache(userId string, channelId string) {
+ for webCon := range h.connections {
+ if webCon.UserId == userId {
+ webCon.updateChannelAccessCache(channelId)
+ break
+ }
+ }
+}