summaryrefslogtreecommitdiffstats
path: root/api
diff options
context:
space:
mode:
Diffstat (limited to 'api')
-rw-r--r--api/admin.go8
-rw-r--r--api/apitestlib.go4
-rw-r--r--api/channel.go15
-rw-r--r--api/command.go64
-rw-r--r--api/command_test.go39
-rw-r--r--api/context.go16
-rw-r--r--api/emoji.go87
-rw-r--r--api/emoji_test.go59
-rw-r--r--api/file.go87
-rw-r--r--api/import.go14
-rw-r--r--api/post.go116
-rw-r--r--api/post_test.go57
-rw-r--r--api/server.go20
-rw-r--r--api/team.go12
-rw-r--r--api/user.go99
-rw-r--r--api/user_test.go114
-rw-r--r--api/web_conn.go30
-rw-r--r--api/web_hub.go5
-rw-r--r--api/websocket_router.go2
-rw-r--r--api/websocket_test.go2
20 files changed, 683 insertions, 167 deletions
diff --git a/api/admin.go b/api/admin.go
index 0edfb246b..16ec98fcf 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -176,6 +176,14 @@ func saveConfig(c *Context, w http.ResponseWriter, r *http.Request) {
utils.SaveConfig(utils.CfgFileName, cfg)
utils.LoadConfig(utils.CfgFileName)
+ if einterfaces.GetMetricsInterface() != nil {
+ if *utils.Cfg.MetricsSettings.Enable {
+ einterfaces.GetMetricsInterface().StartServer()
+ } else {
+ einterfaces.GetMetricsInterface().StopServer()
+ }
+ }
+
// Future feature is to sync the configuration files
// if einterfaces.GetClusterInterface() != nil {
// err := einterfaces.GetClusterInterface().ConfigChanged(cfg, oldCfg, true)
diff --git a/api/apitestlib.go b/api/apitestlib.go
index 37367b71d..9345d3fc4 100644
--- a/api/apitestlib.go
+++ b/api/apitestlib.go
@@ -36,7 +36,7 @@ func SetupEnterprise() *TestHelper {
*utils.Cfg.RateLimitSettings.Enable = false
utils.DisableDebugLogForTest()
utils.License.Features.SetDefaults()
- NewServer(false)
+ NewServer()
StartServer()
utils.InitHTML()
InitApi()
@@ -57,7 +57,7 @@ func Setup() *TestHelper {
utils.Cfg.TeamSettings.MaxUsersPerTeam = 50
*utils.Cfg.RateLimitSettings.Enable = false
utils.DisableDebugLogForTest()
- NewServer(false)
+ NewServer()
StartServer()
InitApi()
utils.EnableDebugLogForTest()
diff --git a/api/channel.go b/api/channel.go
index ea39ee398..9ec556fe6 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -580,7 +580,7 @@ func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelM
go func() {
InvalidateCacheForUser(user.Id)
- Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
+ InvalidateCacheForChannel(channel.Id)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_ADDED, "", channel.Id, "", nil)
message.Add("user_id", user.Id)
@@ -625,7 +625,7 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *m
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
}
- Srv.Store.User().InvalidateProfilesInChannelCache(result.Data.(*model.Channel).Id)
+ InvalidateCacheForChannel(result.Data.(*model.Channel).Id)
}
if result := <-Srv.Store.Channel().GetByName(teamId, "off-topic"); result.Err != nil {
@@ -649,7 +649,7 @@ func JoinDefaultChannels(teamId string, user *model.User, channelRole string) *m
l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
}
- Srv.Store.User().InvalidateProfilesInChannelCache(result.Data.(*model.Channel).Id)
+ InvalidateCacheForChannel(result.Data.(*model.Channel).Id)
}
return err
@@ -662,7 +662,7 @@ func leave(c *Context, w http.ResponseWriter, r *http.Request) {
sc := Srv.Store.Channel().Get(id)
uc := Srv.Store.User().Get(c.Session.UserId)
- ccm := Srv.Store.Channel().GetMemberCount(id)
+ ccm := Srv.Store.Channel().GetMemberCount(id, false)
if cresult := <-sc; cresult.Err != nil {
c.Err = cresult.Err
@@ -718,7 +718,7 @@ func deleteChannel(c *Context, w http.ResponseWriter, r *http.Request) {
sc := Srv.Store.Channel().Get(id)
scm := Srv.Store.Channel().GetMember(id, c.Session.UserId)
- cmc := Srv.Store.Channel().GetMemberCount(id)
+ cmc := Srv.Store.Channel().GetMemberCount(id, false)
uc := Srv.Store.User().Get(c.Session.UserId)
ihc := Srv.Store.Webhook().GetIncomingByChannel(id)
ohc := Srv.Store.Webhook().GetOutgoingByChannel(id)
@@ -949,7 +949,7 @@ func getChannelStats(c *Context, w http.ResponseWriter, r *http.Request) {
channel = result.Data.(*model.Channel)
}
- if result := <-Srv.Store.Channel().GetMemberCount(id); result.Err != nil {
+ if result := <-Srv.Store.Channel().GetMemberCount(id, true); result.Err != nil {
c.Err = result.Err
return
} else {
@@ -1107,7 +1107,6 @@ func removeMember(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(result)))
}
}
-
}
func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel *model.Channel) *model.AppError {
@@ -1120,7 +1119,7 @@ func RemoveUserFromChannel(userIdToRemove string, removerUserId string, channel
}
InvalidateCacheForUser(userIdToRemove)
- Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
+ InvalidateCacheForChannel(channel.Id)
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_REMOVED, "", channel.Id, "", nil)
message.Add("user_id", userIdToRemove)
diff --git a/api/command.go b/api/command.go
index e71661a67..3ae379819 100644
--- a/api/command.go
+++ b/api/command.go
@@ -45,6 +45,7 @@ func InitCommand() {
BaseRoutes.Commands.Handle("/list", ApiUserRequired(listCommands)).Methods("GET")
BaseRoutes.Commands.Handle("/create", ApiUserRequired(createCommand)).Methods("POST")
+ BaseRoutes.Commands.Handle("/update", ApiUserRequired(updateCommand)).Methods("POST")
BaseRoutes.Commands.Handle("/list_team_commands", ApiUserRequired(listTeamCommands)).Methods("GET")
BaseRoutes.Commands.Handle("/regen_token", ApiUserRequired(regenCommandToken)).Methods("POST")
BaseRoutes.Commands.Handle("/delete", ApiUserRequired(deleteCommand)).Methods("POST")
@@ -230,6 +231,8 @@ func handleResponse(c *Context, w http.ResponseWriter, response *model.CommandRe
if utils.Cfg.ServiceSettings.EnablePostUsernameOverride {
if len(cmd.Username) != 0 {
post.AddProp("override_username", cmd.Username)
+ } else if len(response.Username) != 0 {
+ post.AddProp("override_username", response.Username)
} else {
post.AddProp("override_username", model.DEFAULT_WEBHOOK_USERNAME)
}
@@ -238,6 +241,8 @@ func handleResponse(c *Context, w http.ResponseWriter, response *model.CommandRe
if utils.Cfg.ServiceSettings.EnablePostIconOverride {
if len(cmd.IconURL) != 0 {
post.AddProp("override_icon_url", cmd.IconURL)
+ } else if len(response.IconURL) != 0 {
+ post.AddProp("override_icon_url", response.IconURL)
} else {
post.AddProp("override_icon_url", "")
}
@@ -319,6 +324,65 @@ func createCommand(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+func updateCommand(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !*utils.Cfg.ServiceSettings.EnableCommands {
+ c.Err = model.NewLocAppError("updateCommand", "api.command.disabled.app_error", nil, "")
+ c.Err.StatusCode = http.StatusNotImplemented
+ return
+ }
+
+ if !HasPermissionToCurrentTeamContext(c, model.PERMISSION_MANAGE_SLASH_COMMANDS) {
+ c.Err = model.NewLocAppError("updateCommand", "api.command.admin_only.app_error", nil, "")
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+
+ c.LogAudit("attempt")
+
+ cmd := model.CommandFromJson(r.Body)
+
+ if cmd == nil {
+ c.SetInvalidParam("updateCommand", "command")
+ return
+ }
+
+ cmd.Trigger = strings.ToLower(cmd.Trigger)
+
+ var oldCmd *model.Command
+ if result := <-Srv.Store.Command().Get(cmd.Id); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ oldCmd = result.Data.(*model.Command)
+
+ if c.Session.UserId != oldCmd.CreatorId && !HasPermissionToCurrentTeamContext(c, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) {
+ c.LogAudit("fail - inappropriate permissions")
+ c.Err = model.NewLocAppError("updateCommand", "api.command.update.app_error", nil, "user_id="+c.Session.UserId)
+ return
+ }
+
+ if c.TeamId != oldCmd.TeamId {
+ c.Err = model.NewLocAppError("updateCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId)
+ return
+ }
+
+ cmd.Id = oldCmd.Id
+ cmd.Token = oldCmd.Token
+ cmd.CreateAt = oldCmd.CreateAt
+ cmd.UpdateAt = model.GetMillis()
+ cmd.DeleteAt = oldCmd.DeleteAt
+ cmd.CreatorId = oldCmd.CreatorId
+ cmd.TeamId = oldCmd.TeamId
+ }
+
+ if result := <-Srv.Store.Command().Update(cmd); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else {
+ w.Write([]byte(result.Data.(*model.Command).ToJson()))
+ }
+}
+
func listTeamCommands(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ServiceSettings.EnableCommands {
c.Err = model.NewLocAppError("listTeamCommands", "api.command.disabled.app_error", nil, "")
diff --git a/api/command_test.go b/api/command_test.go
index 7a78d350d..45268a9a5 100644
--- a/api/command_test.go
+++ b/api/command_test.go
@@ -120,6 +120,45 @@ func TestListTeamCommands(t *testing.T) {
}
}
+func TestUpdateCommand(t *testing.T) {
+ th := Setup().InitSystemAdmin()
+ Client := th.SystemAdminClient
+ user := th.SystemAdminUser
+ team := th.SystemAdminTeam
+
+ enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
+ defer func() {
+ utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
+ }()
+ *utils.Cfg.ServiceSettings.EnableCommands = true
+
+ cmd1 := &model.Command{
+ CreatorId: user.Id,
+ TeamId: team.Id,
+ URL: "http://nowhere.com",
+ Method: model.COMMAND_METHOD_POST,
+ Trigger: "trigger"}
+
+ cmd1 = Client.Must(Client.CreateCommand(cmd1)).Data.(*model.Command)
+
+ cmd2 := &model.Command{
+ CreatorId: user.Id,
+ TeamId: team.Id,
+ URL: "http://nowhere.com",
+ Method: model.COMMAND_METHOD_POST,
+ Trigger: "trigger2",
+ Token: cmd1.Token,
+ Id: cmd1.Id}
+
+ if result, err := Client.UpdateCommand(cmd2); err != nil {
+ t.Fatal(err)
+ } else {
+ if result.Data.(*model.Command).Trigger == cmd1.Trigger {
+ t.Fatal("update didn't work properly")
+ }
+ }
+}
+
func TestRegenToken(t *testing.T) {
th := Setup().InitSystemAdmin()
Client := th.SystemAdminClient
diff --git a/api/context.go b/api/context.go
index 7466d0b05..3a867624b 100644
--- a/api/context.go
+++ b/api/context.go
@@ -9,6 +9,7 @@ import (
"net/http"
"net/url"
"strings"
+ "time"
l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
@@ -103,6 +104,7 @@ type handler struct {
}
func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+ now := time.Now()
l4g.Debug("%v", r.URL.Path)
c := &Context{}
@@ -228,6 +230,10 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if h.isApi {
w.WriteHeader(c.Err.StatusCode)
w.Write([]byte(c.Err.ToJson()))
+
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementHttpError()
+ }
} else {
if c.Err.StatusCode == http.StatusUnauthorized {
http.Redirect(w, r, c.GetTeamURL()+"/?redirect="+url.QueryEscape(r.URL.Path), http.StatusTemporaryRedirect)
@@ -235,6 +241,16 @@ func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
RenderWebError(c.Err, w, r)
}
}
+
+ }
+
+ if h.isApi && einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementHttpRequest()
+
+ if r.URL.Path != model.API_URL_SUFFIX+"/users/websocket" {
+ elapsed := float64(time.Since(now)) / float64(time.Second)
+ einterfaces.GetMetricsInterface().ObserveHttpRequestDuration(elapsed)
+ }
}
}
diff --git a/api/emoji.go b/api/emoji.go
index 39f57a3c8..9108db2ad 100644
--- a/api/emoji.go
+++ b/api/emoji.go
@@ -6,23 +6,26 @@ package api
import (
"bytes"
"image"
- _ "image/gif"
+ "image/draw"
+ "image/gif"
_ "image/jpeg"
- _ "image/png"
+ "image/png"
"io"
"mime/multipart"
"net/http"
"strings"
l4g "github.com/alecthomas/log4go"
+ "github.com/disintegration/imaging"
"github.com/gorilla/mux"
"github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
+ "image/color/palette"
)
const (
- MaxEmojiFileSize = 64 * 1024 // 64 KB
+ MaxEmojiFileSize = 1000 * 1024 // 1 MB
MaxEmojiWidth = 128
MaxEmojiHeight = 128
)
@@ -147,11 +150,39 @@ func uploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppErro
if config, _, err := image.DecodeConfig(bytes.NewReader(buf.Bytes())); err != nil {
return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.image.app_error", nil, err.Error())
} else if config.Width > MaxEmojiWidth || config.Height > MaxEmojiHeight {
- return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.app_error", nil, "")
- }
-
- if err := WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil {
- return err
+ data := buf.Bytes()
+ newbuf := bytes.NewBuffer(nil)
+ if info, err := model.GetInfoForBytes(imageData.Filename, data); err != nil {
+ return err
+ } else if info.MimeType == "image/gif" {
+ if gif_data, err := gif.DecodeAll(bytes.NewReader(data)); err != nil {
+ return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_decode_error", nil, "")
+ } else {
+ resized_gif := resizeEmojiGif(gif_data)
+ if err := gif.EncodeAll(newbuf, resized_gif); err != nil {
+ return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_encode_error", nil, "")
+ }
+ if err := WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
+ return err
+ }
+ }
+ } else {
+ if img, _, err := image.Decode(bytes.NewReader(data)); err != nil {
+ return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.decode_error", nil, "")
+ } else {
+ resized_image := resizeEmoji(img, config.Width, config.Height)
+ if err := png.Encode(newbuf, resized_image); err != nil {
+ return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.encode_error", nil, "")
+ }
+ if err := WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
+ return err
+ }
+ }
+ }
+ } else {
+ if err := WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil {
+ return err
+ }
}
return nil
@@ -252,3 +283,43 @@ func getEmojiImage(c *Context, w http.ResponseWriter, r *http.Request) {
func getEmojiImagePath(id string) string {
return "emoji/" + id + "/image"
}
+
+func resizeEmoji(img image.Image, width int, height int) image.Image {
+ emojiWidth := float64(width)
+ emojiHeight := float64(height)
+
+ var emoji image.Image
+ if emojiHeight <= MaxEmojiHeight && emojiWidth <= MaxEmojiWidth {
+ emoji = img
+ } else {
+ emoji = imaging.Fit(img, MaxEmojiWidth, MaxEmojiHeight, imaging.Lanczos)
+ }
+ return emoji
+}
+
+func resizeEmojiGif(gifImg *gif.GIF) *gif.GIF {
+ // Create a new RGBA image to hold the incremental frames.
+ firstFrame := gifImg.Image[0].Bounds()
+ b := image.Rect(0, 0, firstFrame.Dx(), firstFrame.Dy())
+ img := image.NewRGBA(b)
+
+ resizedImage := image.Image(nil)
+ // Resize each frame.
+ for index, frame := range gifImg.Image {
+ bounds := frame.Bounds()
+ draw.Draw(img, bounds, frame, bounds.Min, draw.Over)
+ resizedImage = resizeEmoji(img, firstFrame.Dx(), firstFrame.Dy())
+ gifImg.Image[index] = imageToPaletted(resizedImage)
+ }
+ // Set new gif width and height
+ gifImg.Config.Width = resizedImage.Bounds().Dx()
+ gifImg.Config.Height = resizedImage.Bounds().Dy()
+ return gifImg
+}
+
+func imageToPaletted(img image.Image) *image.Paletted {
+ b := img.Bounds()
+ pm := image.NewPaletted(b, palette.Plan9)
+ draw.FloydSteinberg.Draw(pm, b, img, image.ZP)
+ return pm
+}
diff --git a/api/emoji_test.go b/api/emoji_test.go
index fb23cc439..efe4fd363 100644
--- a/api/emoji_test.go
+++ b/api/emoji_test.go
@@ -177,8 +177,8 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
- if _, err := Client.CreateEmoji(emoji, createTestGif(t, 1000, 10), "image.gif"); err == nil {
- t.Fatal("shouldn't be able to create an emoji that's too wide")
+ if _, err := Client.CreateEmoji(emoji, createTestGif(t, 1000, 10), "image.gif"); err != nil {
+ t.Fatal("should be able to create an emoji that's too wide by resizing it")
}
// try to create an emoji that's too tall
@@ -186,8 +186,8 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
- if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 1000), "image.gif"); err == nil {
- t.Fatal("shouldn't be able to create an emoji that's too tall")
+ if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 1000), "image.gif"); err != nil {
+ t.Fatal("should be able to create an emoji that's too tall by resizing it")
}
// try to create an emoji that's too large
@@ -195,7 +195,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
- if _, err := Client.CreateEmoji(emoji, createTestAnimatedGif(t, 100, 100, 4000), "image.gif"); err == nil {
+ if _, err := Client.CreateEmoji(emoji, createTestAnimatedGif(t, 100, 100, 10000), "image.gif"); err == nil {
t.Fatal("shouldn't be able to create an emoji that's too large")
}
@@ -424,3 +424,52 @@ func TestGetEmojiImage(t *testing.T) {
t.Fatal("should've failed to get image for deleted emoji")
}
}
+
+func TestResizeEmoji(t *testing.T) {
+ // try to resize a jpeg image within MaxEmojiWidth and MaxEmojiHeight
+ small_img_data := createTestJpeg(t, MaxEmojiWidth, MaxEmojiHeight)
+ if small_img, _, err := image.Decode(bytes.NewReader(small_img_data)); err != nil {
+ t.Fatal("failed to decode jpeg bytes to image.Image")
+ } else {
+ resized_img := resizeEmoji(small_img, small_img.Bounds().Dx(), small_img.Bounds().Dy())
+ if resized_img.Bounds().Dx() > MaxEmojiWidth || resized_img.Bounds().Dy() > MaxEmojiHeight {
+ t.Fatal("resized jpeg width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight")
+ }
+ if resized_img != small_img {
+ t.Fatal("should've returned small_img itself")
+ }
+ }
+ // try to resize a jpeg image
+ jpeg_data := createTestJpeg(t, 256, 256)
+ if jpeg_img, _, err := image.Decode(bytes.NewReader(jpeg_data)); err != nil {
+ t.Fatal("failed to decode jpeg bytes to image.Image")
+ } else {
+ resized_jpeg := resizeEmoji(jpeg_img, jpeg_img.Bounds().Dx(), jpeg_img.Bounds().Dy())
+ if resized_jpeg.Bounds().Dx() > MaxEmojiWidth || resized_jpeg.Bounds().Dy() > MaxEmojiHeight {
+ t.Fatal("resized jpeg width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight")
+ }
+ }
+ // try to resize a png image
+ png_data := createTestJpeg(t, 256, 256)
+ if png_img, _, err := image.Decode(bytes.NewReader(png_data)); err != nil {
+ t.Fatal("failed to decode png bytes to image.Image")
+ } else {
+ resized_png := resizeEmoji(png_img, png_img.Bounds().Dx(), png_img.Bounds().Dy())
+ if resized_png.Bounds().Dx() > MaxEmojiWidth || resized_png.Bounds().Dy() > MaxEmojiHeight {
+ t.Fatal("resized png width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight")
+ }
+ }
+ // try to resize an animated gif
+ gif_data := createTestAnimatedGif(t, 256, 256, 10)
+ if gif_img, err := gif.DecodeAll(bytes.NewReader(gif_data)); err != nil {
+ t.Fatal("failed to decode gif bytes to gif.GIF")
+ } else {
+ resized_gif := resizeEmojiGif(gif_img)
+ if resized_gif.Config.Width > MaxEmojiWidth || resized_gif.Config.Height > MaxEmojiHeight {
+ t.Fatal("resized gif width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight")
+ }
+ if len(resized_gif.Image) != len(gif_img.Image) {
+ t.Fatal("resized gif should have the same number of frames as original gif")
+ }
+ }
+}
diff --git a/api/file.go b/api/file.go
index 3fc61f469..8de69937a 100644
--- a/api/file.go
+++ b/api/file.go
@@ -171,7 +171,7 @@ func doUploadFile(teamId string, channelId string, userId string, rawFilename st
if info.IsImage() {
// Check dimensions before loading the whole thing into memory later on
if info.Width*info.Height > MaxImageSize {
- err := model.NewLocAppError("uploadFile", "api.file.upload_file.large_image.app_error", nil, "")
+ err := model.NewLocAppError("uploadFile", "api.file.upload_file.large_image.app_error", map[string]interface{}{"Filename": filename}, "")
err.StatusCode = http.StatusBadRequest
return nil, err
}
@@ -195,50 +195,57 @@ func doUploadFile(teamId string, channelId string, userId string, rawFilename st
func handleImages(previewPathList []string, thumbnailPathList []string, fileData [][]byte) {
for i, data := range fileData {
go func(i int, data []byte) {
- // Decode image bytes into Image object
- img, imgType, err := image.Decode(bytes.NewReader(fileData[i]))
- if err != nil {
- l4g.Error(utils.T("api.file.handle_images_forget.decode.error"), err)
- return
- }
-
- width := img.Bounds().Dx()
- height := img.Bounds().Dy()
-
- // Fill in the background of a potentially-transparent png file as white
- if imgType == "png" {
- dst := image.NewRGBA(img.Bounds())
- draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src)
- draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over)
- img = dst
- }
-
- // Flip the image to be upright
- orientation, _ := getImageOrientation(fileData[i])
-
- switch orientation {
- case UprightMirrored:
- img = imaging.FlipH(img)
- case UpsideDown:
- img = imaging.Rotate180(img)
- case UpsideDownMirrored:
- img = imaging.FlipV(img)
- case RotatedCWMirrored:
- img = imaging.Transpose(img)
- case RotatedCCW:
- img = imaging.Rotate270(img)
- case RotatedCCWMirrored:
- img = imaging.Transverse(img)
- case RotatedCW:
- img = imaging.Rotate90(img)
+ img, width, height := prepareImage(fileData[i])
+ if img != nil {
+ go generateThumbnailImage(*img, thumbnailPathList[i], width, height)
+ go generatePreviewImage(*img, previewPathList[i], width)
}
-
- go generateThumbnailImage(img, thumbnailPathList[i], width, height)
- go generatePreviewImage(img, previewPathList[i], width)
}(i, data)
}
}
+func prepareImage(fileData []byte) (*image.Image, int, int) {
+ // Decode image bytes into Image object
+ img, imgType, err := image.Decode(bytes.NewReader(fileData))
+ if err != nil {
+ l4g.Error(utils.T("api.file.handle_images_forget.decode.error"), err)
+ return nil, 0, 0
+ }
+
+ width := img.Bounds().Dx()
+ height := img.Bounds().Dy()
+
+ // Fill in the background of a potentially-transparent png file as white
+ if imgType == "png" {
+ dst := image.NewRGBA(img.Bounds())
+ draw.Draw(dst, dst.Bounds(), image.NewUniform(color.White), image.Point{}, draw.Src)
+ draw.Draw(dst, dst.Bounds(), img, img.Bounds().Min, draw.Over)
+ img = dst
+ }
+
+ // Flip the image to be upright
+ orientation, _ := getImageOrientation(fileData)
+
+ switch orientation {
+ case UprightMirrored:
+ img = imaging.FlipH(img)
+ case UpsideDown:
+ img = imaging.Rotate180(img)
+ case UpsideDownMirrored:
+ img = imaging.FlipV(img)
+ case RotatedCWMirrored:
+ img = imaging.Transpose(img)
+ case RotatedCCW:
+ img = imaging.Rotate270(img)
+ case RotatedCCWMirrored:
+ img = imaging.Transverse(img)
+ case RotatedCW:
+ img = imaging.Rotate90(img)
+ }
+
+ return &img, width, height
+}
+
func getImageOrientation(imageData []byte) (int, error) {
if exifData, err := exif.Decode(bytes.NewReader(imageData)); err != nil {
return Upright, err
diff --git a/api/import.go b/api/import.go
index 3ac6a9ce9..570444464 100644
--- a/api/import.go
+++ b/api/import.go
@@ -62,23 +62,17 @@ func ImportFile(file io.Reader, teamId string, channelId string, userId string,
io.Copy(buf, file)
data := buf.Bytes()
- previewPathList := []string{}
- thumbnailPathList := []string{}
- imageDataList := [][]byte{}
-
fileInfo, err := doUploadFile(teamId, channelId, userId, fileName, data)
if err != nil {
return nil, err
}
- if fileInfo.PreviewPath != "" || fileInfo.ThumbnailPath != "" {
- previewPathList = append(previewPathList, fileInfo.PreviewPath)
- thumbnailPathList = append(thumbnailPathList, fileInfo.ThumbnailPath)
- imageDataList = append(imageDataList, data)
+ img, width, height := prepareImage(data)
+ if img != nil {
+ generateThumbnailImage(*img, fileInfo.ThumbnailPath, width, height)
+ generatePreviewImage(*img, fileInfo.PreviewPath, width)
}
- go handleImages(previewPathList, thumbnailPathList, imageDataList)
-
return fileInfo, nil
}
diff --git a/api/post.go b/api/post.go
index 5d1a04e00..c646b056a 100644
--- a/api/post.go
+++ b/api/post.go
@@ -21,6 +21,7 @@ import (
l4g "github.com/alecthomas/log4go"
"github.com/gorilla/mux"
+ "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/store"
"github.com/mattermost/platform/utils"
@@ -132,7 +133,10 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post
}
}
- post.CreateAt = 0
+ if post.CreateAt != 0 && !HasPermissionToContext(c, model.PERMISSION_MANAGE_SYSTEM) {
+ post.CreateAt = 0
+ c.Err = nil
+ }
post.Hashtags, _ = model.ParseHashtags(post.Message)
@@ -143,6 +147,10 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post
rpost = result.Data.(*model.Post)
}
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementPostCreate()
+ }
+
if len(post.FileIds) > 0 {
// There's a rare bug where the client sends up duplicate FileIds so protect against that
post.FileIds = utils.RemoveDuplicatesFromStringArray(post.FileIds)
@@ -152,6 +160,10 @@ func CreatePost(c *Context, post *model.Post, triggerWebhooks bool) (*model.Post
l4g.Error(utils.T("api.post.create_post.attach_files.error"), post.Id, post.FileIds, c.Session.UserId, result.Err)
}
}
+
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementPostFileAttachment(len(post.FileIds))
+ }
}
handlePostEvents(c, rpost, triggerWebhooks)
@@ -463,7 +475,7 @@ func getMentionKeywordsInChannel(profiles map[string]*model.User) map[string][]s
}
// Add @channel and @all to keywords if user has them turned on
- if profile.NotifyProps["channel"] == "true" {
+ if int64(len(profiles)) < *utils.Cfg.TeamSettings.MaxNotificationsPerChannel && profile.NotifyProps["channel"] == "true" {
keywords["@channel"] = append(keywords["@channel"], profile.Id)
keywords["@all"] = append(keywords["@all"], profile.Id)
}
@@ -474,11 +486,13 @@ func getMentionKeywordsInChannel(profiles map[string]*model.User) map[string][]s
// Given a message and a map mapping mention keywords to the users who use them, returns a map of mentioned
// users and a slice of potencial mention users not in the channel and whether or not @here was mentioned.
-func getExplicitMentions(message string, keywords map[string][]string) (map[string]bool, []string, bool) {
+func getExplicitMentions(message string, keywords map[string][]string) (map[string]bool, []string, bool, bool, bool) {
mentioned := make(map[string]bool)
potentialOthersMentioned := make([]string, 0)
systemMentions := map[string]bool{"@here": true, "@channel": true, "@all": true}
hereMentioned := false
+ allMentioned := false
+ channelMentioned := false
addMentionedUsers := func(ids []string) {
for _, id := range ids {
@@ -493,6 +507,14 @@ func getExplicitMentions(message string, keywords map[string][]string) (map[stri
hereMentioned = true
}
+ if word == "@channel" {
+ channelMentioned = true
+ }
+
+ if word == "@all" {
+ allMentioned = true
+ }
+
// Non-case-sensitive check for regular keys
if ids, match := keywords[strings.ToLower(word)]; match {
addMentionedUsers(ids)
@@ -517,6 +539,14 @@ func getExplicitMentions(message string, keywords map[string][]string) (map[stri
hereMentioned = true
}
+ if splitWord == "@all" {
+ allMentioned = true
+ }
+
+ if splitWord == "@channel" {
+ channelMentioned = true
+ }
+
// Non-case-sensitive check for regular keys
if ids, match := keywords[strings.ToLower(splitWord)]; match {
addMentionedUsers(ids)
@@ -533,7 +563,7 @@ func getExplicitMentions(message string, keywords map[string][]string) (map[stri
}
}
- return mentioned, potentialOthersMentioned, hereMentioned
+ return mentioned, potentialOthersMentioned, hereMentioned, channelMentioned, allMentioned
}
func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *model.Channel) []string {
@@ -557,6 +587,8 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
mentionedUserIds := make(map[string]bool)
allActivityPushUserIds := []string{}
hereNotification := false
+ channelNotification := false
+ allNotification := false
updateMentionChans := []store.StoreChannel{}
if channel.Type == model.CHANNEL_DIRECT {
@@ -572,7 +604,7 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
keywords := getMentionKeywordsInChannel(profileMap)
var potentialOtherMentions []string
- mentionedUserIds, potentialOtherMentions, hereNotification = getExplicitMentions(post.Message, keywords)
+ mentionedUserIds, potentialOtherMentions, hereNotification, channelNotification, allNotification = getExplicitMentions(post.Message, keywords)
// get users that have comment thread mentions enabled
if len(post.RootId) > 0 {
@@ -640,7 +672,13 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
var status *model.Status
var err *model.AppError
if status, err = GetStatus(id); err != nil {
- status = &model.Status{id, model.STATUS_OFFLINE, false, 0, ""}
+ status = &model.Status{
+ UserId: id,
+ Status: model.STATUS_OFFLINE,
+ Manual: false,
+ LastActivityAt: 0,
+ ActiveChannel: "",
+ }
}
if userAllowsEmails && status.Status != model.STATUS_ONLINE {
@@ -649,6 +687,46 @@ func sendNotifications(c *Context, post *model.Post, team *model.Team, channel *
}
}
+ // If the channel has more than 1K users then @here is disabled
+ if hereNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
+ hereNotification = false
+ SendEphemeralPost(
+ c.TeamId,
+ post.UserId,
+ &model.Post{
+ ChannelId: post.ChannelId,
+ Message: utils.T("api.post.disabled_here", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}),
+ CreateAt: post.CreateAt + 1,
+ },
+ )
+ }
+
+ // If the channel has more than 1K users then @channel is disabled
+ if channelNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
+ SendEphemeralPost(
+ c.TeamId,
+ post.UserId,
+ &model.Post{
+ ChannelId: post.ChannelId,
+ Message: utils.T("api.post.disabled_channel", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}),
+ CreateAt: post.CreateAt + 1,
+ },
+ )
+ }
+
+ // If the channel has more than 1K users then @all is disabled
+ if allNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
+ SendEphemeralPost(
+ c.TeamId,
+ post.UserId,
+ &model.Post{
+ ChannelId: post.ChannelId,
+ Message: utils.T("api.post.disabled_all", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}),
+ CreateAt: post.CreateAt + 1,
+ },
+ )
+ }
+
if hereNotification {
if result := <-Srv.Store.Status().GetOnline(); result.Err != nil {
l4g.Warn(utils.T("api.post.notification.here.warn"), result.Err)
@@ -775,8 +853,11 @@ func sendNotificationEmail(c *Context, post *model.Post, user *model.User, chann
}
}
- if !found {
+ if !found && len(teams) > 0 {
team = teams[0]
+ } else {
+ // in case the user hasn't joined any teams we send them to the select_team page
+ team = &model.Team{Name: "select_team", DisplayName: utils.Cfg.TeamSettings.SiteName}
}
}
}
@@ -842,14 +923,17 @@ func sendNotificationEmail(c *Context, post *model.Post, user *model.User, chann
"ChannelName": channelName, "Month": month, "Day": day, "Year": year}
}
- subjectPage := utils.NewHTMLTemplate("post_subject", user.Locale)
- subjectPage.Props["Subject"] = userLocale(mailTemplate, mailParameters)
- subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
+ subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, userLocale(mailTemplate, mailParameters))
bodyPage := utils.NewHTMLTemplate("post_body", user.Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
bodyPage.Props["PostMessage"] = getMessageForNotification(post, userLocale)
- bodyPage.Props["TeamLink"] = teamURL + "/pl/" + post.Id
+ if team.Name != "select_team" {
+ bodyPage.Props["TeamLink"] = teamURL + "/pl/" + post.Id
+ } else {
+ bodyPage.Props["TeamLink"] = teamURL
+ }
+
bodyPage.Props["BodyText"] = bodyText
bodyPage.Props["Button"] = userLocale("api.templates.post_body.button")
bodyPage.Html["Info"] = template.HTML(userLocale("api.templates.post_body.info",
@@ -857,9 +941,13 @@ func sendNotificationEmail(c *Context, post *model.Post, user *model.User, chann
"Hour": fmt.Sprintf("%02d", tm.Hour()), "Minute": fmt.Sprintf("%02d", tm.Minute()),
"TimeZone": zone, "Month": month, "Day": day}))
- if err := utils.SendMail(user.Email, html.UnescapeString(subjectPage.Render()), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(user.Email, html.UnescapeString(subject), bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.post.send_notifications_and_forget.send.error"), user.Email, err)
}
+
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementPostSentEmail()
+ }
}
func getMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string {
@@ -922,6 +1010,7 @@ func sendPushNotification(post *model.Post, user *model.User, channel *model.Cha
msg.Badge = int(badge.Data.(int64))
}
msg.Type = model.PUSH_TYPE_MESSAGE
+ msg.TeamId = channel.TeamId
msg.ChannelId = channel.Id
msg.ChannelName = channel.Name
@@ -949,6 +1038,9 @@ func sendPushNotification(post *model.Post, user *model.User, channel *model.Cha
tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson()))
tmpMessage.SetDeviceIdAndPlatform(session.DeviceId)
sendToPushProxy(tmpMessage)
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementPostSentPush()
+ }
}
}
diff --git a/api/post_test.go b/api/post_test.go
index 0bafb5d20..0e340561c 100644
--- a/api/post_test.go
+++ b/api/post_test.go
@@ -138,6 +138,37 @@ func TestCreatePost(t *testing.T) {
}
}
+func TestCreatePostWithCreateAt(t *testing.T) {
+
+ // An ordinary user cannot use CreateAt
+
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+ channel1 := th.BasicChannel
+
+ post := &model.Post{
+ ChannelId: channel1.Id,
+ Message: "PLT-4349",
+ CreateAt: 1234,
+ }
+ if resp, err := Client.CreatePost(post); err != nil {
+ t.Fatal(err)
+ } else if rpost := resp.Data.(*model.Post); rpost.CreateAt == post.CreateAt {
+ t.Fatal("post should be created with default CreateAt timestamp for ordinary user")
+ }
+
+ // But a System Admin user can
+
+ th2 := Setup().InitSystemAdmin()
+ SysClient := th2.SystemAdminClient
+
+ if resp, err := SysClient.CreatePost(post); err != nil {
+ t.Fatal(err)
+ } else if rpost := resp.Data.(*model.Post); rpost.CreateAt != post.CreateAt {
+ t.Fatal("post should be created with provided CreateAt timestamp for System Admin user")
+ }
+}
+
func testCreatePostWithOutgoingHook(
t *testing.T,
hookContentType string,
@@ -1031,7 +1062,7 @@ func TestGetExplicitMentionsAtHere(t *testing.T) {
}
for message, shouldMention := range cases {
- if _, _, hereMentioned := getExplicitMentions(message, nil); hereMentioned && !shouldMention {
+ if _, _, hereMentioned, _, _ := getExplicitMentions(message, nil); hereMentioned && !shouldMention {
t.Fatalf("shouldn't have mentioned @here with \"%v\"", message)
} else if !hereMentioned && shouldMention {
t.Fatalf("should've have mentioned @here with \"%v\"", message)
@@ -1040,7 +1071,7 @@ func TestGetExplicitMentionsAtHere(t *testing.T) {
// mentioning @here and someone
id := model.NewId()
- if mentions, potential, hereMentioned := getExplicitMentions("@here @user @potential", map[string][]string{"@user": {id}}); !hereMentioned {
+ if mentions, potential, hereMentioned, _, _ := getExplicitMentions("@here @user @potential", map[string][]string{"@user": {id}}); !hereMentioned {
t.Fatal("should've mentioned @here with \"@here @user\"")
} else if len(mentions) != 1 || !mentions[id] {
t.Fatal("should've mentioned @user with \"@here @user\"")
@@ -1056,74 +1087,74 @@ func TestGetExplicitMentions(t *testing.T) {
// not mentioning anybody
message := "this is a message"
keywords := map[string][]string{}
- if mentions, potential, _ := getExplicitMentions(message, keywords); len(mentions) != 0 || len(potential) != 0 {
+ if mentions, potential, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 0 || len(potential) != 0 {
t.Fatal("shouldn't have mentioned anybody or have any potencial mentions")
}
// mentioning a user that doesn't exist
message = "this is a message for @user"
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 0 {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 0 {
t.Fatal("shouldn't have mentioned user that doesn't exist")
}
// mentioning one person
keywords = map[string][]string{"@user": {id1}}
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
t.Fatal("should've mentioned @user")
}
// mentioning one person without an @mention
message = "this is a message for @user"
keywords = map[string][]string{"this": {id1}}
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] {
t.Fatal("should've mentioned this")
}
// mentioning multiple people with one word
message = "this is a message for @user"
keywords = map[string][]string{"@user": {id1, id2}}
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
t.Fatal("should've mentioned two users with @user")
}
// mentioning only one of multiple people
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}}
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
t.Fatal("should've mentioned @user and not @mention")
}
// mentioning multiple people with multiple words
message = "this is an @mention for @user"
keywords = map[string][]string{"@user": {id1}, "@mention": {id2}}
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
t.Fatal("should've mentioned two users with @user and @mention")
}
// mentioning @channel (not a special case, but it's good to double check)
message = "this is an message for @channel"
keywords = map[string][]string{"@channel": {id1, id2}}
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
t.Fatal("should've mentioned two users with @channel")
}
// mentioning @all (not a special case, but it's good to double check)
message = "this is an message for @all"
keywords = map[string][]string{"@all": {id1, id2}}
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 2 || !mentions[id1] || !mentions[id2] {
t.Fatal("should've mentioned two users with @all")
}
// mentioning user.period without mentioning user (PLT-3222)
message = "user.period doesn't complicate things at all by including periods in their username"
keywords = map[string][]string{"user.period": {id1}, "user": {id2}}
- if mentions, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
+ if mentions, _, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || mentions[id2] {
t.Fatal("should've mentioned user.period and not user")
}
// mentioning a potential out of channel user
message = "this is an message for @potential and @user"
keywords = map[string][]string{"@user": {id1}}
- if mentions, potential, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || len(potential) != 1 {
+ if mentions, potential, _, _, _ := getExplicitMentions(message, keywords); len(mentions) != 1 || !mentions[id1] || len(potential) != 1 {
t.Fatal("should've mentioned user and have a potential not in channel")
}
}
diff --git a/api/server.go b/api/server.go
index fee74e373..f5a374507 100644
--- a/api/server.go
+++ b/api/server.go
@@ -7,7 +7,6 @@ import (
"crypto/tls"
"net"
"net/http"
- "net/http/pprof"
"strings"
"time"
@@ -37,20 +36,7 @@ const TIME_TO_WAIT_FOR_CONNECTIONS_TO_CLOSE_ON_SERVER_SHUTDOWN = time.Second
var Srv *Server
-func AttachProfiler(router *mux.Router) {
- router.HandleFunc("/debug/pprof/", pprof.Index)
- router.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
- router.HandleFunc("/debug/pprof/profile", pprof.Profile)
- router.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
-
- // Manually add support for paths linked to by index page at /debug/pprof/
- router.Handle("/debug/pprof/goroutine", pprof.Handler("goroutine"))
- router.Handle("/debug/pprof/heap", pprof.Handler("heap"))
- router.Handle("/debug/pprof/threadcreate", pprof.Handler("threadcreate"))
- router.Handle("/debug/pprof/block", pprof.Handler("block"))
-}
-
-func NewServer(enableProfiler bool) {
+func NewServer() {
l4g.Info(utils.T("api.server.new_server.init.info"))
@@ -58,10 +44,6 @@ func NewServer(enableProfiler bool) {
Srv.Store = store.NewSqlStore()
Srv.Router = mux.NewRouter()
- if enableProfiler {
- AttachProfiler(Srv.Router)
- l4g.Info("Enabled HTTP Profiler")
- }
Srv.Router.NotFoundHandler = http.HandlerFunc(Handle404)
}
diff --git a/api/team.go b/api/team.go
index b1a1ae3cd..8cfb4fe77 100644
--- a/api/team.go
+++ b/api/team.go
@@ -69,8 +69,7 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- subjectPage := utils.NewHTMLTemplate("signup_team_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.signup_team_subject",
+ subject := c.T("api.templates.signup_team_subject",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("signup_team_body", c.Locale)
@@ -89,7 +88,7 @@ func signupTeam(c *Context, w http.ResponseWriter, r *http.Request) {
bodyPage.Props["Link"] = fmt.Sprintf("%s/signup_team_complete/?d=%s&h=%s", c.GetSiteURL(), url.QueryEscape(data), url.QueryEscape(hash))
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
c.Err = err
return
}
@@ -340,7 +339,7 @@ func LeaveTeam(team *model.Team, user *model.User) *model.AppError {
for _, channel := range *channelList {
if channel.Type != model.CHANNEL_DIRECT {
- Srv.Store.User().InvalidateProfilesInChannelCache(channel.Id)
+ InvalidateCacheForChannel(channel.Id)
if result := <-Srv.Store.Channel().RemoveMember(channel.Id, user.Id); result.Err != nil {
return result.Err
}
@@ -717,8 +716,7 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
senderRole := c.T("api.team.invite_members.member")
- subjectPage := utils.NewHTMLTemplate("invite_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.invite_subject",
+ subject := c.T("api.templates.invite_subject",
map[string]interface{}{"SenderName": sender, "TeamDisplayName": team.DisplayName, "SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("invite_body", c.Locale)
@@ -744,7 +742,7 @@ func InviteMembers(c *Context, team *model.Team, user *model.User, invites []str
l4g.Info(utils.T("api.team.invite_members.sending.info"), invite, bodyPage.Props["Link"])
}
- if err := utils.SendMail(invite, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(invite, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.team.invite_members.send.error"), err)
}
}
diff --git a/api/user.go b/api/user.go
index 9c24609ce..3a303bee4 100644
--- a/api/user.go
+++ b/api/user.go
@@ -73,6 +73,7 @@ func InitUser() {
BaseRoutes.Users.Handle("/claim/ldap_to_email", ApiAppHandler(ldapToEmail)).Methods("POST")
BaseRoutes.NeedUser.Handle("/get", ApiUserRequired(getUser)).Methods("GET")
+ BaseRoutes.Users.Handle("/name/{username:[A-Za-z0-9_\\-.]+}", ApiUserRequired(getByUsername)).Methods("GET")
BaseRoutes.NeedUser.Handle("/sessions", ApiUserRequired(getSessions)).Methods("GET")
BaseRoutes.NeedUser.Handle("/audits", ApiUserRequired(getAudits)).Methods("GET")
BaseRoutes.NeedUser.Handle("/image", ApiUserRequiredTrustRequester(getProfileImage)).Methods("GET")
@@ -192,6 +193,7 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
}
+// Check that a user's email domain matches a list of space-delimited domains as a string.
func CheckUserDomain(user *model.User, domains string) bool {
if len(domains) == 0 {
return true
@@ -358,8 +360,7 @@ func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service
func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, verified bool) {
rawUrl, _ := url.Parse(siteURL)
- subjectPage := utils.NewHTMLTemplate("welcome_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.welcome_subject", map[string]interface{}{"ServerURL": rawUrl.Host})
+ subject := c.T("api.templates.welcome_subject", map[string]interface{}{"ServerURL": rawUrl.Host})
bodyPage := utils.NewHTMLTemplate("welcome_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
@@ -380,7 +381,7 @@ func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, v
bodyPage.Props["VerifyUrl"] = link
}
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_welcome_email_and_forget.failed.error"), err)
}
}
@@ -427,8 +428,7 @@ func SendVerifyEmail(c *Context, userId, userEmail, siteURL string) {
url, _ := url.Parse(siteURL)
- subjectPage := utils.NewHTMLTemplate("verify_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.verify_subject",
+ subject := c.T("api.templates.verify_subject",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("verify_body", c.Locale)
@@ -438,7 +438,7 @@ func SendVerifyEmail(c *Context, userId, userEmail, siteURL string) {
bodyPage.Props["VerifyUrl"] = link
bodyPage.Props["Button"] = c.T("api.templates.verify_body.button")
- if err := utils.SendMail(userEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(userEmail, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_verify_email_and_forget.failed.error"), err)
}
}
@@ -469,6 +469,9 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAuditWithUserId(user.Id, "failure")
c.Err = result.Err
c.Err.StatusCode = http.StatusBadRequest
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementLoginFail()
+ }
return
} else {
user = result.Data.(*model.User)
@@ -479,6 +482,9 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
if user, err = getUserForLogin(loginId, ldapOnly); err != nil {
c.LogAudit("failure")
c.Err = err
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementLoginFail()
+ }
return
}
@@ -489,10 +495,16 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
if user, err = authenticateUser(user, password, mfaToken); err != nil {
c.LogAuditWithUserId(user.Id, "failure")
c.Err = err
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementLoginFail()
+ }
return
}
c.LogAuditWithUserId(user.Id, "success")
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementLogin()
+ }
doLogin(c, w, r, user, deviceId)
if c.Err != nil {
@@ -751,6 +763,10 @@ func RevokeSessionById(c *Context, sessionId string) {
}
RevokeWebrtcToken(session.Id)
+
+ if einterfaces.GetClusterInterface() != nil {
+ einterfaces.GetClusterInterface().RemoveAllSessionsForUserId(session.UserId)
+ }
}
}
@@ -767,7 +783,6 @@ func RevokeAllSession(c *Context, userId string) {
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
@@ -777,6 +792,8 @@ func RevokeAllSession(c *Context, userId string) {
RevokeWebrtcToken(session.Id)
}
}
+
+ RemoveAllSessionsForUserId(userId)
}
// UGH...
@@ -791,7 +808,6 @@ func RevokeAllSessionsNoContext(userId string) *model.AppError {
if session.IsOAuth {
RevokeAccessToken(session.Token)
} else {
- sessionCache.Remove(session.Token)
if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
return result.Err
}
@@ -800,6 +816,9 @@ func RevokeAllSessionsNoContext(userId string) *model.AppError {
RevokeWebrtcToken(session.Id)
}
}
+
+ RemoveAllSessionsForUserId(userId)
+
return nil
}
@@ -948,6 +967,24 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+func getByUsername(c *Context, w http.ResponseWriter, r *http.Request) {
+ params := mux.Vars(r)
+ username := params["username"]
+
+ if result := <-Srv.Store.User().GetByUsername(username); result.Err != nil {
+ c.Err = result.Err
+ return
+ } else if HandleEtag(result.Data.(*model.User).Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), w, r) {
+ return
+ } else {
+ user := sanitizeProfile(c, result.Data.(*model.User))
+
+ w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress))
+ w.Write([]byte(result.Data.(*model.User).ToJson()))
+ return
+ }
+}
+
func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
@@ -1591,6 +1628,10 @@ func updateActive(c *Context, w http.ResponseWriter, r *http.Request) {
if ruser, err := UpdateActive(user, active); err != nil {
c.Err = err
} else {
+ if !active {
+ SetStatusOffline(ruser.Id, false)
+ }
+
c.LogAuditWithUserId(ruser.Id, fmt.Sprintf("active=%v", active))
w.Write([]byte(ruser.ToJson()))
}
@@ -1713,7 +1754,7 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
var user *model.User
if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
- c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.find.app_error", nil, "email="+email)
+ w.Write([]byte(model.MapToJson(props)))
return
} else {
user = result.Data.(*model.User)
@@ -1734,8 +1775,7 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
link := fmt.Sprintf("%s/reset_password_complete?code=%s", c.GetSiteURL(), url.QueryEscape(recovery.Code))
- subjectPage := utils.NewHTMLTemplate("reset_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.reset_subject")
+ subject := c.T("api.templates.reset_subject")
bodyPage := utils.NewHTMLTemplate("reset_body", c.Locale)
bodyPage.Props["SiteURL"] = c.GetSiteURL()
@@ -1744,7 +1784,7 @@ func sendPasswordReset(c *Context, w http.ResponseWriter, r *http.Request) {
bodyPage.Props["ResetUrl"] = link
bodyPage.Props["Button"] = c.T("api.templates.reset_body.button")
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
c.Err = model.NewLocAppError("sendPasswordReset", "api.user.send_password_reset.send.app_error", nil, "err="+err.Message)
return
}
@@ -1830,8 +1870,7 @@ func ResetPassword(c *Context, userId, newPassword string) *model.AppError {
}
func sendPasswordChangeEmail(c *Context, email, siteURL, method string) {
- subjectPage := utils.NewHTMLTemplate("password_change_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.password_change_subject",
+ subject := c.T("api.templates.password_change_subject",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "SiteName": utils.Cfg.TeamSettings.SiteName})
bodyPage := utils.NewHTMLTemplate("password_change_body", c.Locale)
@@ -1840,16 +1879,14 @@ func sendPasswordChangeEmail(c *Context, email, siteURL, method string) {
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.password_change_body.info",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "TeamURL": siteURL, "Method": method}))
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_password_change_email_and_forget.error"), err)
}
}
func sendEmailChangeEmail(c *Context, oldEmail, newEmail, siteURL string) {
- subjectPage := utils.NewHTMLTemplate("email_change_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.email_change_subject",
- map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
- subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
+ subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.email_change_subject",
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}))
bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
@@ -1857,7 +1894,7 @@ func sendEmailChangeEmail(c *Context, oldEmail, newEmail, siteURL string) {
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.email_change_body.info",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "NewEmail": newEmail}))
- if err := utils.SendMail(oldEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(oldEmail, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_email_change_email_and_forget.error"), err)
}
}
@@ -1865,10 +1902,8 @@ func sendEmailChangeEmail(c *Context, oldEmail, newEmail, siteURL string) {
func SendEmailChangeVerifyEmail(c *Context, userId, newUserEmail, siteURL string) {
link := fmt.Sprintf("%s/do_verify_email?uid=%s&hid=%s&email=%s", siteURL, userId, model.HashPassword(userId+utils.Cfg.EmailSettings.InviteSalt), url.QueryEscape(newUserEmail))
- subjectPage := utils.NewHTMLTemplate("email_change_verify_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.email_change_verify_subject",
- map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
- subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
+ subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.email_change_verify_subject",
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}))
bodyPage := utils.NewHTMLTemplate("email_change_verify_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
@@ -1878,16 +1913,14 @@ func SendEmailChangeVerifyEmail(c *Context, userId, newUserEmail, siteURL string
bodyPage.Props["VerifyUrl"] = link
bodyPage.Props["VerifyButton"] = c.T("api.templates.email_change_verify_body.button")
- if err := utils.SendMail(newUserEmail, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(newUserEmail, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_email_change_verify_email_and_forget.error"), err)
}
}
func sendEmailChangeUsername(c *Context, oldUsername, newUsername, email, siteURL string) {
- subjectPage := utils.NewHTMLTemplate("username_change_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.username_change_subject",
- map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName})
- subjectPage.Props["SiteName"] = utils.Cfg.TeamSettings.SiteName
+ subject := fmt.Sprintf("[%v] %v", utils.Cfg.TeamSettings.SiteName, c.T("api.templates.username_change_subject",
+ map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName}))
bodyPage := utils.NewHTMLTemplate("email_change_body", c.Locale)
bodyPage.Props["SiteURL"] = siteURL
@@ -1895,7 +1928,7 @@ func sendEmailChangeUsername(c *Context, oldUsername, newUsername, email, siteUR
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.username_change_body.info",
map[string]interface{}{"TeamDisplayName": utils.Cfg.TeamSettings.SiteName, "NewUsername": newUsername}))
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_email_change_username_and_forget.error"), err)
}
@@ -1967,6 +2000,7 @@ func updateUserNotify(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
+// Check if the username is already used by another user. Return false if the username is invalid.
func IsUsernameTaken(name string) bool {
if !model.IsValidUsername(name) {
@@ -2239,8 +2273,7 @@ func ldapToEmail(c *Context, w http.ResponseWriter, r *http.Request) {
}
func sendSignInChangeEmail(c *Context, email, siteURL, method string) {
- subjectPage := utils.NewHTMLTemplate("signin_change_subject", c.Locale)
- subjectPage.Props["Subject"] = c.T("api.templates.singin_change_email.subject",
+ subject := c.T("api.templates.singin_change_email.subject",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"]})
bodyPage := utils.NewHTMLTemplate("signin_change_body", c.Locale)
@@ -2249,7 +2282,7 @@ func sendSignInChangeEmail(c *Context, email, siteURL, method string) {
bodyPage.Html["Info"] = template.HTML(c.T("api.templates.singin_change_email.body.info",
map[string]interface{}{"SiteName": utils.ClientCfg["SiteName"], "Method": method}))
- if err := utils.SendMail(email, subjectPage.Render(), bodyPage.Render()); err != nil {
+ if err := utils.SendMail(email, subject, bodyPage.Render()); err != nil {
l4g.Error(utils.T("api.user.send_sign_in_change_email_and_forget.error"), err)
}
}
diff --git a/api/user_test.go b/api/user_test.go
index 1ffb2140c..a10cee961 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -80,6 +80,51 @@ func TestCreateUser(t *testing.T) {
}
}
+func TestCheckUserDomain(t *testing.T) {
+ th := Setup().InitBasic()
+ user := th.BasicUser
+
+ cases := []struct {
+ domains string
+ matched bool
+ }{
+ {"simulator.amazonses.com", true},
+ {"gmail.com", false},
+ {"", true},
+ {"gmail.com simulator.amazonses.com", true},
+ }
+ for _, c := range cases {
+ matched := CheckUserDomain(user, c.domains)
+ if matched != c.matched {
+ if c.matched {
+ t.Logf("'%v' should have matched '%v'", user.Email, c.domains)
+ } else {
+ t.Logf("'%v' should not have matched '%v'", user.Email, c.domains)
+ }
+ t.FailNow()
+ }
+ }
+}
+
+func TestIsUsernameTaken(t *testing.T) {
+ th := Setup().InitBasic()
+ user := th.BasicUser
+ taken := IsUsernameTaken(user.Username)
+
+ if !taken {
+ t.Logf("the username '%v' should be taken", user.Username)
+ t.FailNow()
+ }
+
+ newUsername := "randomUsername"
+ taken = IsUsernameTaken(newUsername)
+
+ if taken {
+ t.Logf("the username '%v' should not be taken", newUsername)
+ t.FailNow()
+ }
+}
+
func TestLogin(t *testing.T) {
th := Setup()
Client := th.CreateClient()
@@ -1088,8 +1133,9 @@ func TestUserUpdateDeviceId(t *testing.T) {
}
func TestUserUpdateActive(t *testing.T) {
- th := Setup()
+ th := Setup().InitSystemAdmin()
Client := th.CreateClient()
+ SystemAdminClient := th.SystemAdminClient
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)
@@ -1142,6 +1188,18 @@ func TestUserUpdateActive(t *testing.T) {
if _, err := Client.UpdateActive("12345678901234567890123456", false); err == nil {
t.Fatal("Should have errored, bad id")
}
+
+ SetStatusOnline(user3.Id, "", false)
+
+ if _, err := SystemAdminClient.UpdateActive(user3.Id, false); err != nil {
+ t.Fatal(err)
+ }
+
+ if status, err := GetStatus(user3.Id); err != nil {
+ t.Fatal(err)
+ } else if status.Status != model.STATUS_OFFLINE {
+ t.Fatal("status should have been set to offline")
+ }
}
func TestUserPermDelete(t *testing.T) {
@@ -1198,16 +1256,21 @@ func TestSendPasswordReset(t *testing.T) {
LinkUserToTeam(user, team)
store.Must(Srv.Store.User().VerifyEmail(user.Id))
- if _, err := Client.SendPasswordReset(user.Email); err != nil {
+ if result, err := Client.SendPasswordReset(user.Email); err != nil {
t.Fatal(err)
+ } else {
+ resp := result.Data.(map[string]string)
+ if resp["email"] != user.Email {
+ t.Fatal("wrong email")
+ }
}
- if _, err := Client.SendPasswordReset(""); err == nil {
- t.Fatal("Should have errored - no email")
+ if _, err := Client.SendPasswordReset("junk@junk.com"); err != nil {
+ t.Fatal("Should have errored - bad email")
}
- if _, err := Client.SendPasswordReset("junk@junk.com"); err == nil {
- t.Fatal("Should have errored - bad email")
+ if _, err := Client.SendPasswordReset(""); err == nil {
+ t.Fatal("Should have errored - no email")
}
authData := model.NewId()
@@ -2276,3 +2339,42 @@ func TestAutocompleteUsers(t *testing.T) {
t.Fatal("should have errored - bad team id")
}
}
+
+func TestGetByUsername(t *testing.T) {
+ th := Setup().InitBasic()
+ Client := th.BasicClient
+
+ if result, err := Client.GetByUsername(th.BasicUser.Username, ""); err != nil {
+ t.Fatal("Failed to get user")
+ } else {
+ if result.Data.(*model.User).Password != "" {
+ t.Fatal("User shouldn't have any password data once set")
+ }
+ }
+
+ emailPrivacy := utils.Cfg.PrivacySettings.ShowEmailAddress
+ namePrivacy := utils.Cfg.PrivacySettings.ShowFullName
+ defer func() {
+ utils.Cfg.PrivacySettings.ShowEmailAddress = emailPrivacy
+ utils.Cfg.PrivacySettings.ShowFullName = namePrivacy
+ }()
+
+ utils.Cfg.PrivacySettings.ShowEmailAddress = false
+ utils.Cfg.PrivacySettings.ShowFullName = false
+
+ if result, err := Client.GetByUsername(th.BasicUser2.Username, ""); err != nil {
+ t.Fatal(err)
+ } else {
+ u := result.Data.(*model.User)
+ if u.Password != "" {
+ t.Fatal("password must be empty")
+ }
+ if *u.AuthData != "" {
+ t.Fatal("auth data must be empty")
+ }
+ if u.Email != "" {
+ t.Fatal("email should be sanitized")
+ }
+ }
+
+}
diff --git a/api/web_conn.go b/api/web_conn.go
index c906b7c95..a2b801904 100644
--- a/api/web_conn.go
+++ b/api/web_conn.go
@@ -7,6 +7,7 @@ import (
"fmt"
"time"
+ "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
@@ -111,6 +112,12 @@ func (c *WebConn) writePump() {
return
}
+ if msg.EventType() == model.WEBSOCKET_EVENT_POSTED {
+ if einterfaces.GetMetricsInterface() != nil {
+ einterfaces.GetMetricsInterface().IncrementPostBroadcast()
+ }
+ }
+
case <-ticker.C:
c.WebSocket.SetWriteDeadline(time.Now().Add(WRITE_WAIT))
if err := c.WebSocket.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
@@ -140,7 +147,16 @@ func (webCon *WebConn) InvalidateCache() {
}
func (webCon *WebConn) isAuthenticated() bool {
- return webCon.SessionToken != ""
+ if webCon.SessionToken == "" {
+ return false
+ }
+
+ session := GetSession(webCon.SessionToken)
+ if session == nil || session.IsExpired() {
+ return false
+ }
+
+ return true
}
func (webCon *WebConn) SendHello() {
@@ -171,6 +187,18 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool {
// Only report events to users who are in the channel for the event
if len(msg.Broadcast.ChannelId) > 0 {
+ // Only broadcast typing messages if less than 1K people in channel
+ if msg.Event == model.WEBSOCKET_EVENT_TYPING {
+ if result := <-Srv.Store.Channel().GetMemberCount(msg.Broadcast.ChannelId, true); result.Err != nil {
+ l4g.Error("webhub.shouldSendEvent: " + result.Err.Error())
+ return false
+ } else {
+ if result.Data.(int64) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
+ return false
+ }
+ }
+ }
+
if model.GetMillis()-webCon.LastAllChannelMembersTime > 1000*60*15 { // 15 minutes
webCon.AllChannelMembers = nil
webCon.LastAllChannelMembersTime = 0
diff --git a/api/web_hub.go b/api/web_hub.go
index b607703f2..4136eaf7c 100644
--- a/api/web_hub.go
+++ b/api/web_hub.go
@@ -102,6 +102,11 @@ func PublishSkipClusterSend(message *model.WebSocketEvent) {
}
}
+func InvalidateCacheForChannel(channelId string) {
+ Srv.Store.User().InvalidateProfilesInChannelCache(channelId)
+ Srv.Store.Channel().InvalidateMemberCount(channelId)
+}
+
func InvalidateCacheForUser(userId string) {
InvalidateCacheForUserSkipClusterSend(userId)
diff --git a/api/websocket_router.go b/api/websocket_router.go
index 504e434b7..989d41373 100644
--- a/api/websocket_router.go
+++ b/api/websocket_router.go
@@ -63,7 +63,7 @@ func (wr *WebSocketRouter) ServeWebSocket(conn *WebConn, r *model.WebSocketReque
return
}
- if conn.SessionToken == "" {
+ if !conn.isAuthenticated() {
err := model.NewLocAppError("ServeWebSocket", "api.web_socket_router.not_authenticated.app_error", nil, "")
wr.ReturnWebSocketError(conn, r, err)
return
diff --git a/api/websocket_test.go b/api/websocket_test.go
index 144c1a39b..e04d1a3f0 100644
--- a/api/websocket_test.go
+++ b/api/websocket_test.go
@@ -45,8 +45,6 @@ func TestWebSocketAuthentication(t *testing.T) {
t.Fatal("should have closed")
}
- WebSocketClient.Close()
-
if conn, _, err := websocket.DefaultDialer.Dial(WebSocketClient.ApiUrl+"/users/websocket", nil); err != nil {
t.Fatal("should have connected")
} else {