summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/admin.go2
-rw-r--r--api/channel.go186
-rw-r--r--api/command_join.go2
-rw-r--r--api/command_logout.go4
-rw-r--r--api/command_msg.go2
-rw-r--r--api/context.go2
-rw-r--r--api/emoji.go10
-rw-r--r--api/emoji_test.go2
-rw-r--r--api/file.go346
-rw-r--r--api/file_test.go31
-rw-r--r--api/oauth.go34
-rw-r--r--api/post.go21
-rw-r--r--api/team.go5
-rw-r--r--api/user.go682
-rw-r--r--api/user_test.go26
-rw-r--r--api/webrtc.go19
-rw-r--r--api/webrtc_test.go3
-rw-r--r--app/audit.go16
-rw-r--r--app/channel.go187
-rw-r--r--app/file.go340
-rw-r--r--app/file_test.go33
-rw-r--r--app/notification.go394
-rw-r--r--app/oauth.go34
-rw-r--r--app/preference.go16
-rw-r--r--app/session.go52
-rw-r--r--app/team.go8
-rw-r--r--app/user.go421
-rw-r--r--app/user_test.go27
-rw-r--r--app/webhook.go40
-rw-r--r--app/webtrc.go33
-rw-r--r--cmd/platform/oldcommands.go2
-rw-r--r--cmd/platform/user.go2
-rw-r--r--i18n/en.json12
33 files changed, 1634 insertions, 1360 deletions
diff --git a/api/admin.go b/api/admin.go
index 2b5afc47a..300796bad 100644
--- a/api/admin.go
+++ b/api/admin.go
@@ -665,7 +665,7 @@ func adminResetMfa(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if err := DeactivateMfa(userId); err != nil {
+ if err := app.DeactivateMfa(userId); err != nil {
c.Err = err
return
}
diff --git a/api/channel.go b/api/channel.go
index f0d520b4e..590409921 100644
--- a/api/channel.go
+++ b/api/channel.go
@@ -119,7 +119,7 @@ func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if sc, err := CreateDirectChannel(c.Session.UserId, userId); err != nil {
+ if sc, err := app.CreateDirectChannel(c.Session.UserId, userId); err != nil {
c.Err = err
return
} else {
@@ -127,33 +127,6 @@ func createDirectChannel(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func CreateDirectChannel(userId string, otherUserId string) (*model.Channel, *model.AppError) {
- uc := app.Srv.Store.User().Get(otherUserId)
-
- if uresult := <-uc; uresult.Err != nil {
- return nil, model.NewLocAppError("CreateDirectChannel", "api.channel.create_direct_channel.invalid_user.app_error", nil, otherUserId)
- }
-
- if result := <-app.Srv.Store.Channel().CreateDirectChannel(userId, otherUserId); result.Err != nil {
- if result.Err.Id == store.CHANNEL_EXISTS_ERROR {
- return result.Data.(*model.Channel), nil
- } else {
- return nil, result.Err
- }
- } else {
- channel := result.Data.(*model.Channel)
-
- app.InvalidateCacheForUser(userId)
- app.InvalidateCacheForUser(otherUserId)
-
- message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_DIRECT_ADDED, "", channel.Id, "", nil)
- message.Add("teammate_id", otherUserId)
- go app.Publish(message)
-
- return channel, nil
- }
-}
-
func CanManageChannel(c *Context, channel *model.Channel) bool {
if channel.Type == model.CHANNEL_OPEN && !HasPermissionToChannelContext(c, channel.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_PROPERTIES) {
return false
@@ -229,7 +202,9 @@ func updateChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
} else {
if oldChannelDisplayName != channel.DisplayName {
- go PostUpdateChannelDisplayNameMessage(c, channel.Id, oldChannelDisplayName, channel.DisplayName)
+ if err := app.PostUpdateChannelDisplayNameMessage(c.Session.UserId, channel.Id, c.TeamId, oldChannelDisplayName, channel.DisplayName); err != nil {
+ l4g.Error(err.Error())
+ }
}
c.LogAudit("name=" + channel.Name)
w.Write([]byte(oldChannel.ToJson()))
@@ -277,76 +252,15 @@ func updateChannelHeader(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err = ucresult.Err
return
} else {
- go PostUpdateChannelHeaderMessage(c, channel.Id, oldChannelHeader, channelHeader)
+ if err := app.PostUpdateChannelHeaderMessage(c.Session.UserId, channel.Id, c.TeamId, oldChannelHeader, channelHeader); err != nil {
+ l4g.Error(err.Error())
+ }
c.LogAudit("name=" + channel.Name)
w.Write([]byte(channel.ToJson()))
}
}
}
-func PostUpdateChannelHeaderMessage(c *Context, channelId string, oldChannelHeader, newChannelHeader string) {
- uc := app.Srv.Store.User().Get(c.Session.UserId)
-
- if uresult := <-uc; uresult.Err != nil {
- l4g.Error(utils.T("api.channel.post_update_channel_header_message_and_forget.retrieve_user.error"), uresult.Err)
- return
- } else {
- user := uresult.Data.(*model.User)
-
- var message string
- if oldChannelHeader == "" {
- message = fmt.Sprintf(utils.T("api.channel.post_update_channel_header_message_and_forget.updated_to"), user.Username, newChannelHeader)
- } else if newChannelHeader == "" {
- message = fmt.Sprintf(utils.T("api.channel.post_update_channel_header_message_and_forget.removed"), user.Username, oldChannelHeader)
- } else {
- message = fmt.Sprintf(utils.T("api.channel.post_update_channel_header_message_and_forget.updated_from"), user.Username, oldChannelHeader, newChannelHeader)
- }
-
- post := &model.Post{
- ChannelId: channelId,
- Message: message,
- Type: model.POST_HEADER_CHANGE,
- UserId: c.Session.UserId,
- Props: model.StringInterface{
- "old_header": oldChannelHeader,
- "new_header": newChannelHeader,
- },
- }
-
- if _, err := app.CreatePost(post, c.TeamId, false); err != nil {
- l4g.Error(utils.T("api.channel.post_update_channel_header_message_and_forget.join_leave.error"), err)
- }
- }
-}
-
-func PostUpdateChannelDisplayNameMessage(c *Context, channelId string, oldChannelDisplayName, newChannelDisplayName string) {
- uc := app.Srv.Store.User().Get(c.Session.UserId)
-
- if uresult := <-uc; uresult.Err != nil {
- l4g.Error(utils.T("api.channel.post_update_channel_displayname_message_and_forget.retrieve_user.error"), uresult.Err)
- return
- } else {
- user := uresult.Data.(*model.User)
-
- message := fmt.Sprintf(utils.T("api.channel.post_update_channel_displayname_message_and_forget.updated_from"), user.Username, oldChannelDisplayName, newChannelDisplayName)
-
- post := &model.Post{
- ChannelId: channelId,
- Message: message,
- Type: model.POST_DISPLAYNAME_CHANGE,
- UserId: c.Session.UserId,
- Props: model.StringInterface{
- "old_displayname": oldChannelDisplayName,
- "new_displayname": newChannelDisplayName,
- },
- }
-
- if _, err := app.CreatePost(post, c.TeamId, false); err != nil {
- l4g.Error(utils.T("api.channel.post_update_channel_displayname_message_and_forget.create_post.error"), err)
- }
- }
-}
-
func updateChannelPurpose(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
channelId := props["channel_id"]
@@ -473,84 +387,34 @@ func join(c *Context, w http.ResponseWriter, r *http.Request) {
channelId := params["channel_id"]
channelName := params["channel_name"]
- var outChannel *model.Channel = nil
+ var channel *model.Channel
+ var err *model.AppError
if channelId != "" {
- if err, channel := JoinChannelById(c, c.Session.UserId, channelId); err != nil {
- c.Err = err
- c.Err.StatusCode = http.StatusForbidden
- return
- } else {
- outChannel = channel
- }
+ channel, err = app.GetChannel(channelId)
} else if channelName != "" {
- if err, channel := JoinChannelByName(c, c.Session.UserId, c.TeamId, channelName); err != nil {
- c.Err = err
- c.Err.StatusCode = http.StatusForbidden
- return
- } else {
- outChannel = channel
- }
+ channel, err = app.GetChannelByName(channelName, c.TeamId)
} else {
c.SetInvalidParam("join", "channel_id, channel_name")
return
}
- w.Write([]byte(outChannel.ToJson()))
-}
-
-func JoinChannelByName(c *Context, userId string, teamId string, channelName string) (*model.AppError, *model.Channel) {
- channelChannel := app.Srv.Store.Channel().GetByName(teamId, channelName)
- userChannel := app.Srv.Store.User().Get(userId)
-
- return joinChannel(c, channelChannel, userChannel)
-}
-
-func JoinChannelById(c *Context, userId string, channelId string) (*model.AppError, *model.Channel) {
- channelChannel := app.Srv.Store.Channel().Get(channelId, true)
- userChannel := app.Srv.Store.User().Get(userId)
-
- return joinChannel(c, channelChannel, userChannel)
-}
-
-func joinChannel(c *Context, channelChannel store.StoreChannel, userChannel store.StoreChannel) (*model.AppError, *model.Channel) {
- if cresult := <-channelChannel; cresult.Err != nil {
- return cresult.Err, nil
- } else if uresult := <-userChannel; uresult.Err != nil {
- return uresult.Err, nil
- } else {
- channel := cresult.Data.(*model.Channel)
- user := uresult.Data.(*model.User)
- if mresult := <-app.Srv.Store.Channel().GetMember(channel.Id, user.Id); mresult.Err == nil && mresult.Data != nil {
- // the user is already in the channel so just return successful
- return nil, channel
- }
+ if err != nil {
+ c.Err = err
+ return
+ }
+ if channel.Type == model.CHANNEL_OPEN {
if !HasPermissionToTeamContext(c, channel.TeamId, model.PERMISSION_JOIN_PUBLIC_CHANNELS) {
- return c.Err, nil
- }
-
- if channel.Type == model.CHANNEL_OPEN {
- if _, err := app.AddUserToChannel(user, channel); err != nil {
- return err, nil
- }
- go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.join_channel.post_and_forget"), user.Username), model.POST_JOIN_LEAVE)
- } else {
- return model.NewLocAppError("join", "api.channel.join_channel.permissions.app_error", nil, ""), nil
+ return
}
- return nil, channel
}
-}
-func PostUserAddRemoveMessage(c *Context, channelId string, message, postType string) {
- post := &model.Post{
- ChannelId: channelId,
- Message: message,
- Type: postType,
- UserId: c.Session.UserId,
- }
- if _, err := app.CreatePost(post, c.TeamId, false); err != nil {
- l4g.Error(utils.T("api.channel.post_user_add_remove_message_and_forget.error"), err)
+ if err = app.JoinChannel(channel, c.Session.UserId); err != nil {
+ c.Err = err
+ return
}
+
+ w.Write([]byte(channel.ToJson()))
}
func leave(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -601,7 +465,7 @@ func leave(c *Context, w http.ResponseWriter, r *http.Request) {
RemoveUserFromChannel(c.Session.UserId, c.Session.UserId, channel)
- go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.leave.left"), user.Username), model.POST_JOIN_LEAVE)
+ go app.PostUserAddRemoveMessage(c.Session.UserId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.leave.left"), user.Username), model.POST_JOIN_LEAVE)
result := make(map[string]string)
result["id"] = channel.Id
@@ -902,7 +766,7 @@ func addMember(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("name=" + channel.Name + " user_id=" + userId)
- go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.add_member.added"), nUser.Username, oUser.Username), model.POST_ADD_REMOVE)
+ go app.PostUserAddRemoveMessage(c.Session.UserId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.add_member.added"), nUser.Username, oUser.Username), model.POST_ADD_REMOVE)
<-app.Srv.Store.Channel().UpdateLastViewedAt([]string{id}, oUser.Id)
w.Write([]byte(cm.ToJson()))
@@ -956,7 +820,7 @@ func removeMember(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("name=" + channel.Name + " user_id=" + userIdToRemove)
- go PostUserAddRemoveMessage(c, channel.Id, fmt.Sprintf(utils.T("api.channel.remove_member.removed"), oUser.Username), model.POST_ADD_REMOVE)
+ go app.PostUserAddRemoveMessage(c.Session.UserId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.remove_member.removed"), oUser.Username), model.POST_ADD_REMOVE)
result := make(map[string]string)
result["channel_id"] = channel.Id
diff --git a/api/command_join.go b/api/command_join.go
index 2210d2857..bad176656 100644
--- a/api/command_join.go
+++ b/api/command_join.go
@@ -45,7 +45,7 @@ func (me *JoinProvider) DoCommand(c *Context, args *model.CommandArgs, message s
return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
- if err, _ := JoinChannelById(c, c.Session.UserId, channel.Id); err != nil {
+ if err := app.JoinChannel(channel, c.Session.UserId); err != nil {
return &model.CommandResponse{Text: c.T("api.command_join.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
diff --git a/api/command_logout.go b/api/command_logout.go
index 00375bb58..0eaa9a0ba 100644
--- a/api/command_logout.go
+++ b/api/command_logout.go
@@ -4,6 +4,7 @@
package api
import (
+ "github.com/mattermost/platform/app"
"github.com/mattermost/platform/model"
)
@@ -38,8 +39,7 @@ func (me *LogoutProvider) DoCommand(c *Context, args *model.CommandArgs, message
// We can't actually remove the user's cookie from here so we just dump their session and let the browser figure it out
if c.Session.Id != "" {
- RevokeSessionById(c, c.Session.Id)
- if c.Err != nil {
+ if err := app.RevokeSessionById(c.Session.Id); err != nil {
return FAIL
}
return SUCCESS
diff --git a/api/command_msg.go b/api/command_msg.go
index 36a9344ea..86203c2cd 100644
--- a/api/command_msg.go
+++ b/api/command_msg.go
@@ -66,7 +66,7 @@ func (me *msgProvider) DoCommand(c *Context, args *model.CommandArgs, message st
targetChannelId := ""
if channel := <-app.Srv.Store.Channel().GetByName(c.TeamId, channelName); channel.Err != nil {
if channel.Err.Id == "store.sql_channel.get_by_name.missing.app_error" {
- if directChannel, err := CreateDirectChannel(c.Session.UserId, userProfile.Id); err != nil {
+ if directChannel, err := app.CreateDirectChannel(c.Session.UserId, userProfile.Id); err != nil {
c.Err = err
return &model.CommandResponse{Text: c.T("api.command_msg.dm_fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
} else {
diff --git a/api/context.go b/api/context.go
index c2036ed81..349cbd5b6 100644
--- a/api/context.go
+++ b/api/context.go
@@ -469,7 +469,7 @@ func (c *Context) CheckTeamId() {
return
}
} else {
- // just return because it fail on the HasPermissionToContext and the error is already on the Context c.Err
+ // HasPermissionToContext automatically fills the Context error
return
}
}
diff --git a/api/emoji.go b/api/emoji.go
index fd78d5bd6..fb511cd03 100644
--- a/api/emoji.go
+++ b/api/emoji.go
@@ -163,7 +163,7 @@ func uploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppErro
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 {
+ if err := app.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}
@@ -175,13 +175,13 @@ func uploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppErro
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 {
+ if err := app.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}
}
} else {
- if err := WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil {
+ if err := app.WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}
@@ -236,7 +236,7 @@ func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
}
func deleteEmojiImage(id string) {
- if err := MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil {
+ if err := app.MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil {
l4g.Error("Failed to rename image when deleting emoji %v", id)
}
}
@@ -275,7 +275,7 @@ func getEmojiImage(c *Context, w http.ResponseWriter, r *http.Request) {
} else {
var img []byte
- if data, err := ReadFile(getEmojiImagePath(id)); err != nil {
+ if data, err := app.ReadFile(getEmojiImagePath(id)); err != nil {
c.Err = model.NewLocAppError("getEmojiImage", "api.emoji.get_image.read.app_error", nil, err.Error())
return
} else {
diff --git a/api/emoji_test.go b/api/emoji_test.go
index 243703193..73440ba15 100644
--- a/api/emoji_test.go
+++ b/api/emoji_test.go
@@ -317,7 +317,7 @@ func createTestPng(t *testing.T, width int, height int) []byte {
func createTestEmoji(t *testing.T, emoji *model.Emoji, imageData []byte) *model.Emoji {
emoji = store.Must(app.Srv.Store.Emoji().Save(emoji)).(*model.Emoji)
- if err := WriteFile(imageData, "emoji/"+emoji.Id+"/image"); err != nil {
+ if err := app.WriteFile(imageData, "emoji/"+emoji.Id+"/image"); err != nil {
store.Must(app.Srv.Store.Emoji().Delete(emoji.Id, time.Now().Unix()))
t.Fatalf("failed to write image: %v", err.Error())
}
diff --git a/api/file.go b/api/file.go
index 179c658cd..b0cedde40 100644
--- a/api/file.go
+++ b/api/file.go
@@ -5,24 +5,17 @@ package api
import (
"bytes"
- "crypto/sha256"
- "encoding/base64"
- "fmt"
"image"
"image/color"
"image/draw"
_ "image/gif"
"image/jpeg"
"io"
- "io/ioutil"
"net/http"
"net/url"
- "os"
- "path"
"path/filepath"
"strconv"
"strings"
- "sync"
l4g "github.com/alecthomas/log4go"
"github.com/disintegration/imaging"
@@ -32,8 +25,6 @@ import (
"github.com/mattermost/platform/utils"
"github.com/rwcarlsen/goexif/exif"
_ "golang.org/x/image/bmp"
-
- s3 "github.com/minio/minio-go"
)
const (
@@ -182,7 +173,7 @@ func doUploadFile(teamId string, channelId string, userId string, rawFilename st
info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
}
- if err := WriteFile(data, info.Path); err != nil {
+ if err := app.WriteFile(data, info.Path); err != nil {
return nil, err
}
@@ -285,7 +276,7 @@ func generateThumbnailImage(img image.Image, thumbnailPath string, width int, he
return
}
- if err := WriteFile(buf.Bytes(), thumbnailPath); err != nil {
+ if err := app.WriteFile(buf.Bytes(), thumbnailPath); err != nil {
l4g.Error(utils.T("api.file.handle_images_forget.upload_thumb.error"), thumbnailPath, err)
return
}
@@ -306,7 +297,7 @@ func generatePreviewImage(img image.Image, previewPath string, width int) {
return
}
- if err := WriteFile(buf.Bytes(), previewPath); err != nil {
+ if err := app.WriteFile(buf.Bytes(), previewPath); err != nil {
l4g.Error(utils.T("api.file.handle_images_forget.upload_preview.error"), previewPath, err)
return
}
@@ -319,7 +310,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if data, err := ReadFile(info.Path); err != nil {
+ if data, err := app.ReadFile(info.Path); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
@@ -341,7 +332,7 @@ func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if data, err := ReadFile(info.ThumbnailPath); err != nil {
+ if data, err := app.ReadFile(info.ThumbnailPath); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, "", data, w, r); err != nil {
@@ -363,7 +354,7 @@ func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if data, err := ReadFile(info.PreviewPath); err != nil {
+ if data, err := app.ReadFile(info.PreviewPath); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, "", data, w, r); err != nil {
@@ -400,7 +391,7 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) {
hash := r.URL.Query().Get("h")
if len(hash) > 0 {
- correctHash := generatePublicLinkHash(info.Id, *utils.Cfg.FileSettings.PublicLinkSalt)
+ correctHash := app.GeneratePublicLinkHash(info.Id, *utils.Cfg.FileSettings.PublicLinkSalt)
if hash != correctHash {
c.Err = model.NewLocAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "")
@@ -413,7 +404,7 @@ func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if data, err := ReadFile(info.Path); err != nil {
+ if data, err := app.ReadFile(info.Path); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
@@ -482,7 +473,7 @@ func getPublicFileOld(c *Context, w http.ResponseWriter, r *http.Request) {
hash := r.URL.Query().Get("h")
if len(hash) > 0 {
- correctHash := generatePublicLinkHash(filename, *utils.Cfg.FileSettings.PublicLinkSalt)
+ correctHash := app.GeneratePublicLinkHash(filename, *utils.Cfg.FileSettings.PublicLinkSalt)
if hash != correctHash {
c.Err = model.NewLocAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "")
@@ -511,7 +502,7 @@ func getPublicFileOld(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if data, err := ReadFile(info.Path); err != nil {
+ if data, err := app.ReadFile(info.Path); err != nil {
c.Err = err
c.Err.StatusCode = http.StatusNotFound
} else if err := writeFileResponse(info.Name, info.MimeType, data, w, r); err != nil {
@@ -560,320 +551,5 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- w.Write([]byte(model.StringToJson(generatePublicLink(c.GetSiteURL(), info))))
-}
-
-func generatePublicLink(siteURL string, info *model.FileInfo) string {
- hash := generatePublicLinkHash(info.Id, *utils.Cfg.FileSettings.PublicLinkSalt)
- return fmt.Sprintf("%s%s/public/files/%v/get?h=%s", siteURL, model.API_URL_SUFFIX, info.Id, hash)
-}
-
-func generatePublicLinkHash(fileId, salt string) string {
- hash := sha256.New()
- hash.Write([]byte(salt))
- hash.Write([]byte(fileId))
-
- return base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
-}
-
-var fileMigrationLock sync.Mutex
-
-// Creates and stores FileInfos for a post created before the FileInfos table existed.
-func migrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
- if len(post.Filenames) == 0 {
- l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.no_filenames.warn"), post.Id)
- return []*model.FileInfo{}
- }
-
- cchan := app.Srv.Store.Channel().Get(post.ChannelId, true)
-
- // There's a weird bug that rarely happens where a post ends up with duplicate Filenames so remove those
- filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames)
-
- var channel *model.Channel
- if result := <-cchan; result.Err != nil {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.channel.app_error"), post.Id, post.ChannelId, result.Err)
- return []*model.FileInfo{}
- } else {
- channel = result.Data.(*model.Channel)
- }
-
- // Find the team that was used to make this post since its part of the file path that isn't saved in the Filename
- var teamId string
- if channel.TeamId == "" {
- // This post was made in a cross-team DM channel so we need to find where its files were saved
- teamId = findTeamIdForFilename(post, filenames[0])
- } else {
- teamId = channel.TeamId
- }
-
- // Create FileInfo objects for this post
- infos := make([]*model.FileInfo, 0, len(filenames))
- if teamId == "" {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.team_id.error"), post.Id, filenames)
- } else {
- for _, filename := range filenames {
- info := getInfoForFilename(post, teamId, filename)
- if info == nil {
- continue
- }
-
- infos = append(infos, info)
- }
- }
-
- // Lock to prevent only one migration thread from trying to update the post at once, preventing duplicate FileInfos from being created
- fileMigrationLock.Lock()
- defer fileMigrationLock.Unlock()
-
- if result := <-app.Srv.Store.Post().Get(post.Id); result.Err != nil {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_again.app_error"), post.Id, result.Err)
- return []*model.FileInfo{}
- } else if newPost := result.Data.(*model.PostList).Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) {
- // Another thread has already created FileInfos for this post, so just return those
- if result := <-app.Srv.Store.FileInfo().GetForPost(post.Id); result.Err != nil {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_file_infos_again.app_error"), post.Id, result.Err)
- return []*model.FileInfo{}
- } else {
- l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.not_migrating_post.debug"), post.Id)
- return result.Data.([]*model.FileInfo)
- }
- }
-
- l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.migrating_post.debug"), post.Id)
-
- savedInfos := make([]*model.FileInfo, 0, len(infos))
- fileIds := make([]string, 0, len(filenames))
- for _, info := range infos {
- if result := <-app.Srv.Store.FileInfo().Save(info); result.Err != nil {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_file_info.app_error"), post.Id, info.Id, info.Path, result.Err)
- continue
- }
-
- savedInfos = append(savedInfos, info)
- fileIds = append(fileIds, info.Id)
- }
-
- // Copy and save the updated post
- newPost := &model.Post{}
- *newPost = *post
-
- newPost.Filenames = []string{}
- newPost.FileIds = fileIds
-
- // Update Posts to clear Filenames and set FileIds
- if result := <-app.Srv.Store.Post().Update(newPost, post); result.Err != nil {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_post.app_error"), post.Id, newPost.FileIds, post.Filenames, result.Err)
- return []*model.FileInfo{}
- } else {
- return savedInfos
- }
-}
-
-func findTeamIdForFilename(post *model.Post, filename string) string {
- split := strings.SplitN(filename, "/", 5)
- id := split[3]
- name, _ := url.QueryUnescape(split[4])
-
- // This post is in a direct channel so we need to figure out what team the files are stored under.
- if result := <-app.Srv.Store.Team().GetTeamsByUserId(post.UserId); result.Err != nil {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.teams.app_error"), post.Id, result.Err)
- } else if teams := result.Data.([]*model.Team); len(teams) == 1 {
- // The user has only one team so the post must've been sent from it
- return teams[0].Id
- } else {
- for _, team := range teams {
- path := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", team.Id, post.ChannelId, post.UserId, id, name)
- if _, err := ReadFile(path); err == nil {
- // Found the team that this file was posted from
- return team.Id
- }
- }
- }
-
- return ""
-}
-
-func getInfoForFilename(post *model.Post, teamId string, filename string) *model.FileInfo {
- // Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension}
- split := strings.SplitN(filename, "/", 5)
- if len(split) < 5 {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.unexpected_filename.error"), post.Id, filename)
- return nil
- }
-
- channelId := split[1]
- userId := split[2]
- oldId := split[3]
- name, _ := url.QueryUnescape(split[4])
-
- if split[0] != "" || split[1] != post.ChannelId || split[2] != post.UserId || strings.Contains(split[4], "/") {
- l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.mismatched_filename.warn"), post.Id, post.ChannelId, post.UserId, filename)
- }
-
- pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamId, channelId, userId, oldId)
- path := pathPrefix + name
-
- // Open the file and populate the fields of the FileInfo
- var info *model.FileInfo
- if data, err := ReadFile(path); err != nil {
- l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.file_not_found.error"), post.Id, filename, path, err)
- return nil
- } else {
- var err *model.AppError
- info, err = model.GetInfoForBytes(name, data)
- if err != nil {
- l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.info.app_error"), post.Id, filename, err)
- }
- }
-
- // Generate a new ID because with the old system, you could very rarely get multiple posts referencing the same file
- info.Id = model.NewId()
- info.CreatorId = post.UserId
- info.PostId = post.Id
- info.CreateAt = post.CreateAt
- info.UpdateAt = post.UpdateAt
- info.Path = path
-
- if info.IsImage() {
- nameWithoutExtension := name[:strings.LastIndex(name, ".")]
- info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg"
- info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
- }
-
- return info
-}
-
-func WriteFile(f []byte, path string) *model.AppError {
- if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
- endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint
- accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId
- secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey
- secure := *utils.Cfg.FileSettings.AmazonS3SSL
- s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure)
- if err != nil {
- return model.NewLocAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error())
- }
- bucket := utils.Cfg.FileSettings.AmazonS3Bucket
- ext := filepath.Ext(path)
-
- if model.IsFileExtImage(ext) {
- _, err = s3Clnt.PutObject(bucket, path, bytes.NewReader(f), model.GetImageMimeType(ext))
- } else {
- _, err = s3Clnt.PutObject(bucket, path, bytes.NewReader(f), "binary/octet-stream")
- }
- if err != nil {
- return model.NewLocAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error())
- }
- } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
- if err := writeFileLocally(f, utils.Cfg.FileSettings.Directory+path); err != nil {
- return err
- }
- } else {
- return model.NewLocAppError("WriteFile", "api.file.write_file.configured.app_error", nil, "")
- }
-
- return nil
-}
-
-func MoveFile(oldPath, newPath string) *model.AppError {
- if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
- endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint
- accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId
- secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey
- secure := *utils.Cfg.FileSettings.AmazonS3SSL
- s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure)
- if err != nil {
- return model.NewLocAppError("moveFile", "api.file.write_file.s3.app_error", nil, err.Error())
- }
- bucket := utils.Cfg.FileSettings.AmazonS3Bucket
-
- var copyConds = s3.NewCopyConditions()
- if err = s3Clnt.CopyObject(bucket, newPath, "/"+path.Join(bucket, oldPath), copyConds); err != nil {
- return model.NewLocAppError("moveFile", "api.file.move_file.delete_from_s3.app_error", nil, err.Error())
- }
- if err = s3Clnt.RemoveObject(bucket, oldPath); err != nil {
- return model.NewLocAppError("moveFile", "api.file.move_file.delete_from_s3.app_error", nil, err.Error())
- }
- } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
- if err := os.MkdirAll(filepath.Dir(utils.Cfg.FileSettings.Directory+newPath), 0774); err != nil {
- return model.NewLocAppError("moveFile", "api.file.move_file.rename.app_error", nil, err.Error())
- }
-
- if err := os.Rename(utils.Cfg.FileSettings.Directory+oldPath, utils.Cfg.FileSettings.Directory+newPath); err != nil {
- return model.NewLocAppError("moveFile", "api.file.move_file.rename.app_error", nil, err.Error())
- }
- } else {
- return model.NewLocAppError("moveFile", "api.file.move_file.configured.app_error", nil, "")
- }
-
- return nil
-}
-
-func writeFileLocally(f []byte, path string) *model.AppError {
- if err := os.MkdirAll(filepath.Dir(path), 0774); err != nil {
- directory, _ := filepath.Abs(filepath.Dir(path))
- return model.NewLocAppError("WriteFile", "api.file.write_file_locally.create_dir.app_error", nil, "directory="+directory+", err="+err.Error())
- }
-
- if err := ioutil.WriteFile(path, f, 0644); err != nil {
- return model.NewLocAppError("WriteFile", "api.file.write_file_locally.writing.app_error", nil, err.Error())
- }
-
- return nil
-}
-
-func ReadFile(path string) ([]byte, *model.AppError) {
- if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
- endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint
- accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId
- secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey
- secure := *utils.Cfg.FileSettings.AmazonS3SSL
- s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure)
- if err != nil {
- return nil, model.NewLocAppError("ReadFile", "api.file.read_file.s3.app_error", nil, err.Error())
- }
- bucket := utils.Cfg.FileSettings.AmazonS3Bucket
- minioObject, err := s3Clnt.GetObject(bucket, path)
- defer minioObject.Close()
- if err != nil {
- return nil, model.NewLocAppError("ReadFile", "api.file.read_file.s3.app_error", nil, err.Error())
- }
- if f, err := ioutil.ReadAll(minioObject); err != nil {
- return nil, model.NewLocAppError("ReadFile", "api.file.read_file.s3.app_error", nil, err.Error())
- } else {
- return f, nil
- }
- } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
- if f, err := ioutil.ReadFile(utils.Cfg.FileSettings.Directory + path); err != nil {
- return nil, model.NewLocAppError("ReadFile", "api.file.read_file.reading_local.app_error", nil, err.Error())
- } else {
- return f, nil
- }
- } else {
- return nil, model.NewLocAppError("ReadFile", "api.file.read_file.configured.app_error", nil, "")
- }
-}
-
-func openFileWriteStream(path string) (io.Writer, *model.AppError) {
- if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
- return nil, model.NewLocAppError("openFileWriteStream", "api.file.open_file_write_stream.s3.app_error", nil, "")
- } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
- if err := os.MkdirAll(filepath.Dir(utils.Cfg.FileSettings.Directory+path), 0774); err != nil {
- return nil, model.NewLocAppError("openFileWriteStream", "api.file.open_file_write_stream.creating_dir.app_error", nil, err.Error())
- }
-
- if fileHandle, err := os.Create(utils.Cfg.FileSettings.Directory + path); err != nil {
- return nil, model.NewLocAppError("openFileWriteStream", "api.file.open_file_write_stream.local_server.app_error", nil, err.Error())
- } else {
- fileHandle.Chmod(0644)
- return fileHandle, nil
- }
- }
-
- return nil, model.NewLocAppError("openFileWriteStream", "api.file.open_file_write_stream.configured.app_error", nil, "")
-}
-
-func closeFileWriteStream(file io.Writer) {
- file.(*os.File).Close()
+ w.Write([]byte(model.StringToJson(app.GeneratePublicLink(c.GetSiteURL(), info))))
}
diff --git a/api/file_test.go b/api/file_test.go
index 56e604fea..ce3e1fab4 100644
--- a/api/file_test.go
+++ b/api/file_test.go
@@ -508,7 +508,7 @@ func TestGetPublicFileOld(t *testing.T) {
}
func generatePublicLinkOld(siteURL, teamId, channelId, userId, filename string) string {
- hash := generatePublicLinkHash(filename, *utils.Cfg.FileSettings.PublicLinkSalt)
+ hash := app.GeneratePublicLinkHash(filename, *utils.Cfg.FileSettings.PublicLinkSalt)
return fmt.Sprintf("%s%s/public/files/get/%s/%s/%s/%s?h=%s", siteURL, model.API_URL_SUFFIX, teamId, channelId, userId, filename, hash)
}
@@ -581,29 +581,6 @@ func TestGetPublicLink(t *testing.T) {
}
}
-func TestGeneratePublicLinkHash(t *testing.T) {
- filename1 := model.NewId() + "/" + model.NewRandomString(16) + ".txt"
- filename2 := model.NewId() + "/" + model.NewRandomString(16) + ".txt"
- salt1 := model.NewRandomString(32)
- salt2 := model.NewRandomString(32)
-
- hash1 := generatePublicLinkHash(filename1, salt1)
- hash2 := generatePublicLinkHash(filename2, salt1)
- hash3 := generatePublicLinkHash(filename1, salt2)
-
- if hash1 != generatePublicLinkHash(filename1, salt1) {
- t.Fatal("hash should be equal for the same file name and salt")
- }
-
- if hash1 == hash2 {
- t.Fatal("hashes for different files should not be equal")
- }
-
- if hash1 == hash3 {
- t.Fatal("hashes for the same file with different salts should not be equal")
- }
-}
-
func TestMigrateFilenamesToFileInfos(t *testing.T) {
th := Setup().InitBasic()
@@ -740,7 +717,7 @@ func TestFindTeamIdForFilename(t *testing.T) {
Filenames: []string{fmt.Sprintf("/%s/%s/%s/%s", channel1.Id, user1.Id, fileId1, "test.png")},
})).(*model.Post)
- if teamId := findTeamIdForFilename(post1, post1.Filenames[0]); teamId != team1.Id {
+ if teamId := app.FindTeamIdForFilename(post1, post1.Filenames[0]); teamId != team1.Id {
t.Fatal("file should've been found under team1")
}
@@ -753,7 +730,7 @@ func TestFindTeamIdForFilename(t *testing.T) {
})).(*model.Post)
Client.SetTeamId(team1.Id)
- if teamId := findTeamIdForFilename(post2, post2.Filenames[0]); teamId != team2.Id {
+ if teamId := app.FindTeamIdForFilename(post2, post2.Filenames[0]); teamId != team2.Id {
t.Fatal("file should've been found under team2")
}
}
@@ -795,7 +772,7 @@ func TestGetInfoForFilename(t *testing.T) {
Filenames: []string{fmt.Sprintf("/%s/%s/%s/%s", channel1.Id, user1.Id, fileId1, "test.png")},
})).(*model.Post)
- if info := getInfoForFilename(post1, team1.Id, post1.Filenames[0]); info == nil {
+ if info := app.GetInfoForFilename(post1, team1.Id, post1.Filenames[0]); info == nil {
t.Fatal("info shouldn't be nil")
} else if info.Id == "" {
t.Fatal("info.Id shouldn't be empty")
diff --git a/api/oauth.go b/api/oauth.go
index 80daf7415..b378eb2a3 100644
--- a/api/oauth.go
+++ b/api/oauth.go
@@ -250,32 +250,6 @@ func getAuthorizedApps(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func RevokeAccessToken(token string) *model.AppError {
-
- session, _ := app.GetSession(token)
- schan := app.Srv.Store.Session().Remove(token)
-
- if result := <-app.Srv.Store.OAuth().GetAccessData(token); result.Err != nil {
- return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.get.app_error", nil, "")
- }
-
- tchan := app.Srv.Store.OAuth().RemoveAccessData(token)
-
- if result := <-tchan; result.Err != nil {
- return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_token.app_error", nil, "")
- }
-
- if result := <-schan; result.Err != nil {
- return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_session.app_error", nil, "")
- }
-
- if session != nil {
- app.RemoveAllSessionsForUserId(session.UserId)
- }
-
- return nil
-}
-
func GetAuthData(code string) *model.AuthData {
if result := <-app.Srv.Store.OAuth().GetAuthData(code); result.Err != nil {
l4g.Error(utils.T("api.oauth.get_auth_data.find.error"), code)
@@ -311,7 +285,11 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
action := props["action"]
switch action {
case model.OAUTH_ACTION_SIGNUP:
- CreateOAuthUser(c, w, r, service, body, teamId)
+ if user, err := app.CreateOAuthUser(service, body, teamId); err != nil {
+ c.Err = err
+ } else {
+ doLogin(c, w, r, user, "")
+ }
if c.Err == nil {
http.Redirect(w, r, GetProtocol(r)+"://"+r.Host, http.StatusTemporaryRedirect)
}
@@ -931,7 +909,7 @@ func deauthorizeOAuthApp(c *Context, w http.ResponseWriter, r *http.Request) {
accessData := result.Data.([]*model.AccessData)
for _, a := range accessData {
- if err := RevokeAccessToken(a.Token); err != nil {
+ if err := app.RevokeAccessToken(a.Token); err != nil {
c.Err = err
return
}
diff --git a/api/post.go b/api/post.go
index 270ab72ca..bbdce78e8 100644
--- a/api/post.go
+++ b/api/post.go
@@ -362,13 +362,18 @@ func getPermalinkTmp(c *Context, w http.ResponseWriter, r *http.Request) {
}
post := list.Posts[list.Order[0]]
- // Because we confuse permissions and membership in Mattermost's model, we have to just
- // try to join the channel without checking if we already have permission to it. This is
- // because system admins have permissions to every channel but are not nessisary a member
- // of every channel. If we checked here then system admins would skip joining the channel and
- // error when they tried to view it.
- if err, _ := JoinChannelById(c, c.Session.UserId, post.ChannelId); err != nil {
- // On error just return with permissions error
+ var channel *model.Channel
+ var err *model.AppError
+ if channel, err = app.GetChannel(post.ChannelId); err != nil {
+ c.Err = err
+ return
+ }
+
+ if !HasPermissionToTeamContext(c, channel.TeamId, model.PERMISSION_JOIN_PUBLIC_CHANNELS) {
+ return
+ }
+
+ if err = app.JoinChannel(channel, c.Session.UserId); err != nil {
c.Err = err
return
}
@@ -611,7 +616,7 @@ func getFileInfosForPost(c *Context, w http.ResponseWriter, r *http.Request) {
if len(post.Filenames) > 0 {
// The post has Filenames that need to be replaced with FileInfos
- infos = migrateFilenamesToFileInfos(post)
+ infos = app.MigrateFilenamesToFileInfos(post)
}
}
diff --git a/api/team.go b/api/team.go
index a8ed90eb5..6f907a513 100644
--- a/api/team.go
+++ b/api/team.go
@@ -248,7 +248,10 @@ func revokeAllSessions(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("revoked_all=" + id)
if session.IsOAuth {
- RevokeAccessToken(session.Token)
+ if err := app.RevokeAccessToken(session.Token); err != nil {
+ c.Err = err
+ return
+ }
} else {
if result := <-app.Srv.Store.Session().Remove(session.Id); result.Err != nil {
c.Err = result.Err
diff --git a/api/user.go b/api/user.go
index e3cae3704..701badfc9 100644
--- a/api/user.go
+++ b/api/user.go
@@ -7,16 +7,12 @@ import (
"bytes"
b64 "encoding/base64"
"fmt"
- "hash/fnv"
"html/template"
"image"
- "image/color"
- "image/draw"
_ "image/gif"
_ "image/jpeg"
"image/png"
"io"
- "io/ioutil"
"net/http"
"net/url"
"strconv"
@@ -25,7 +21,6 @@ import (
l4g "github.com/alecthomas/log4go"
"github.com/disintegration/imaging"
- "github.com/golang/freetype"
"github.com/gorilla/mux"
"github.com/mattermost/platform/app"
"github.com/mattermost/platform/einterfaces"
@@ -102,94 +97,50 @@ func createUser(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- hash := r.URL.Query().Get("h")
- teamId := ""
- var team *model.Team
- shouldSendWelcomeEmail := true
user.EmailVerified = false
- if len(hash) > 0 {
- data := r.URL.Query().Get("d")
- props := model.MapFromJson(strings.NewReader(data))
-
- if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
- c.Err = model.NewLocAppError("createUser", "api.user.create_user.signup_link_invalid.app_error", nil, "")
- return
- }
+ shouldSendWelcomeEmail := true
- t, err := strconv.ParseInt(props["time"], 10, 64)
- if err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours
- c.Err = model.NewLocAppError("createUser", "api.user.create_user.signup_link_expired.app_error", nil, "")
- return
- }
+ hash := r.URL.Query().Get("h")
+ inviteId := r.URL.Query().Get("iid")
- teamId = props["id"]
+ if !CheckUserDomain(user, utils.Cfg.TeamSettings.RestrictCreationToDomains) {
+ c.Err = model.NewLocAppError("createUser", "api.user.create_user.accepted_domain.app_error", nil, "")
+ return
+ }
- // try to load the team to make sure it exists
- if result := <-app.Srv.Store.Team().Get(teamId); result.Err != nil {
- c.Err = result.Err
+ var ruser *model.User
+ var err *model.AppError
+ if len(hash) > 0 {
+ data := r.URL.Query().Get("d")
+ ruser, err = app.CreateUserWithHash(user, hash, data)
+ if err != nil {
+ c.Err = err
return
- } else {
- team = result.Data.(*model.Team)
}
- user.Email = props["email"]
- user.EmailVerified = true
shouldSendWelcomeEmail = false
- }
-
- inviteId := r.URL.Query().Get("iid")
- if len(inviteId) > 0 {
- if result := <-app.Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
- c.Err = result.Err
+ } else if len(inviteId) > 0 {
+ ruser, err = app.CreateUserWithInviteId(user, inviteId)
+ if err != nil {
+ c.Err = err
return
- } else {
- team = result.Data.(*model.Team)
- teamId = team.Id
}
- }
-
- firstAccount := false
- if app.SessionCacheLength() == 0 {
- if cr := <-app.Srv.Store.User().GetTotalUsersCount(); cr.Err != nil {
- c.Err = cr.Err
+ } else {
+ if !app.IsFirstUserAccount() && !*utils.Cfg.TeamSettings.EnableOpenServer {
+ c.Err = model.NewLocAppError("createUser", "api.user.create_user.no_open_server", nil, "email="+user.Email)
return
- } else {
- count := cr.Data.(int64)
- if count <= 0 {
- firstAccount = true
- }
}
- }
-
- if !firstAccount && !*utils.Cfg.TeamSettings.EnableOpenServer && len(teamId) == 0 {
- c.Err = model.NewLocAppError("createUser", "api.user.create_user.no_open_server", nil, "email="+user.Email)
- return
- }
-
- if !CheckUserDomain(user, utils.Cfg.TeamSettings.RestrictCreationToDomains) {
- c.Err = model.NewLocAppError("createUser", "api.user.create_user.accepted_domain.app_error", nil, "")
- return
- }
-
- ruser, err := app.CreateUser(user)
- if err != nil {
- c.Err = err
- return
- }
- if len(teamId) > 0 {
- err := app.JoinUserToTeam(team, ruser)
+ ruser, err = app.CreateUser(user)
if err != nil {
c.Err = err
return
}
-
- go addDirectChannels(team.Id, ruser)
}
if shouldSendWelcomeEmail {
- go sendWelcomeEmail(c, ruser.Id, ruser.Email, c.GetSiteURL(), ruser.EmailVerified)
+ sendWelcomeEmail(c, ruser.Id, ruser.Email, c.GetSiteURL(), ruser.EmailVerified)
}
w.Write([]byte(ruser.ToJson()))
@@ -239,77 +190,6 @@ func IsVerifyHashRequired(user *model.User, team *model.Team, hash string) bool
return shouldVerifyHash
}
-func CreateOAuthUser(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader, teamId string) *model.User {
- var user *model.User
- provider := einterfaces.GetOauthProvider(service)
- if provider == nil {
- c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.not_available.app_error", map[string]interface{}{"Service": strings.Title(service)}, "")
- return nil
- } else {
- user = provider.GetUserFromJson(userData)
- }
-
- if user == nil {
- c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.create.app_error", map[string]interface{}{"Service": service}, "")
- return nil
- }
-
- suchan := app.Srv.Store.User().GetByAuth(user.AuthData, service)
- euchan := app.Srv.Store.User().GetByEmail(user.Email)
-
- found := true
- count := 0
- for found {
- if found = IsUsernameTaken(user.Username); found {
- user.Username = user.Username + strconv.Itoa(count)
- count += 1
- }
- }
-
- if result := <-suchan; result.Err == nil {
- c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_used.app_error",
- map[string]interface{}{"Service": service}, "email="+user.Email)
- return nil
- }
-
- if result := <-euchan; result.Err == nil {
- authService := result.Data.(*model.User).AuthService
- if authService == "" {
- c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error",
- map[string]interface{}{"Service": service, "Auth": model.USER_AUTH_SERVICE_EMAIL}, "email="+user.Email)
- } else {
- c.Err = model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error",
- map[string]interface{}{"Service": service, "Auth": authService}, "email="+user.Email)
- }
- return nil
- }
-
- user.EmailVerified = true
-
- ruser, err := app.CreateUser(user)
- if err != nil {
- c.Err = err
- return nil
- }
-
- if len(teamId) > 0 {
- err = app.JoinUserToTeamById(teamId, user)
- if err != nil {
- c.Err = err
- return nil
- }
-
- go addDirectChannels(teamId, user)
- }
-
- doLogin(c, w, r, ruser, "")
- if c.Err != nil {
- return nil
- }
-
- return ruser
-}
-
func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, verified bool) {
rawUrl, _ := url.Parse(siteURL)
@@ -339,43 +219,6 @@ func sendWelcomeEmail(c *Context, userId string, email string, siteURL string, v
}
}
-func addDirectChannels(teamId string, user *model.User) {
- var profiles map[string]*model.User
- if result := <-app.Srv.Store.User().GetProfiles(teamId, 0, 100); result.Err != nil {
- l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error())
- return
- } else {
- profiles = result.Data.(map[string]*model.User)
- }
-
- var preferences model.Preferences
-
- for id := range profiles {
- if id == user.Id {
- continue
- }
-
- profile := profiles[id]
-
- preference := model.Preference{
- UserId: user.Id,
- Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
- Name: profile.Id,
- Value: "true",
- }
-
- preferences = append(preferences, preference)
-
- if len(preferences) >= 10 {
- break
- }
- }
-
- if result := <-app.Srv.Store.Preference().Save(&preferences); result.Err != nil {
- l4g.Error(utils.T("api.user.add_direct_channels_and_forget.failed.error"), user.Id, teamId, result.Err.Error())
- }
-}
-
func SendVerifyEmail(c *Context, userId, userEmail, 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(userEmail))
@@ -418,21 +261,19 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
if len(id) != 0 {
c.LogAuditWithUserId(id, "attempt")
- if result := <-app.Srv.Store.User().Get(id); result.Err != nil {
+ if user, err = app.GetUser(id); err != nil {
c.LogAuditWithUserId(id, "failure")
- c.Err = result.Err
+ c.Err = err
c.Err.StatusCode = http.StatusBadRequest
if einterfaces.GetMetricsInterface() != nil {
einterfaces.GetMetricsInterface().IncrementLoginFail()
}
return
- } else {
- user = result.Data.(*model.User)
}
} else {
c.LogAudit("attempt")
- if user, err = getUserForLogin(loginId, ldapOnly); err != nil {
+ if user, err = app.GetUserForLogin(loginId, ldapOnly); err != nil {
c.LogAudit("failure")
c.Err = err
if einterfaces.GetMetricsInterface() != nil {
@@ -469,37 +310,6 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(user.ToJson()))
}
-func getUserForLogin(loginId string, onlyLdap bool) (*model.User, *model.AppError) {
- ldapAvailable := *utils.Cfg.LdapSettings.Enable && einterfaces.GetLdapInterface() != nil && utils.IsLicensed && *utils.License.Features.LDAP
-
- if result := <-app.Srv.Store.User().GetForLogin(
- loginId,
- *utils.Cfg.EmailSettings.EnableSignInWithUsername && !onlyLdap,
- *utils.Cfg.EmailSettings.EnableSignInWithEmail && !onlyLdap,
- ldapAvailable,
- ); result.Err != nil && result.Err.Id == "store.sql_user.get_for_login.multiple_users" {
- // don't fall back to LDAP in this case since we already know there's an LDAP user, but that it shouldn't work
- result.Err.StatusCode = http.StatusBadRequest
- return nil, result.Err
- } else if result.Err != nil {
- if !ldapAvailable {
- // failed to find user and no LDAP server to fall back on
- result.Err.StatusCode = http.StatusBadRequest
- return nil, result.Err
- }
-
- // fall back to LDAP server to see if we can find a user
- if ldapUser, ldapErr := einterfaces.GetLdapInterface().GetUser(loginId); ldapErr != nil {
- ldapErr.StatusCode = http.StatusBadRequest
- return nil, ldapErr
- } else {
- return ldapUser, nil
- }
- } else {
- return result.Data.(*model.User), nil
- }
-}
-
func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service string, userData io.Reader) *model.User {
buf := bytes.Buffer{}
buf.ReadFrom(userData)
@@ -521,20 +331,23 @@ func LoginByOAuth(c *Context, w http.ResponseWriter, r *http.Request, service st
}
var user *model.User
- if result := <-app.Srv.Store.User().GetByAuth(&authData, service); result.Err != nil {
- if result.Err.Id == store.MISSING_AUTH_ACCOUNT_ERROR {
- return CreateOAuthUser(c, w, r, service, bytes.NewReader(buf.Bytes()), "")
+ var err *model.AppError
+ if user, err = app.GetUserByAuth(&authData, service); err != nil {
+ if err.Id == store.MISSING_AUTH_ACCOUNT_ERROR {
+ if user, err = app.CreateOAuthUser(service, bytes.NewReader(buf.Bytes()), ""); err != nil {
+ c.Err = err
+ return nil
+ }
}
- c.Err = result.Err
+ c.Err = err
return nil
- } else {
- user = result.Data.(*model.User)
- doLogin(c, w, r, user, "")
- if c.Err != nil {
- return nil
- }
- return user
}
+
+ doLogin(c, w, r, user, "")
+ if c.Err != nil {
+ return nil
+ }
+ return user
}
// User MUST be authenticated completely before calling Login
@@ -558,9 +371,8 @@ func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.Use
for _, session := range sessions {
if session.DeviceId == deviceId {
l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, user.Id)
- RevokeSessionById(c, session.Id)
- if c.Err != nil {
- c.LogError(c.Err)
+ if err := app.RevokeSessionById(session.Id); err != nil {
+ c.LogError(err)
c.Err = nil
}
}
@@ -634,7 +446,12 @@ func doLogin(c *Context, w http.ResponseWriter, r *http.Request, user *model.Use
func revokeSession(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
id := props["id"]
- RevokeSessionById(c, id)
+
+ if err := app.RevokeSessionById(id); err != nil {
+ c.Err = err
+ return
+ }
+
w.Write([]byte(model.MapToJson(props)))
}
@@ -652,23 +469,11 @@ func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- // A special case where we logout of all other sessions with the same Id
- if result := <-app.Srv.Store.Session().GetSessions(c.Session.UserId); result.Err != nil {
- c.Err = result.Err
+ // A special case where we logout of all other sessions with the same device id
+ if err := app.RevokeSessionsForDeviceId(c.Session.UserId, deviceId, c.Session.Id); err != nil {
+ c.Err = err
c.Err.StatusCode = http.StatusInternalServerError
return
- } else {
- sessions := result.Data.([]*model.Session)
- for _, session := range sessions {
- if session.DeviceId == deviceId && session.Id != c.Session.Id {
- l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, c.Session.UserId)
- RevokeSessionById(c, session.Id)
- if c.Err != nil {
- c.LogError(c.Err)
- c.Err = nil
- }
- }
- }
}
app.RemoveAllSessionsForUserId(c.Session.UserId)
@@ -694,34 +499,14 @@ func attachDeviceId(c *Context, w http.ResponseWriter, r *http.Request) {
http.SetCookie(w, sessionCookie)
- if result := <-app.Srv.Store.Session().UpdateDeviceId(c.Session.Id, deviceId, c.Session.ExpiresAt); result.Err != nil {
- c.Err = result.Err
+ if err := app.AttachDeviceId(c.Session.Id, deviceId, c.Session.ExpiresAt); err != nil {
+ c.Err = err
return
}
w.Write([]byte(model.MapToJson(props)))
}
-func RevokeSessionById(c *Context, sessionId string) {
- if result := <-app.Srv.Store.Session().Get(sessionId); result.Err != nil {
- c.Err = result.Err
- } else {
- session := result.Data.(*model.Session)
- c.LogAudit("session_id=" + session.Id)
-
- if session.IsOAuth {
- RevokeAccessToken(session.Token)
- } else {
- if result := <-app.Srv.Store.Session().Remove(session.Id); result.Err != nil {
- c.Err = result.Err
- }
- }
-
- RevokeWebrtcToken(session.Id)
- app.RemoveAllSessionsForUserId(session.UserId)
- }
-}
-
// IF YOU UPDATE THIS PLEASE UPDATE BELOW
func RevokeAllSession(c *Context, userId string) {
if result := <-app.Srv.Store.Session().GetSessions(userId); result.Err != nil {
@@ -733,7 +518,7 @@ func RevokeAllSession(c *Context, userId string) {
for _, session := range sessions {
c.LogAuditWithUserId(userId, "session_id="+session.Id)
if session.IsOAuth {
- RevokeAccessToken(session.Token)
+ app.RevokeAccessToken(session.Token)
} else {
if result := <-app.Srv.Store.Session().Remove(session.Id); result.Err != nil {
c.Err = result.Err
@@ -741,7 +526,7 @@ func RevokeAllSession(c *Context, userId string) {
}
}
- RevokeWebrtcToken(session.Id)
+ app.RevokeWebrtcToken(session.Id)
}
}
@@ -758,14 +543,14 @@ func RevokeAllSessionsNoContext(userId string) *model.AppError {
for _, session := range sessions {
if session.IsOAuth {
- RevokeAccessToken(session.Token)
+ app.RevokeAccessToken(session.Token)
} else {
if result := <-app.Srv.Store.Session().Remove(session.Id); result.Err != nil {
return result.Err
}
}
- RevokeWebrtcToken(session.Id)
+ app.RevokeWebrtcToken(session.Id)
}
}
@@ -810,23 +595,26 @@ func Logout(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("")
c.RemoveSessionCookie(w, r)
if c.Session.Id != "" {
- RevokeSessionById(c, c.Session.Id)
+ if err := app.RevokeSessionById(c.Session.Id); err != nil {
+ c.Err = err
+ return
+ }
}
}
func getMe(c *Context, w http.ResponseWriter, r *http.Request) {
- if result := <-app.Srv.Store.User().Get(c.Session.UserId); result.Err != nil {
- c.Err = result.Err
+ if user, err := app.GetUser(c.Session.UserId); err != nil {
+ c.Err = err
c.RemoveSessionCookie(w, r)
l4g.Error(utils.T("api.user.get_me.getting.error"), c.Session.UserId)
return
- } else if HandleEtag(result.Data.(*model.User).Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get Me", w, r) {
+ } else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get Me", w, r) {
return
} else {
- result.Data.(*model.User).Sanitize(map[string]bool{})
- w.Header().Set(model.HEADER_ETAG_SERVER, result.Data.(*model.User).Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress))
- w.Write([]byte(result.Data.(*model.User).ToJson()))
+ user.Sanitize(map[string]bool{})
+ w.Header().Set(model.HEADER_ETAG_SERVER, user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress))
+ w.Write([]byte(user.ToJson()))
return
}
}
@@ -835,59 +623,36 @@ func getInitialLoad(c *Context, w http.ResponseWriter, r *http.Request) {
il := model.InitialLoad{}
- var cchan store.StoreChannel
-
- if app.SessionCacheLength() == 0 {
- // Below is a special case when intializating a new server
- // Lets check to make sure the server is really empty
-
- cchan = app.Srv.Store.User().GetTotalUsersCount()
- }
-
if len(c.Session.UserId) != 0 {
- uchan := app.Srv.Store.User().Get(c.Session.UserId)
- pchan := app.Srv.Store.Preference().GetAll(c.Session.UserId)
- tchan := app.Srv.Store.Team().GetTeamsByUserId(c.Session.UserId)
+ var err *model.AppError
- il.TeamMembers = c.Session.TeamMembers
-
- if ru := <-uchan; ru.Err != nil {
- c.Err = ru.Err
+ il.User, err = app.GetUser(c.Session.UserId)
+ if err != nil {
+ c.Err = err
return
- } else {
- il.User = ru.Data.(*model.User)
- il.User.Sanitize(map[string]bool{})
}
+ il.User.Sanitize(map[string]bool{})
- if rp := <-pchan; rp.Err != nil {
- c.Err = rp.Err
- return
- } else {
- il.Preferences = rp.Data.(model.Preferences)
- }
+ il.Preferences, err = app.GetPreferencesForUser(c.Session.Id)
- if rt := <-tchan; rt.Err != nil {
- c.Err = rt.Err
+ il.Teams, err = app.GetTeamsForUser(c.Session.UserId)
+ if err != nil {
+ c.Err = err
return
- } else {
- il.Teams = rt.Data.([]*model.Team)
+ }
- for _, team := range il.Teams {
- team.Sanitize()
- }
+ for _, team := range il.Teams {
+ team.Sanitize()
}
+
+ il.TeamMembers = c.Session.TeamMembers
}
- if cchan != nil {
- if cr := <-cchan; cr.Err != nil {
- c.Err = cr.Err
- return
- } else {
- count := cr.Data.(int64)
- if count <= 0 {
- il.NoAccounts = true
- }
- }
+ if app.SessionCacheLength() == 0 {
+ // Below is a special case when intializating a new server
+ // Lets check to make sure the server is really empty
+
+ il.NoAccounts = app.IsFirstUserAccount()
}
il.ClientCfg = utils.ClientCfg
@@ -905,16 +670,19 @@ func getUser(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id := params["user_id"]
- if result := <-app.Srv.Store.User().Get(id); result.Err != nil {
- c.Err = result.Err
+ var user *model.User
+ var err *model.AppError
+
+ if user, err = app.GetUser(id); err != nil {
+ c.Err = err
return
- } else if HandleEtag(result.Data.(*model.User).Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get User", w, r) {
+ } else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get User", w, r) {
return
} else {
- user := sanitizeProfile(c, result.Data.(*model.User))
+ sanitizeProfile(c, 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()))
+ w.Write([]byte(user.ToJson()))
return
}
}
@@ -923,16 +691,19 @@ func getByUsername(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
username := params["username"]
- if result := <-app.Srv.Store.User().GetByUsername(username); result.Err != nil {
- c.Err = result.Err
+ var user *model.User
+ var err *model.AppError
+
+ if user, err = app.GetUserByUsername(username); err != nil {
+ c.Err = err
return
- } else if HandleEtag(result.Data.(*model.User).Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get By Username", w, r) {
+ } else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get By Username", w, r) {
return
} else {
- user := sanitizeProfile(c, result.Data.(*model.User))
+ sanitizeProfile(c, 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()))
+ w.Write([]byte(user.ToJson()))
return
}
}
@@ -941,16 +712,19 @@ func getByEmail(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
email := params["email"]
- if result := <-app.Srv.Store.User().GetByEmail(email); result.Err != nil {
- c.Err = result.Err
+ var user *model.User
+ var err *model.AppError
+
+ if user, err = app.GetUserByEmail(email); err != nil {
+ c.Err = err
return
- } else if HandleEtag(result.Data.(*model.User).Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get By Email", w, r) {
+ } else if HandleEtag(user.Etag(utils.Cfg.PrivacySettings.ShowFullName, utils.Cfg.PrivacySettings.ShowEmailAddress), "Get By Email", w, r) {
return
} else {
- user := sanitizeProfile(c, result.Data.(*model.User))
+ sanitizeProfile(c, 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()))
+ w.Write([]byte(user.ToJson()))
return
}
}
@@ -970,17 +744,18 @@ func getProfiles(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- etag := (<-app.Srv.Store.User().GetEtagForAllProfiles()).Data.(string)
+ etag := app.GetUsersEtag()
if HandleEtag(etag, "Get Profiles", w, r) {
return
}
- if result := <-app.Srv.Store.User().GetAllProfiles(offset, limit); result.Err != nil {
- c.Err = result.Err
+ var profiles map[string]*model.User
+ var profileErr *model.AppError
+
+ if profiles, profileErr = app.GetUsers(offset, limit); profileErr != nil {
+ c.Err = profileErr
return
} else {
- profiles := result.Data.(map[string]*model.User)
-
for k, p := range profiles {
profiles[k] = sanitizeProfile(c, p)
}
@@ -1012,17 +787,18 @@ func getProfilesInTeam(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- etag := (<-app.Srv.Store.User().GetEtagForProfiles(teamId)).Data.(string)
+ etag := app.GetUsersInTeamEtag(teamId)
if HandleEtag(etag, "Get Profiles In Team", w, r) {
return
}
- if result := <-app.Srv.Store.User().GetProfiles(teamId, offset, limit); result.Err != nil {
- c.Err = result.Err
+ var profiles map[string]*model.User
+ var profileErr *model.AppError
+
+ if profiles, profileErr = app.GetUsersInTeam(teamId, offset, limit); profileErr != nil {
+ c.Err = profileErr
return
} else {
- profiles := result.Data.(map[string]*model.User)
-
for k, p := range profiles {
profiles[k] = sanitizeProfile(c, p)
}
@@ -1058,12 +834,13 @@ func getProfilesInChannel(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if result := <-app.Srv.Store.User().GetProfilesInChannel(channelId, offset, limit, false); result.Err != nil {
- c.Err = result.Err
+ var profiles map[string]*model.User
+ var profileErr *model.AppError
+
+ if profiles, err = app.GetUsersInChannel(channelId, offset, limit); profileErr != nil {
+ c.Err = profileErr
return
} else {
- profiles := result.Data.(map[string]*model.User)
-
for k, p := range profiles {
profiles[k] = sanitizeProfile(c, p)
}
@@ -1098,12 +875,13 @@ func getProfilesNotInChannel(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- if result := <-app.Srv.Store.User().GetProfilesNotInChannel(c.TeamId, channelId, offset, limit); result.Err != nil {
- c.Err = result.Err
+ var profiles map[string]*model.User
+ var profileErr *model.AppError
+
+ if profiles, err = app.GetUsersNotInChannel(c.TeamId, channelId, offset, limit); profileErr != nil {
+ c.Err = profileErr
return
} else {
- profiles := result.Data.(map[string]*model.User)
-
for k, p := range profiles {
profiles[k] = sanitizeProfile(c, p)
}
@@ -1120,18 +898,10 @@ func getAudits(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- userChan := app.Srv.Store.User().Get(id)
- auditChan := app.Srv.Store.Audit().Get(id, 20)
-
- if c.Err = (<-userChan).Err; c.Err != nil {
- return
- }
-
- if result := <-auditChan; result.Err != nil {
- c.Err = result.Err
+ if audits, err := app.GetAudits(id, 20); err != nil {
+ c.Err = err
return
} else {
- audits := result.Data.(model.Audits)
etag := audits.Etag()
if HandleEtag(etag, "Get Audits", w, r) {
@@ -1147,128 +917,29 @@ func getAudits(c *Context, w http.ResponseWriter, r *http.Request) {
}
}
-func createProfileImage(username string, userId string) ([]byte, *model.AppError) {
- colors := []color.NRGBA{
- {197, 8, 126, 255},
- {227, 207, 18, 255},
- {28, 181, 105, 255},
- {35, 188, 224, 255},
- {116, 49, 196, 255},
- {197, 8, 126, 255},
- {197, 19, 19, 255},
- {250, 134, 6, 255},
- {227, 207, 18, 255},
- {123, 201, 71, 255},
- {28, 181, 105, 255},
- {35, 188, 224, 255},
- {116, 49, 196, 255},
- {197, 8, 126, 255},
- {197, 19, 19, 255},
- {250, 134, 6, 255},
- {227, 207, 18, 255},
- {123, 201, 71, 255},
- {28, 181, 105, 255},
- {35, 188, 224, 255},
- {116, 49, 196, 255},
- {197, 8, 126, 255},
- {197, 19, 19, 255},
- {250, 134, 6, 255},
- {227, 207, 18, 255},
- {123, 201, 71, 255},
- }
-
- h := fnv.New32a()
- h.Write([]byte(userId))
- seed := h.Sum32()
-
- initial := string(strings.ToUpper(username)[0])
-
- fontBytes, err := ioutil.ReadFile(utils.FindDir("fonts") + utils.Cfg.FileSettings.InitialFont)
- if err != nil {
- return nil, model.NewLocAppError("createProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error())
- }
- font, err := freetype.ParseFont(fontBytes)
- if err != nil {
- return nil, model.NewLocAppError("createProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error())
- }
-
- width := int(utils.Cfg.FileSettings.ProfileWidth)
- height := int(utils.Cfg.FileSettings.ProfileHeight)
- color := colors[int64(seed)%int64(len(colors))]
- dstImg := image.NewRGBA(image.Rect(0, 0, width, height))
- srcImg := image.White
- draw.Draw(dstImg, dstImg.Bounds(), &image.Uniform{color}, image.ZP, draw.Src)
- size := float64((width + height) / 4)
-
- c := freetype.NewContext()
- c.SetFont(font)
- c.SetFontSize(size)
- c.SetClip(dstImg.Bounds())
- c.SetDst(dstImg)
- c.SetSrc(srcImg)
-
- pt := freetype.Pt(width/6, height*2/3)
- _, err = c.DrawString(initial, pt)
- if err != nil {
- return nil, model.NewLocAppError("createProfileImage", "api.user.create_profile_image.initial.app_error", nil, err.Error())
- }
-
- buf := new(bytes.Buffer)
-
- if imgErr := png.Encode(buf, dstImg); imgErr != nil {
- return nil, model.NewLocAppError("createProfileImage", "api.user.create_profile_image.encode.app_error", nil, imgErr.Error())
- } else {
- return buf.Bytes(), nil
- }
-}
-
func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
params := mux.Vars(r)
id := params["user_id"]
- readFailed := false
var etag string
- if result := <-app.Srv.Store.User().Get(id); result.Err != nil {
- c.Err = result.Err
+ if user, err := app.GetUser(id); err != nil {
+ c.Err = err
return
} else {
- var img []byte
- etag = strconv.FormatInt(result.Data.(*model.User).LastPictureUpdate, 10)
+ etag = strconv.FormatInt(user.LastPictureUpdate, 10)
if HandleEtag(etag, "Profile Image", w, r) {
return
}
- if len(utils.Cfg.FileSettings.DriverName) == 0 {
- var err *model.AppError
- if img, err = createProfileImage(result.Data.(*model.User).Username, id); err != nil {
- c.Err = err
- return
- }
- } else {
- path := "users/" + id + "/profile.png"
-
- if data, err := ReadFile(path); err != nil {
- readFailed = true
-
- if img, err = createProfileImage(result.Data.(*model.User).Username, id); err != nil {
- c.Err = err
- return
- }
-
- if result.Data.(*model.User).LastPictureUpdate == 0 {
- if err := WriteFile(img, path); err != nil {
- c.Err = err
- return
- }
- }
-
- } else {
- img = data
- }
+ var img []byte
+ img, err = app.GetProfileImage(user)
+ if err != nil {
+ c.Err = err
+ return
}
- if c.Session.UserId == id || readFailed {
+ if c.Session.UserId == id {
w.Header().Set("Cache-Control", "max-age=300, public") // 5 mins
} else {
w.Header().Set("Cache-Control", "max-age=86400, public") // 24 hrs
@@ -1353,7 +1024,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
path := "users/" + c.Session.UserId + "/profile.png"
- if err := WriteFile(buf.Bytes(), path); err != nil {
+ if err := app.WriteFile(buf.Bytes(), path); err != nil {
c.Err = model.NewLocAppError("uploadProfileImage", "api.user.upload_profile_user.upload_profile.app_error", nil, "")
return
}
@@ -2000,22 +1671,6 @@ 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) {
- return false
- }
-
- if result := <-app.Srv.Store.User().GetByUsername(name); result.Err != nil {
- return false
- } else {
- return true
- }
-
- return false
-}
-
func emailToOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
@@ -2336,7 +1991,7 @@ func resendVerification(c *Context, w http.ResponseWriter, r *http.Request) {
return
}
- if user, error := getUserForLogin(email, false); error != nil {
+ if user, error := app.GetUserForLogin(email, false); error != nil {
c.Err = error
return
} else {
@@ -2403,13 +2058,13 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) {
c.LogAudit("attempt")
if activate {
- if err := ActivateMfa(c.Session.UserId, token); err != nil {
+ if err := app.ActivateMfa(c.Session.UserId, token); err != nil {
c.Err = err
return
}
c.LogAudit("success - activated")
} else {
- if err := DeactivateMfa(c.Session.UserId); err != nil {
+ if err := app.DeactivateMfa(c.Session.UserId); err != nil {
c.Err = err
return
}
@@ -2432,47 +2087,6 @@ func updateMfa(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(rdata)))
}
-func ActivateMfa(userId, token string) *model.AppError {
- mfaInterface := einterfaces.GetMfaInterface()
- if mfaInterface == nil {
- err := model.NewLocAppError("ActivateMfa", "api.user.update_mfa.not_available.app_error", nil, "")
- err.StatusCode = http.StatusNotImplemented
- return err
- }
-
- var user *model.User
- if result := <-app.Srv.Store.User().Get(userId); result.Err != nil {
- return result.Err
- } else {
- user = result.Data.(*model.User)
- }
-
- if len(user.AuthService) > 0 && user.AuthService != model.USER_AUTH_SERVICE_LDAP {
- return model.NewLocAppError("ActivateMfa", "api.user.activate_mfa.email_and_ldap_only.app_error", nil, "")
- }
-
- if err := mfaInterface.Activate(user, token); err != nil {
- return err
- }
-
- return nil
-}
-
-func DeactivateMfa(userId string) *model.AppError {
- mfaInterface := einterfaces.GetMfaInterface()
- if mfaInterface == nil {
- err := model.NewLocAppError("DeactivateMfa", "api.user.update_mfa.not_available.app_error", nil, "")
- err.StatusCode = http.StatusNotImplemented
- return err
- }
-
- if err := mfaInterface.Deactivate(userId); err != nil {
- return err
- }
-
- return nil
-}
-
func checkMfa(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.IsLicensed || !*utils.License.Features.MFA || !*utils.Cfg.ServiceSettings.EnableMultifactorAuthentication {
rdata := map[string]string{}
@@ -2592,7 +2206,7 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
case model.OAUTH_ACTION_SIGNUP:
teamId := relayProps["team_id"]
if len(teamId) > 0 {
- go addDirectChannels(teamId, user)
+ go app.AddDirectChannels(teamId, user)
}
break
case model.OAUTH_ACTION_EMAIL_TO_SSO:
diff --git a/api/user_test.go b/api/user_test.go
index fa7f28e6b..3163de078 100644
--- a/api/user_test.go
+++ b/api/user_test.go
@@ -107,25 +107,6 @@ func TestCheckUserDomain(t *testing.T) {
}
}
-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()
@@ -227,7 +208,10 @@ func TestLogin(t *testing.T) {
data := model.MapToJson(props)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt))
- ruser2, _ := Client.CreateUserFromSignup(&user2, data, hash)
+ ruser2, err := Client.CreateUserFromSignup(&user2, data, hash)
+ if err != nil {
+ t.Fatal(err)
+ }
if _, err := Client.Login(ruser2.Data.(*model.User).Email, user2.Password); err != nil {
t.Fatal("From verfied hash")
@@ -686,7 +670,7 @@ func TestUserCreateImage(t *testing.T) {
th := Setup()
Client := th.CreateClient()
- b, err := createProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba")
+ b, err := app.CreateProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba")
if err != nil {
t.Fatal(err)
}
diff --git a/api/webrtc.go b/api/webrtc.go
index 7f30ffef9..15b7cfa4f 100644
--- a/api/webrtc.go
+++ b/api/webrtc.go
@@ -115,22 +115,3 @@ func closeBody(r *http.Response) {
r.Body.Close()
}
}
-
-func RevokeWebrtcToken(sessionId string) {
- token := base64.StdEncoding.EncodeToString([]byte(sessionId))
- data := make(map[string]string)
- data["janus"] = "remove_token"
- data["token"] = token
- data["transaction"] = model.NewId()
- data["admin_secret"] = *utils.Cfg.WebrtcSettings.GatewayAdminSecret
-
- rq, _ := http.NewRequest("POST", *utils.Cfg.WebrtcSettings.GatewayAdminUrl, strings.NewReader(model.MapToJson(data)))
- rq.Header.Set("Content-Type", "application/json")
-
- // we do not care about the response
- tr := &http.Transport{
- TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
- }
- httpClient := &http.Client{Transport: tr}
- httpClient.Do(rq)
-}
diff --git a/api/webrtc_test.go b/api/webrtc_test.go
index 953333b09..21049d95d 100644
--- a/api/webrtc_test.go
+++ b/api/webrtc_test.go
@@ -5,6 +5,7 @@ package api
import (
"fmt"
+ "github.com/mattermost/platform/app"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
"testing"
@@ -39,5 +40,5 @@ func TestWebrtcToken(t *testing.T) {
fmt.Println("Turn Password", result["turn_password"])
}
- RevokeWebrtcToken(sessionId)
+ app.RevokeWebrtcToken(sessionId)
}
diff --git a/app/audit.go b/app/audit.go
new file mode 100644
index 000000000..6978e9bc2
--- /dev/null
+++ b/app/audit.go
@@ -0,0 +1,16 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+)
+
+func GetAudits(userId string, limit int) (model.Audits, *model.AppError) {
+ if result := <-Srv.Store.Audit().Get(userId, limit); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(model.Audits), nil
+ }
+}
diff --git a/app/channel.go b/app/channel.go
index 1771c856b..9451ca974 100644
--- a/app/channel.go
+++ b/app/channel.go
@@ -163,6 +163,33 @@ func CreateChannel(channel *model.Channel, addMember bool) (*model.Channel, *mod
}
}
+func CreateDirectChannel(userId string, otherUserId string) (*model.Channel, *model.AppError) {
+ uc := Srv.Store.User().Get(otherUserId)
+
+ if uresult := <-uc; uresult.Err != nil {
+ return nil, model.NewLocAppError("CreateDirectChannel", "api.channel.create_direct_channel.invalid_user.app_error", nil, otherUserId)
+ }
+
+ if result := <-Srv.Store.Channel().CreateDirectChannel(userId, otherUserId); result.Err != nil {
+ if result.Err.Id == store.CHANNEL_EXISTS_ERROR {
+ return result.Data.(*model.Channel), nil
+ } else {
+ return nil, result.Err
+ }
+ } else {
+ channel := result.Data.(*model.Channel)
+
+ InvalidateCacheForUser(userId)
+ InvalidateCacheForUser(otherUserId)
+
+ message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_DIRECT_ADDED, "", channel.Id, "", nil)
+ message.Add("teammate_id", otherUserId)
+ Publish(message)
+
+ return channel, nil
+ }
+}
+
func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelMember, *model.AppError) {
if channel.DeleteAt > 0 {
return nil, model.NewLocAppError("AddUserToChannel", "api.channel.add_user_to_channel.deleted.app_error", nil, "")
@@ -210,7 +237,165 @@ func AddUserToChannel(user *model.User, channel *model.Channel) (*model.ChannelM
message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_USER_ADDED, "", channel.Id, "", nil)
message.Add("user_id", user.Id)
message.Add("team_id", channel.TeamId)
- go Publish(message)
+ Publish(message)
return newMember, nil
}
+
+func AddDirectChannels(teamId string, user *model.User) *model.AppError {
+ var profiles map[string]*model.User
+ if result := <-Srv.Store.User().GetProfiles(teamId, 0, 100); result.Err != nil {
+ return model.NewLocAppError("AddDirectChannels", "api.user.add_direct_channels_and_forget.failed.error", map[string]interface{}{"UserId": user.Id, "TeamId": teamId, "Error": result.Err.Error()}, "")
+ } else {
+ profiles = result.Data.(map[string]*model.User)
+ }
+
+ var preferences model.Preferences
+
+ for id := range profiles {
+ if id == user.Id {
+ continue
+ }
+
+ profile := profiles[id]
+
+ preference := model.Preference{
+ UserId: user.Id,
+ Category: model.PREFERENCE_CATEGORY_DIRECT_CHANNEL_SHOW,
+ Name: profile.Id,
+ Value: "true",
+ }
+
+ preferences = append(preferences, preference)
+
+ if len(preferences) >= 10 {
+ break
+ }
+ }
+
+ if result := <-Srv.Store.Preference().Save(&preferences); result.Err != nil {
+ return model.NewLocAppError("AddDirectChannels", "api.user.add_direct_channels_and_forget.failed.error", map[string]interface{}{"UserId": user.Id, "TeamId": teamId, "Error": result.Err.Error()}, "")
+ }
+
+ return nil
+}
+
+func PostUpdateChannelHeaderMessage(userId string, channelId string, teamId string, oldChannelHeader, newChannelHeader string) *model.AppError {
+ uc := Srv.Store.User().Get(userId)
+
+ if uresult := <-uc; uresult.Err != nil {
+ return model.NewLocAppError("PostUpdateChannelHeaderMessage", "api.channel.post_update_channel_header_message_and_forget.retrieve_user.error", nil, uresult.Err.Error())
+ } else {
+ user := uresult.Data.(*model.User)
+
+ var message string
+ if oldChannelHeader == "" {
+ message = fmt.Sprintf(utils.T("api.channel.post_update_channel_header_message_and_forget.updated_to"), user.Username, newChannelHeader)
+ } else if newChannelHeader == "" {
+ message = fmt.Sprintf(utils.T("api.channel.post_update_channel_header_message_and_forget.removed"), user.Username, oldChannelHeader)
+ } else {
+ message = fmt.Sprintf(utils.T("api.channel.post_update_channel_header_message_and_forget.updated_from"), user.Username, oldChannelHeader, newChannelHeader)
+ }
+
+ post := &model.Post{
+ ChannelId: channelId,
+ Message: message,
+ Type: model.POST_HEADER_CHANGE,
+ UserId: userId,
+ Props: model.StringInterface{
+ "old_header": oldChannelHeader,
+ "new_header": newChannelHeader,
+ },
+ }
+
+ if _, err := CreatePost(post, teamId, false); err != nil {
+ return model.NewLocAppError("", "api.channel.post_update_channel_header_message_and_forget.post.error", nil, err.Error())
+ }
+ }
+
+ return nil
+}
+
+func PostUpdateChannelDisplayNameMessage(userId string, channelId string, teamId string, oldChannelDisplayName, newChannelDisplayName string) *model.AppError {
+ uc := Srv.Store.User().Get(userId)
+
+ if uresult := <-uc; uresult.Err != nil {
+ return model.NewLocAppError("PostUpdateChannelDisplayNameMessage", "api.channel.post_update_channel_displayname_message_and_forget.retrieve_user.error", nil, uresult.Err.Error())
+ } else {
+ user := uresult.Data.(*model.User)
+
+ message := fmt.Sprintf(utils.T("api.channel.post_update_channel_displayname_message_and_forget.updated_from"), user.Username, oldChannelDisplayName, newChannelDisplayName)
+
+ post := &model.Post{
+ ChannelId: channelId,
+ Message: message,
+ Type: model.POST_DISPLAYNAME_CHANGE,
+ UserId: userId,
+ Props: model.StringInterface{
+ "old_displayname": oldChannelDisplayName,
+ "new_displayname": newChannelDisplayName,
+ },
+ }
+
+ if _, err := CreatePost(post, teamId, false); err != nil {
+ return model.NewLocAppError("PostUpdateChannelDisplayNameMessage", "api.channel.post_update_channel_displayname_message_and_forget.create_post.error", nil, err.Error())
+ }
+ }
+
+ return nil
+}
+
+func GetChannel(channelId string) (*model.Channel, *model.AppError) {
+ if result := <-Srv.Store.Channel().Get(channelId, true); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.Channel), nil
+ }
+}
+
+func GetChannelByName(channelName, teamId string) (*model.Channel, *model.AppError) {
+ if result := <-Srv.Store.Channel().GetByName(teamId, channelName); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.Channel), nil
+ }
+}
+
+func JoinChannel(channel *model.Channel, userId string) *model.AppError {
+ userChan := Srv.Store.User().Get(userId)
+ memberChan := Srv.Store.Channel().GetMember(channel.Id, userId)
+
+ if uresult := <-userChan; uresult.Err != nil {
+ return uresult.Err
+ } else if mresult := <-memberChan; mresult.Err == nil && mresult.Data != nil {
+ // user is already in the channel
+ return nil
+ } else {
+ user := uresult.Data.(*model.User)
+
+ if channel.Type == model.CHANNEL_OPEN {
+ if _, err := AddUserToChannel(user, channel); err != nil {
+ return err
+ }
+ PostUserAddRemoveMessage(userId, channel.Id, channel.TeamId, fmt.Sprintf(utils.T("api.channel.join_channel.post_and_forget"), user.Username), model.POST_JOIN_LEAVE)
+ } else {
+ return model.NewLocAppError("JoinChannel", "api.channel.join_channel.permissions.app_error", nil, "")
+ }
+ }
+
+ return nil
+}
+
+func PostUserAddRemoveMessage(userId, channelId, teamId, message, postType string) *model.AppError {
+ post := &model.Post{
+ ChannelId: channelId,
+ Message: message,
+ Type: postType,
+ UserId: userId,
+ }
+ if _, err := CreatePost(post, teamId, false); err != nil {
+ return model.NewLocAppError("PostUserAddRemoveMessage", "api.channel.post_user_add_remove_message_and_forget.error", nil, err.Error())
+ }
+
+ return nil
+}
diff --git a/app/file.go b/app/file.go
new file mode 100644
index 000000000..93a286a14
--- /dev/null
+++ b/app/file.go
@@ -0,0 +1,340 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "bytes"
+ "crypto/sha256"
+ "encoding/base64"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "net/url"
+ "os"
+ "path"
+ "path/filepath"
+ "strings"
+ "sync"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+
+ s3 "github.com/minio/minio-go"
+)
+
+func ReadFile(path string) ([]byte, *model.AppError) {
+ if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
+ endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint
+ accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId
+ secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey
+ secure := *utils.Cfg.FileSettings.AmazonS3SSL
+ s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure)
+ if err != nil {
+ return nil, model.NewLocAppError("ReadFile", "api.file.read_file.s3.app_error", nil, err.Error())
+ }
+ bucket := utils.Cfg.FileSettings.AmazonS3Bucket
+ minioObject, err := s3Clnt.GetObject(bucket, path)
+ defer minioObject.Close()
+ if err != nil {
+ return nil, model.NewLocAppError("ReadFile", "api.file.read_file.s3.app_error", nil, err.Error())
+ }
+ if f, err := ioutil.ReadAll(minioObject); err != nil {
+ return nil, model.NewLocAppError("ReadFile", "api.file.read_file.s3.app_error", nil, err.Error())
+ } else {
+ return f, nil
+ }
+ } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
+ if f, err := ioutil.ReadFile(utils.Cfg.FileSettings.Directory + path); err != nil {
+ return nil, model.NewLocAppError("ReadFile", "api.file.read_file.reading_local.app_error", nil, err.Error())
+ } else {
+ return f, nil
+ }
+ } else {
+ return nil, model.NewLocAppError("ReadFile", "api.file.read_file.configured.app_error", nil, "")
+ }
+}
+
+func MoveFile(oldPath, newPath string) *model.AppError {
+ if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
+ endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint
+ accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId
+ secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey
+ secure := *utils.Cfg.FileSettings.AmazonS3SSL
+ s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure)
+ if err != nil {
+ return model.NewLocAppError("moveFile", "api.file.write_file.s3.app_error", nil, err.Error())
+ }
+ bucket := utils.Cfg.FileSettings.AmazonS3Bucket
+
+ var copyConds = s3.NewCopyConditions()
+ if err = s3Clnt.CopyObject(bucket, newPath, "/"+path.Join(bucket, oldPath), copyConds); err != nil {
+ return model.NewLocAppError("moveFile", "api.file.move_file.delete_from_s3.app_error", nil, err.Error())
+ }
+ if err = s3Clnt.RemoveObject(bucket, oldPath); err != nil {
+ return model.NewLocAppError("moveFile", "api.file.move_file.delete_from_s3.app_error", nil, err.Error())
+ }
+ } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
+ if err := os.MkdirAll(filepath.Dir(utils.Cfg.FileSettings.Directory+newPath), 0774); err != nil {
+ return model.NewLocAppError("moveFile", "api.file.move_file.rename.app_error", nil, err.Error())
+ }
+
+ if err := os.Rename(utils.Cfg.FileSettings.Directory+oldPath, utils.Cfg.FileSettings.Directory+newPath); err != nil {
+ return model.NewLocAppError("moveFile", "api.file.move_file.rename.app_error", nil, err.Error())
+ }
+ } else {
+ return model.NewLocAppError("moveFile", "api.file.move_file.configured.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func WriteFile(f []byte, path string) *model.AppError {
+ if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
+ endpoint := utils.Cfg.FileSettings.AmazonS3Endpoint
+ accessKey := utils.Cfg.FileSettings.AmazonS3AccessKeyId
+ secretKey := utils.Cfg.FileSettings.AmazonS3SecretAccessKey
+ secure := *utils.Cfg.FileSettings.AmazonS3SSL
+ s3Clnt, err := s3.New(endpoint, accessKey, secretKey, secure)
+ if err != nil {
+ return model.NewLocAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error())
+ }
+ bucket := utils.Cfg.FileSettings.AmazonS3Bucket
+ ext := filepath.Ext(path)
+
+ if model.IsFileExtImage(ext) {
+ _, err = s3Clnt.PutObject(bucket, path, bytes.NewReader(f), model.GetImageMimeType(ext))
+ } else {
+ _, err = s3Clnt.PutObject(bucket, path, bytes.NewReader(f), "binary/octet-stream")
+ }
+ if err != nil {
+ return model.NewLocAppError("WriteFile", "api.file.write_file.s3.app_error", nil, err.Error())
+ }
+ } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
+ if err := writeFileLocally(f, utils.Cfg.FileSettings.Directory+path); err != nil {
+ return err
+ }
+ } else {
+ return model.NewLocAppError("WriteFile", "api.file.write_file.configured.app_error", nil, "")
+ }
+
+ return nil
+}
+
+func writeFileLocally(f []byte, path string) *model.AppError {
+ if err := os.MkdirAll(filepath.Dir(path), 0774); err != nil {
+ directory, _ := filepath.Abs(filepath.Dir(path))
+ return model.NewLocAppError("WriteFile", "api.file.write_file_locally.create_dir.app_error", nil, "directory="+directory+", err="+err.Error())
+ }
+
+ if err := ioutil.WriteFile(path, f, 0644); err != nil {
+ return model.NewLocAppError("WriteFile", "api.file.write_file_locally.writing.app_error", nil, err.Error())
+ }
+
+ return nil
+}
+
+func openFileWriteStream(path string) (io.Writer, *model.AppError) {
+ if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_S3 {
+ return nil, model.NewLocAppError("openFileWriteStream", "api.file.open_file_write_stream.s3.app_error", nil, "")
+ } else if utils.Cfg.FileSettings.DriverName == model.IMAGE_DRIVER_LOCAL {
+ if err := os.MkdirAll(filepath.Dir(utils.Cfg.FileSettings.Directory+path), 0774); err != nil {
+ return nil, model.NewLocAppError("openFileWriteStream", "api.file.open_file_write_stream.creating_dir.app_error", nil, err.Error())
+ }
+
+ if fileHandle, err := os.Create(utils.Cfg.FileSettings.Directory + path); err != nil {
+ return nil, model.NewLocAppError("openFileWriteStream", "api.file.open_file_write_stream.local_server.app_error", nil, err.Error())
+ } else {
+ fileHandle.Chmod(0644)
+ return fileHandle, nil
+ }
+ }
+
+ return nil, model.NewLocAppError("openFileWriteStream", "api.file.open_file_write_stream.configured.app_error", nil, "")
+}
+
+func closeFileWriteStream(file io.Writer) {
+ file.(*os.File).Close()
+}
+
+func GetInfoForFilename(post *model.Post, teamId string, filename string) *model.FileInfo {
+ // Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension}
+ split := strings.SplitN(filename, "/", 5)
+ if len(split) < 5 {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.unexpected_filename.error"), post.Id, filename)
+ return nil
+ }
+
+ channelId := split[1]
+ userId := split[2]
+ oldId := split[3]
+ name, _ := url.QueryUnescape(split[4])
+
+ if split[0] != "" || split[1] != post.ChannelId || split[2] != post.UserId || strings.Contains(split[4], "/") {
+ l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.mismatched_filename.warn"), post.Id, post.ChannelId, post.UserId, filename)
+ }
+
+ pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamId, channelId, userId, oldId)
+ path := pathPrefix + name
+
+ // Open the file and populate the fields of the FileInfo
+ var info *model.FileInfo
+ if data, err := ReadFile(path); err != nil {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.file_not_found.error"), post.Id, filename, path, err)
+ return nil
+ } else {
+ var err *model.AppError
+ info, err = model.GetInfoForBytes(name, data)
+ if err != nil {
+ l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.info.app_error"), post.Id, filename, err)
+ }
+ }
+
+ // Generate a new ID because with the old system, you could very rarely get multiple posts referencing the same file
+ info.Id = model.NewId()
+ info.CreatorId = post.UserId
+ info.PostId = post.Id
+ info.CreateAt = post.CreateAt
+ info.UpdateAt = post.UpdateAt
+ info.Path = path
+
+ if info.IsImage() {
+ nameWithoutExtension := name[:strings.LastIndex(name, ".")]
+ info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview.jpg"
+ info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb.jpg"
+ }
+
+ return info
+}
+
+func FindTeamIdForFilename(post *model.Post, filename string) string {
+ split := strings.SplitN(filename, "/", 5)
+ id := split[3]
+ name, _ := url.QueryUnescape(split[4])
+
+ // This post is in a direct channel so we need to figure out what team the files are stored under.
+ if result := <-Srv.Store.Team().GetTeamsByUserId(post.UserId); result.Err != nil {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.teams.app_error"), post.Id, result.Err)
+ } else if teams := result.Data.([]*model.Team); len(teams) == 1 {
+ // The user has only one team so the post must've been sent from it
+ return teams[0].Id
+ } else {
+ for _, team := range teams {
+ path := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", team.Id, post.ChannelId, post.UserId, id, name)
+ if _, err := ReadFile(path); err == nil {
+ // Found the team that this file was posted from
+ return team.Id
+ }
+ }
+ }
+
+ return ""
+}
+
+var fileMigrationLock sync.Mutex
+
+// Creates and stores FileInfos for a post created before the FileInfos table existed.
+func MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
+ if len(post.Filenames) == 0 {
+ l4g.Warn(utils.T("api.file.migrate_filenames_to_file_infos.no_filenames.warn"), post.Id)
+ return []*model.FileInfo{}
+ }
+
+ cchan := Srv.Store.Channel().Get(post.ChannelId, true)
+
+ // There's a weird bug that rarely happens where a post ends up with duplicate Filenames so remove those
+ filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames)
+
+ var channel *model.Channel
+ if result := <-cchan; result.Err != nil {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.channel.app_error"), post.Id, post.ChannelId, result.Err)
+ return []*model.FileInfo{}
+ } else {
+ channel = result.Data.(*model.Channel)
+ }
+
+ // Find the team that was used to make this post since its part of the file path that isn't saved in the Filename
+ var teamId string
+ if channel.TeamId == "" {
+ // This post was made in a cross-team DM channel so we need to find where its files were saved
+ teamId = FindTeamIdForFilename(post, filenames[0])
+ } else {
+ teamId = channel.TeamId
+ }
+
+ // Create FileInfo objects for this post
+ infos := make([]*model.FileInfo, 0, len(filenames))
+ if teamId == "" {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.team_id.error"), post.Id, filenames)
+ } else {
+ for _, filename := range filenames {
+ info := GetInfoForFilename(post, teamId, filename)
+ if info == nil {
+ continue
+ }
+
+ infos = append(infos, info)
+ }
+ }
+
+ // Lock to prevent only one migration thread from trying to update the post at once, preventing duplicate FileInfos from being created
+ fileMigrationLock.Lock()
+ defer fileMigrationLock.Unlock()
+
+ if result := <-Srv.Store.Post().Get(post.Id); result.Err != nil {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_again.app_error"), post.Id, result.Err)
+ return []*model.FileInfo{}
+ } else if newPost := result.Data.(*model.PostList).Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) {
+ // Another thread has already created FileInfos for this post, so just return those
+ if result := <-Srv.Store.FileInfo().GetForPost(post.Id); result.Err != nil {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.get_post_file_infos_again.app_error"), post.Id, result.Err)
+ return []*model.FileInfo{}
+ } else {
+ l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.not_migrating_post.debug"), post.Id)
+ return result.Data.([]*model.FileInfo)
+ }
+ }
+
+ l4g.Debug(utils.T("api.file.migrate_filenames_to_file_infos.migrating_post.debug"), post.Id)
+
+ savedInfos := make([]*model.FileInfo, 0, len(infos))
+ fileIds := make([]string, 0, len(filenames))
+ for _, info := range infos {
+ if result := <-Srv.Store.FileInfo().Save(info); result.Err != nil {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_file_info.app_error"), post.Id, info.Id, info.Path, result.Err)
+ continue
+ }
+
+ savedInfos = append(savedInfos, info)
+ fileIds = append(fileIds, info.Id)
+ }
+
+ // Copy and save the updated post
+ newPost := &model.Post{}
+ *newPost = *post
+
+ newPost.Filenames = []string{}
+ newPost.FileIds = fileIds
+
+ // Update Posts to clear Filenames and set FileIds
+ if result := <-Srv.Store.Post().Update(newPost, post); result.Err != nil {
+ l4g.Error(utils.T("api.file.migrate_filenames_to_file_infos.save_post.app_error"), post.Id, newPost.FileIds, post.Filenames, result.Err)
+ return []*model.FileInfo{}
+ } else {
+ return savedInfos
+ }
+}
+
+func GeneratePublicLink(siteURL string, info *model.FileInfo) string {
+ hash := GeneratePublicLinkHash(info.Id, *utils.Cfg.FileSettings.PublicLinkSalt)
+ return fmt.Sprintf("%s%s/public/files/%v/get?h=%s", siteURL, model.API_URL_SUFFIX, info.Id, hash)
+}
+
+func GeneratePublicLinkHash(fileId, salt string) string {
+ hash := sha256.New()
+ hash.Write([]byte(salt))
+ hash.Write([]byte(fileId))
+
+ return base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
+}
diff --git a/app/file_test.go b/app/file_test.go
new file mode 100644
index 000000000..9df03315e
--- /dev/null
+++ b/app/file_test.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "testing"
+
+ "github.com/mattermost/platform/model"
+)
+
+func TestGeneratePublicLinkHash(t *testing.T) {
+ filename1 := model.NewId() + "/" + model.NewRandomString(16) + ".txt"
+ filename2 := model.NewId() + "/" + model.NewRandomString(16) + ".txt"
+ salt1 := model.NewRandomString(32)
+ salt2 := model.NewRandomString(32)
+
+ hash1 := GeneratePublicLinkHash(filename1, salt1)
+ hash2 := GeneratePublicLinkHash(filename2, salt1)
+ hash3 := GeneratePublicLinkHash(filename1, salt2)
+
+ if hash1 != GeneratePublicLinkHash(filename1, salt1) {
+ t.Fatal("hash should be equal for the same file name and salt")
+ }
+
+ if hash1 == hash2 {
+ t.Fatal("hashes for different files should not be equal")
+ }
+
+ if hash1 == hash3 {
+ t.Fatal("hashes for the same file with different salts should not be equal")
+ }
+}
diff --git a/app/notification.go b/app/notification.go
index d5e3c7b13..fc1d44f06 100644
--- a/app/notification.go
+++ b/app/notification.go
@@ -25,192 +25,189 @@ import (
)
func SendNotifications(post *model.Post, team *model.Team, channel *model.Channel) ([]string, *model.AppError) {
- mentionedUsersList := make([]string, 0)
- var fchan store.StoreChannel
- var senderUsername string
+ pchan := Srv.Store.User().GetProfilesInChannel(channel.Id, -1, -1, true)
+ fchan := Srv.Store.FileInfo().GetForPost(post.Id)
- if post.IsSystemMessage() {
- senderUsername = utils.T("system.message.name")
+ var profileMap map[string]*model.User
+ if result := <-pchan; result.Err != nil {
+ return nil, result.Err
} else {
- pchan := Srv.Store.User().GetProfilesInChannel(channel.Id, -1, -1, true)
- fchan = Srv.Store.FileInfo().GetForPost(post.Id)
+ profileMap = result.Data.(map[string]*model.User)
+ }
- var profileMap map[string]*model.User
- if result := <-pchan; result.Err != nil {
- return nil, result.Err
+ // If the user who made the post isn't in the channel, don't send a notification
+ if _, ok := profileMap[post.UserId]; !ok {
+ l4g.Debug(utils.T("api.post.send_notifications.user_id.debug"), post.Id, channel.Id, post.UserId)
+ return []string{}, nil
+ }
+
+ mentionedUserIds := make(map[string]bool)
+ allActivityPushUserIds := []string{}
+ hereNotification := false
+ channelNotification := false
+ allNotification := false
+ updateMentionChans := []store.StoreChannel{}
+
+ if channel.Type == model.CHANNEL_DIRECT {
+ var otherUserId string
+ if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId {
+ otherUserId = userIds[1]
} else {
- profileMap = result.Data.(map[string]*model.User)
+ otherUserId = userIds[0]
}
- // If the user who made the post isn't in the channel don't send a notification
- if _, ok := profileMap[post.UserId]; !ok {
- l4g.Debug(utils.T("api.post.send_notifications.user_id.debug"), post.Id, channel.Id, post.UserId)
- return []string{}, nil
+ mentionedUserIds[otherUserId] = true
+ if post.Props["from_webhook"] == "true" {
+ mentionedUserIds[post.UserId] = true
}
+ } else {
+ keywords := GetMentionKeywordsInChannel(profileMap)
- mentionedUserIds := make(map[string]bool)
- allActivityPushUserIds := []string{}
- hereNotification := false
- channelNotification := false
- allNotification := false
- updateMentionChans := []store.StoreChannel{}
+ var potentialOtherMentions []string
+ mentionedUserIds, potentialOtherMentions, hereNotification, channelNotification, allNotification = GetExplicitMentions(post.Message, keywords)
- if channel.Type == model.CHANNEL_DIRECT {
- var otherUserId string
- if userIds := strings.Split(channel.Name, "__"); userIds[0] == post.UserId {
- otherUserId = userIds[1]
+ // get users that have comment thread mentions enabled
+ if len(post.RootId) > 0 {
+ if result := <-Srv.Store.Post().Get(post.RootId); result.Err != nil {
+ return nil, result.Err
} else {
- otherUserId = userIds[0]
- }
-
- mentionedUserIds[otherUserId] = true
- if post.Props["from_webhook"] == "true" {
- mentionedUserIds[post.UserId] = true
- }
- } else {
- keywords := GetMentionKeywordsInChannel(profileMap)
-
- var potentialOtherMentions []string
- mentionedUserIds, potentialOtherMentions, hereNotification, channelNotification, allNotification = GetExplicitMentions(post.Message, keywords)
+ list := result.Data.(*model.PostList)
- // get users that have comment thread mentions enabled
- if len(post.RootId) > 0 {
- if result := <-Srv.Store.Post().Get(post.RootId); result.Err != nil {
- return nil, result.Err
- } else {
- list := result.Data.(*model.PostList)
-
- for _, threadPost := range list.Posts {
- if profile, ok := profileMap[threadPost.UserId]; ok {
- if profile.NotifyProps["comments"] == "any" || (profile.NotifyProps["comments"] == "root" && threadPost.Id == list.Order[0]) {
- mentionedUserIds[threadPost.UserId] = true
- }
- }
+ for _, threadPost := range list.Posts {
+ profile := profileMap[threadPost.UserId]
+ if profile.NotifyProps["comments"] == "any" || (profile.NotifyProps["comments"] == "root" && threadPost.Id == list.Order[0]) {
+ mentionedUserIds[threadPost.UserId] = true
}
}
}
+ }
- // prevent the user from mentioning themselves
- if post.Props["from_webhook"] != "true" {
- delete(mentionedUserIds, post.UserId)
- }
+ // prevent the user from mentioning themselves
+ if post.Props["from_webhook"] != "true" {
+ delete(mentionedUserIds, post.UserId)
+ }
- if len(potentialOtherMentions) > 0 {
- if result := <-Srv.Store.User().GetProfilesByUsernames(potentialOtherMentions, team.Id); result.Err == nil {
- outOfChannelMentions := result.Data.(map[string]*model.User)
- go sendOutOfChannelMentions(post, team.Id, outOfChannelMentions)
- }
+ if len(potentialOtherMentions) > 0 {
+ if result := <-Srv.Store.User().GetProfilesByUsernames(potentialOtherMentions, team.Id); result.Err == nil {
+ outOfChannelMentions := result.Data.(map[string]*model.User)
+ go sendOutOfChannelMentions(post, team.Id, outOfChannelMentions)
}
+ }
- // find which users in the channel are set up to always receive mobile notifications
- for _, profile := range profileMap {
- if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL &&
- (post.UserId != profile.Id || post.Props["from_webhook"] == "true") {
- allActivityPushUserIds = append(allActivityPushUserIds, profile.Id)
- }
+ // find which users in the channel are set up to always receive mobile notifications
+ for _, profile := range profileMap {
+ if profile.NotifyProps["push"] == model.USER_NOTIFY_ALL &&
+ (post.UserId != profile.Id || post.Props["from_webhook"] == "true") &&
+ !post.IsSystemMessage() {
+ allActivityPushUserIds = append(allActivityPushUserIds, profile.Id)
}
}
+ }
- mentionedUsersList = make([]string, 0, len(mentionedUserIds))
- for id := range mentionedUserIds {
- mentionedUsersList = append(mentionedUsersList, id)
- updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, id))
- }
+ mentionedUsersList := make([]string, 0, len(mentionedUserIds))
+ for id := range mentionedUserIds {
+ mentionedUsersList = append(mentionedUsersList, id)
+ updateMentionChans = append(updateMentionChans, Srv.Store.Channel().IncrementMentionCount(post.ChannelId, id))
+ }
- var sender *model.User
- senderName := make(map[string]string)
- for _, id := range mentionedUsersList {
- senderName[id] = ""
- if profile, ok := profileMap[post.UserId]; ok {
- if value, ok := post.Props["override_username"]; ok && post.Props["from_webhook"] == "true" {
- senderName[id] = value.(string)
+ var sender *model.User
+ senderName := make(map[string]string)
+ for _, id := range mentionedUsersList {
+ senderName[id] = ""
+ if post.IsSystemMessage() {
+ senderName[id] = utils.T("system.message.name")
+ } else if profile, ok := profileMap[post.UserId]; ok {
+ if value, ok := post.Props["override_username"]; ok && post.Props["from_webhook"] == "true" {
+ senderName[id] = value.(string)
+ } else {
+ // Get the Display name preference from the receiver
+ if result := <-Srv.Store.Preference().Get(id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "name_format"); result.Err != nil {
+ // Show default sender's name if user doesn't set display settings.
+ senderName[id] = profile.Username
} else {
- //Get the Display name preference from the receiver
- if result := <-Srv.Store.Preference().Get(id, model.PREFERENCE_CATEGORY_DISPLAY_SETTINGS, "name_format"); result.Err != nil {
- // Show default sender's name if user doesn't set display settings.
- senderName[id] = profile.Username
- } else {
- senderName[id] = profile.GetDisplayNameForPreference(result.Data.(model.Preference).Value)
- }
+ senderName[id] = profile.GetDisplayNameForPreference(result.Data.(model.Preference).Value)
}
- sender = profile
}
+ sender = profile
}
+ }
- if value, ok := post.Props["override_username"]; ok && post.Props["from_webhook"] == "true" {
- senderUsername = value.(string)
- } else {
- senderUsername = profileMap[post.UserId].Username
- }
-
- if utils.Cfg.EmailSettings.SendEmailNotifications {
- for _, id := range mentionedUsersList {
- userAllowsEmails := profileMap[id].NotifyProps["email"] != "false"
+ var senderUsername string
+ if value, ok := post.Props["override_username"]; ok && post.Props["from_webhook"] == "true" {
+ senderUsername = value.(string)
+ } else {
+ senderUsername = profileMap[post.UserId].Username
+ }
- var status *model.Status
- var err *model.AppError
- if status, err = GetStatus(id); err != nil {
- status = &model.Status{
- UserId: id,
- Status: model.STATUS_OFFLINE,
- Manual: false,
- LastActivityAt: 0,
- ActiveChannel: "",
- }
+ if utils.Cfg.EmailSettings.SendEmailNotifications {
+ for _, id := range mentionedUsersList {
+ userAllowsEmails := profileMap[id].NotifyProps["email"] != "false"
+
+ var status *model.Status
+ var err *model.AppError
+ if status, err = GetStatus(id); err != nil {
+ status = &model.Status{
+ UserId: id,
+ Status: model.STATUS_OFFLINE,
+ Manual: false,
+ LastActivityAt: 0,
+ ActiveChannel: "",
}
+ }
- if userAllowsEmails && status.Status != model.STATUS_ONLINE && profileMap[id].DeleteAt == 0 {
- if err := sendNotificationEmail(post, profileMap[id], channel, team, senderName[id], sender); err != nil {
- l4g.Error(err.Error())
- }
- }
+ if userAllowsEmails && status.Status != model.STATUS_ONLINE && profileMap[id].DeleteAt == 0 {
+ sendNotificationEmail(post, profileMap[id], channel, team, senderName[id], sender)
}
}
+ }
- T := utils.GetUserTranslations(profileMap[post.UserId].Locale)
-
- // If the channel has more than 1K users then @here is disabled
- if hereNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
- hereNotification = false
- SendEphemeralPost(
- team.Id,
- post.UserId,
- &model.Post{
- ChannelId: post.ChannelId,
- Message: 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(
- team.Id,
- post.UserId,
- &model.Post{
- ChannelId: post.ChannelId,
- Message: 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(
- team.Id,
- post.UserId,
- &model.Post{
- ChannelId: post.ChannelId,
- Message: T("api.post.disabled_all", map[string]interface{}{"Users": *utils.Cfg.TeamSettings.MaxNotificationsPerChannel}),
- CreateAt: post.CreateAt + 1,
- },
- )
- }
-
- if hereNotification {
- statuses := GetAllStatuses()
+ T := utils.GetUserTranslations(profileMap[post.UserId].Locale)
+
+ // If the channel has more than 1K users then @here is disabled
+ if hereNotification && int64(len(profileMap)) > *utils.Cfg.TeamSettings.MaxNotificationsPerChannel {
+ hereNotification = false
+ SendEphemeralPost(
+ team.Id,
+ post.UserId,
+ &model.Post{
+ ChannelId: post.ChannelId,
+ Message: 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(
+ team.Id,
+ post.UserId,
+ &model.Post{
+ ChannelId: post.ChannelId,
+ Message: 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(
+ team.Id,
+ post.UserId,
+ &model.Post{
+ ChannelId: post.ChannelId,
+ Message: 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 {
+ return nil, result.Err
+ } else {
+ statuses := result.Data.([]*model.Status)
for _, status := range statuses {
if status.UserId == post.UserId {
continue
@@ -225,29 +222,43 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe
}
}
}
+ }
- // Make sure all mention updates are complete to prevent race
- // Probably better to batch these DB updates in the future
- // MUST be completed before push notifications send
- for _, uchan := range updateMentionChans {
- if result := <-uchan; result.Err != nil {
- l4g.Warn(utils.T("api.post.update_mention_count_and_forget.update_error"), post.Id, post.ChannelId, result.Err)
- }
+ // Make sure all mention updates are complete to prevent race
+ // Probably better to batch these DB updates in the future
+ // MUST be completed before push notifications send
+ for _, uchan := range updateMentionChans {
+ if result := <-uchan; result.Err != nil {
+ l4g.Warn(utils.T("api.post.update_mention_count_and_forget.update_error"), post.Id, post.ChannelId, result.Err)
}
+ }
- sendPushNotifications := false
- if *utils.Cfg.EmailSettings.SendPushNotifications {
- pushServer := *utils.Cfg.EmailSettings.PushNotificationServer
- if pushServer == model.MHPNS && (!utils.IsLicensed || !*utils.License.Features.MHPNS) {
- l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn"))
- sendPushNotifications = false
- } else {
- sendPushNotifications = true
+ sendPushNotifications := false
+ if *utils.Cfg.EmailSettings.SendPushNotifications {
+ pushServer := *utils.Cfg.EmailSettings.PushNotificationServer
+ if pushServer == model.MHPNS && (!utils.IsLicensed || !*utils.License.Features.MHPNS) {
+ l4g.Warn(utils.T("api.post.send_notifications_and_forget.push_notification.mhpnsWarn"))
+ sendPushNotifications = false
+ } else {
+ sendPushNotifications = true
+ }
+ }
+
+ if sendPushNotifications {
+ for _, id := range mentionedUsersList {
+ var status *model.Status
+ var err *model.AppError
+ if status, err = GetStatus(id); err != nil {
+ status = &model.Status{id, model.STATUS_OFFLINE, false, 0, ""}
+ }
+
+ if DoesStatusAllowPushNotification(profileMap[id], status, post.ChannelId) {
+ sendPushNotification(post, profileMap[id], channel, senderName[id], true)
}
}
- if sendPushNotifications {
- for _, id := range mentionedUsersList {
+ for _, id := range allActivityPushUserIds {
+ if _, ok := mentionedUserIds[id]; !ok {
var status *model.Status
var err *model.AppError
if status, err = GetStatus(id); err != nil {
@@ -255,25 +266,7 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe
}
if DoesStatusAllowPushNotification(profileMap[id], status, post.ChannelId) {
- if err := sendPushNotification(post, profileMap[id], channel, senderName[id], true); err != nil {
- l4g.Error(err.Error())
- }
- }
- }
-
- for _, id := range allActivityPushUserIds {
- if _, ok := mentionedUserIds[id]; !ok {
- var status *model.Status
- var err *model.AppError
- if status, err = GetStatus(id); err != nil {
- status = &model.Status{id, model.STATUS_OFFLINE, false, 0, ""}
- }
-
- if DoesStatusAllowPushNotification(profileMap[id], status, post.ChannelId) {
- if err := sendPushNotification(post, profileMap[id], channel, senderName[id], false); err != nil {
- l4g.Error(err.Error())
- }
- }
+ sendPushNotification(post, profileMap[id], channel, senderName[id], false)
}
}
}
@@ -287,7 +280,7 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe
message.Add("sender_name", senderUsername)
message.Add("team_id", team.Id)
- if len(post.FileIds) != 0 && fchan != nil {
+ if len(post.FileIds) != 0 {
message.Add("otherFile", "true")
var infos []*model.FileInfo
@@ -314,7 +307,6 @@ func SendNotifications(post *model.Post, team *model.Team, channel *model.Channe
}
func sendNotificationEmail(post *model.Post, user *model.User, channel *model.Channel, team *model.Team, senderName string, sender *model.User) *model.AppError {
-
if channel.Type == model.CHANNEL_DIRECT && channel.TeamId != team.Id {
// this message is a cross-team DM so it we need to find a team that the recipient is on to use in the link
if result := <-Srv.Store.Team().GetTeamsByUserId(user.Id); result.Err != nil {
@@ -327,18 +319,15 @@ func sendNotificationEmail(post *model.Post, user *model.User, channel *model.Ch
for i := range teams {
if teams[i].Id == team.Id {
found = true
- team = teams[i]
break
}
}
- if !found {
- if 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}
- }
+ 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}
}
}
}
@@ -511,8 +500,9 @@ func sendPushNotification(post *model.Post, user *model.User, channel *model.Cha
tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson()))
tmpMessage.SetDeviceIdAndPlatform(session.DeviceId)
if err := sendToPushProxy(tmpMessage); err != nil {
- l4g.Error(err.Error)
+ return err
}
+
if einterfaces.GetMetricsInterface() != nil {
einterfaces.GetMetricsInterface().IncrementPostSentPush()
}
@@ -539,11 +529,12 @@ func ClearPushNotification(userId string, channelId string) *model.AppError {
}
l4g.Debug(utils.T("api.post.send_notifications_and_forget.clear_push_notification.debug"), msg.DeviceId, msg.ChannelId)
+
for _, session := range sessions {
tmpMessage := *model.PushNotificationFromJson(strings.NewReader(msg.ToJson()))
tmpMessage.SetDeviceIdAndPlatform(session.DeviceId)
if err := sendToPushProxy(tmpMessage); err != nil {
- l4g.Error(err.Error)
+ return err
}
}
@@ -551,6 +542,7 @@ func ClearPushNotification(userId string, channelId string) *model.AppError {
}
func sendToPushProxy(msg model.PushNotification) *model.AppError {
+
msg.ServerId = utils.CfgDiagnosticId
tr := &http.Transport{
diff --git a/app/oauth.go b/app/oauth.go
new file mode 100644
index 000000000..862897b24
--- /dev/null
+++ b/app/oauth.go
@@ -0,0 +1,34 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+)
+
+func RevokeAccessToken(token string) *model.AppError {
+
+ session, _ := GetSession(token)
+ schan := Srv.Store.Session().Remove(token)
+
+ if result := <-Srv.Store.OAuth().GetAccessData(token); result.Err != nil {
+ return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.get.app_error", nil, "")
+ }
+
+ tchan := Srv.Store.OAuth().RemoveAccessData(token)
+
+ if result := <-tchan; result.Err != nil {
+ return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_token.app_error", nil, "")
+ }
+
+ if result := <-schan; result.Err != nil {
+ return model.NewLocAppError("RevokeAccessToken", "api.oauth.revoke_access_token.del_session.app_error", nil, "")
+ }
+
+ if session != nil {
+ RemoveAllSessionsForUserId(session.UserId)
+ }
+
+ return nil
+}
diff --git a/app/preference.go b/app/preference.go
new file mode 100644
index 000000000..4e492c4a8
--- /dev/null
+++ b/app/preference.go
@@ -0,0 +1,16 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "github.com/mattermost/platform/model"
+)
+
+func GetPreferencesForUser(userId string) (model.Preferences, *model.AppError) {
+ if result := <-Srv.Store.Preference().GetAll(userId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(model.Preferences), nil
+ }
+}
diff --git a/app/session.go b/app/session.go
index 29c961e81..3bb167891 100644
--- a/app/session.go
+++ b/app/session.go
@@ -92,3 +92,55 @@ func InvalidateAllCaches() {
func SessionCacheLength() int {
return sessionCache.Len()
}
+
+func RevokeSessionsForDeviceId(userId string, deviceId string, currentSessionId string) *model.AppError {
+ if result := <-Srv.Store.Session().GetSessions(userId); result.Err != nil {
+ return result.Err
+ } else {
+ sessions := result.Data.([]*model.Session)
+ for _, session := range sessions {
+ if session.DeviceId == deviceId && session.Id != currentSessionId {
+ l4g.Debug(utils.T("api.user.login.revoking.app_error"), session.Id, userId)
+ if err := RevokeSession(session); err != nil {
+ // Soft error so we still remove the other sessions
+ l4g.Error(err.Error())
+ }
+ }
+ }
+ }
+
+ return nil
+}
+
+func RevokeSessionById(sessionId string) *model.AppError {
+ if result := <-Srv.Store.Session().Get(sessionId); result.Err != nil {
+ return result.Err
+ } else {
+ return RevokeSession(result.Data.(*model.Session))
+ }
+}
+
+func RevokeSession(session *model.Session) *model.AppError {
+ if session.IsOAuth {
+ if err := RevokeAccessToken(session.Token); err != nil {
+ return err
+ }
+ } else {
+ if result := <-Srv.Store.Session().Remove(session.Id); result.Err != nil {
+ return result.Err
+ }
+ }
+
+ RevokeWebrtcToken(session.Id)
+ RemoveAllSessionsForUserId(session.UserId)
+
+ return nil
+}
+
+func AttachDeviceId(sessionId string, deviceId string, expiresAt int64) *model.AppError {
+ if result := <-Srv.Store.Session().UpdateDeviceId(sessionId, deviceId, expiresAt); result.Err != nil {
+ return result.Err
+ }
+
+ return nil
+}
diff --git a/app/team.go b/app/team.go
index 98b6894a5..495e0773f 100644
--- a/app/team.go
+++ b/app/team.go
@@ -80,3 +80,11 @@ func JoinUserToTeam(team *model.Team, user *model.User) *model.AppError {
return nil
}
+
+func GetTeamsForUser(userId string) ([]*model.Team, *model.AppError) {
+ if result := <-Srv.Store.Team().GetTeamsByUserId(userId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.([]*model.Team), nil
+ }
+}
diff --git a/app/user.go b/app/user.go
index 5acd9dcaa..909c8cca9 100644
--- a/app/user.go
+++ b/app/user.go
@@ -4,11 +4,105 @@
package app
import (
+ "bytes"
+ "fmt"
+ "hash/fnv"
+ "image"
+ "image/color"
+ "image/draw"
+ _ "image/gif"
+ _ "image/jpeg"
+ "image/png"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "strconv"
+ "strings"
+
l4g "github.com/alecthomas/log4go"
+ "github.com/golang/freetype"
+ "github.com/mattermost/platform/einterfaces"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
+func CreateUserWithHash(user *model.User, hash string, data string) (*model.User, *model.AppError) {
+ props := model.MapFromJson(strings.NewReader(data))
+
+ if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.EmailSettings.InviteSalt)) {
+ return nil, model.NewLocAppError("CreateUserWithHash", "api.user.create_user.signup_link_invalid.app_error", nil, "")
+ }
+
+ if t, err := strconv.ParseInt(props["time"], 10, 64); err != nil || model.GetMillis()-t > 1000*60*60*48 { // 48 hours
+ return nil, model.NewLocAppError("CreateUserWithHash", "api.user.create_user.signup_link_expired.app_error", nil, "")
+ }
+
+ teamId := props["id"]
+
+ var team *model.Team
+ if result := <-Srv.Store.Team().Get(teamId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ team = result.Data.(*model.Team)
+ }
+
+ user.Email = props["email"]
+ user.EmailVerified = true
+
+ var ruser *model.User
+ var err *model.AppError
+ if ruser, err = CreateUser(user); err != nil {
+ return nil, err
+ }
+
+ if err := JoinUserToTeam(team, ruser); err != nil {
+ return nil, err
+ }
+
+ AddDirectChannels(team.Id, ruser)
+
+ return ruser, nil
+}
+
+func CreateUserWithInviteId(user *model.User, inviteId string) (*model.User, *model.AppError) {
+ var team *model.Team
+ if result := <-Srv.Store.Team().GetByInviteId(inviteId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ team = result.Data.(*model.Team)
+ }
+
+ var ruser *model.User
+ var err *model.AppError
+ if ruser, err = CreateUser(user); err != nil {
+ return nil, err
+ }
+
+ if err := JoinUserToTeam(team, ruser); err != nil {
+ return nil, err
+ }
+
+ AddDirectChannels(team.Id, ruser)
+
+ return ruser, nil
+}
+
+func IsFirstUserAccount() bool {
+ if SessionCacheLength() == 0 {
+ if cr := <-Srv.Store.User().GetTotalUsersCount(); cr.Err != nil {
+ l4g.Error(cr.Err)
+ return false
+ } else {
+ count := cr.Data.(int64)
+ if count <= 0 {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
func CreateUser(user *model.User) (*model.User, *model.AppError) {
user.Roles = model.ROLE_SYSTEM_USER.Id
@@ -58,3 +152,330 @@ func CreateUser(user *model.User) (*model.User, *model.AppError) {
return ruser, nil
}
}
+
+func CreateOAuthUser(service string, userData io.Reader, teamId string) (*model.User, *model.AppError) {
+ var user *model.User
+ provider := einterfaces.GetOauthProvider(service)
+ if provider == nil {
+ return nil, model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.not_available.app_error", map[string]interface{}{"Service": strings.Title(service)}, "")
+ } else {
+ user = provider.GetUserFromJson(userData)
+ }
+
+ if user == nil {
+ return nil, model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.create.app_error", map[string]interface{}{"Service": service}, "")
+ }
+
+ suchan := Srv.Store.User().GetByAuth(user.AuthData, service)
+ euchan := Srv.Store.User().GetByEmail(user.Email)
+
+ found := true
+ count := 0
+ for found {
+ if found = IsUsernameTaken(user.Username); found {
+ user.Username = user.Username + strconv.Itoa(count)
+ count += 1
+ }
+ }
+
+ if result := <-suchan; result.Err == nil {
+ return nil, model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_used.app_error", map[string]interface{}{"Service": service}, "email="+user.Email)
+ }
+
+ if result := <-euchan; result.Err == nil {
+ authService := result.Data.(*model.User).AuthService
+ if authService == "" {
+ return nil, model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error",
+ map[string]interface{}{"Service": service, "Auth": model.USER_AUTH_SERVICE_EMAIL}, "email="+user.Email)
+ } else {
+ return nil, model.NewLocAppError("CreateOAuthUser", "api.user.create_oauth_user.already_attached.app_error",
+ map[string]interface{}{"Service": service, "Auth": authService}, "email="+user.Email)
+ }
+ }
+
+ user.EmailVerified = true
+
+ ruser, err := CreateUser(user)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(teamId) > 0 {
+ err = JoinUserToTeamById(teamId, user)
+ if err != nil {
+ return nil, err
+ }
+
+ err = AddDirectChannels(teamId, user)
+ if err != nil {
+ l4g.Error(err.Error())
+ }
+ }
+
+ return ruser, nil
+}
+
+// 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) {
+ return false
+ }
+
+ if result := <-Srv.Store.User().GetByUsername(name); result.Err != nil {
+ return false
+ } else {
+ return true
+ }
+
+ return false
+}
+
+func GetUser(userId string) (*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().Get(userId); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.User), nil
+ }
+}
+
+func GetUserByUsername(username string) (*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().GetByUsername(username); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.User), nil
+ }
+}
+
+func GetUserByEmail(email string) (*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().GetByEmail(email); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.User), nil
+ }
+}
+
+func GetUserByAuth(authData *string, authService string) (*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().GetByAuth(authData, authService); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(*model.User), nil
+ }
+}
+
+func GetUserForLogin(loginId string, onlyLdap bool) (*model.User, *model.AppError) {
+ ldapAvailable := *utils.Cfg.LdapSettings.Enable && einterfaces.GetLdapInterface() != nil && utils.IsLicensed && *utils.License.Features.LDAP
+
+ if result := <-Srv.Store.User().GetForLogin(
+ loginId,
+ *utils.Cfg.EmailSettings.EnableSignInWithUsername && !onlyLdap,
+ *utils.Cfg.EmailSettings.EnableSignInWithEmail && !onlyLdap,
+ ldapAvailable,
+ ); result.Err != nil && result.Err.Id == "store.sql_user.get_for_login.multiple_users" {
+ // don't fall back to LDAP in this case since we already know there's an LDAP user, but that it shouldn't work
+ result.Err.StatusCode = http.StatusBadRequest
+ return nil, result.Err
+ } else if result.Err != nil {
+ if !ldapAvailable {
+ // failed to find user and no LDAP server to fall back on
+ result.Err.StatusCode = http.StatusBadRequest
+ return nil, result.Err
+ }
+
+ // fall back to LDAP server to see if we can find a user
+ if ldapUser, ldapErr := einterfaces.GetLdapInterface().GetUser(loginId); ldapErr != nil {
+ ldapErr.StatusCode = http.StatusBadRequest
+ return nil, ldapErr
+ } else {
+ return ldapUser, nil
+ }
+ } else {
+ return result.Data.(*model.User), nil
+ }
+}
+
+func GetUsers(offset int, limit int) (map[string]*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().GetAllProfiles(offset, limit); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(map[string]*model.User), nil
+ }
+}
+
+func GetUsersEtag() string {
+ return (<-Srv.Store.User().GetEtagForAllProfiles()).Data.(string)
+}
+
+func GetUsersInTeam(teamId string, offset int, limit int) (map[string]*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().GetProfiles(teamId, offset, limit); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(map[string]*model.User), nil
+ }
+}
+
+func GetUsersInTeamEtag(teamId string) string {
+ return (<-Srv.Store.User().GetEtagForProfiles(teamId)).Data.(string)
+}
+
+func GetUsersInChannel(channelId string, offset int, limit int) (map[string]*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().GetProfilesInChannel(channelId, offset, limit, false); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(map[string]*model.User), nil
+ }
+}
+
+func GetUsersNotInChannel(teamId string, channelId string, offset int, limit int) (map[string]*model.User, *model.AppError) {
+ if result := <-Srv.Store.User().GetProfilesNotInChannel(teamId, channelId, offset, limit); result.Err != nil {
+ return nil, result.Err
+ } else {
+ return result.Data.(map[string]*model.User), nil
+ }
+}
+
+func ActivateMfa(userId, token string) *model.AppError {
+ mfaInterface := einterfaces.GetMfaInterface()
+ if mfaInterface == nil {
+ err := model.NewLocAppError("ActivateMfa", "api.user.update_mfa.not_available.app_error", nil, "")
+ err.StatusCode = http.StatusNotImplemented
+ return err
+ }
+
+ var user *model.User
+ if result := <-Srv.Store.User().Get(userId); result.Err != nil {
+ return result.Err
+ } else {
+ user = result.Data.(*model.User)
+ }
+
+ if len(user.AuthService) > 0 && user.AuthService != model.USER_AUTH_SERVICE_LDAP {
+ return model.NewLocAppError("ActivateMfa", "api.user.activate_mfa.email_and_ldap_only.app_error", nil, "")
+ }
+
+ if err := mfaInterface.Activate(user, token); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func DeactivateMfa(userId string) *model.AppError {
+ mfaInterface := einterfaces.GetMfaInterface()
+ if mfaInterface == nil {
+ err := model.NewLocAppError("DeactivateMfa", "api.user.update_mfa.not_available.app_error", nil, "")
+ err.StatusCode = http.StatusNotImplemented
+ return err
+ }
+
+ if err := mfaInterface.Deactivate(userId); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func CreateProfileImage(username string, userId string) ([]byte, *model.AppError) {
+ colors := []color.NRGBA{
+ {197, 8, 126, 255},
+ {227, 207, 18, 255},
+ {28, 181, 105, 255},
+ {35, 188, 224, 255},
+ {116, 49, 196, 255},
+ {197, 8, 126, 255},
+ {197, 19, 19, 255},
+ {250, 134, 6, 255},
+ {227, 207, 18, 255},
+ {123, 201, 71, 255},
+ {28, 181, 105, 255},
+ {35, 188, 224, 255},
+ {116, 49, 196, 255},
+ {197, 8, 126, 255},
+ {197, 19, 19, 255},
+ {250, 134, 6, 255},
+ {227, 207, 18, 255},
+ {123, 201, 71, 255},
+ {28, 181, 105, 255},
+ {35, 188, 224, 255},
+ {116, 49, 196, 255},
+ {197, 8, 126, 255},
+ {197, 19, 19, 255},
+ {250, 134, 6, 255},
+ {227, 207, 18, 255},
+ {123, 201, 71, 255},
+ }
+
+ h := fnv.New32a()
+ h.Write([]byte(userId))
+ seed := h.Sum32()
+
+ initial := string(strings.ToUpper(username)[0])
+
+ fontBytes, err := ioutil.ReadFile(utils.FindDir("fonts") + utils.Cfg.FileSettings.InitialFont)
+ if err != nil {
+ return nil, model.NewLocAppError("CreateProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error())
+ }
+ font, err := freetype.ParseFont(fontBytes)
+ if err != nil {
+ return nil, model.NewLocAppError("CreateProfileImage", "api.user.create_profile_image.default_font.app_error", nil, err.Error())
+ }
+
+ width := int(utils.Cfg.FileSettings.ProfileWidth)
+ height := int(utils.Cfg.FileSettings.ProfileHeight)
+ color := colors[int64(seed)%int64(len(colors))]
+ dstImg := image.NewRGBA(image.Rect(0, 0, width, height))
+ srcImg := image.White
+ draw.Draw(dstImg, dstImg.Bounds(), &image.Uniform{color}, image.ZP, draw.Src)
+ size := float64((width + height) / 4)
+
+ c := freetype.NewContext()
+ c.SetFont(font)
+ c.SetFontSize(size)
+ c.SetClip(dstImg.Bounds())
+ c.SetDst(dstImg)
+ c.SetSrc(srcImg)
+
+ pt := freetype.Pt(width/6, height*2/3)
+ _, err = c.DrawString(initial, pt)
+ if err != nil {
+ return nil, model.NewLocAppError("CreateProfileImage", "api.user.create_profile_image.initial.app_error", nil, err.Error())
+ }
+
+ buf := new(bytes.Buffer)
+
+ if imgErr := png.Encode(buf, dstImg); imgErr != nil {
+ return nil, model.NewLocAppError("CreateProfileImage", "api.user.create_profile_image.encode.app_error", nil, imgErr.Error())
+ } else {
+ return buf.Bytes(), nil
+ }
+}
+
+func GetProfileImage(user *model.User) ([]byte, *model.AppError) {
+ var img []byte
+
+ if len(utils.Cfg.FileSettings.DriverName) == 0 {
+ var err *model.AppError
+ if img, err = CreateProfileImage(user.Username, user.Id); err != nil {
+ return nil, err
+ }
+ } else {
+ path := "users/" + user.Id + "/profile.png"
+
+ if data, err := ReadFile(path); err != nil {
+ if img, err = CreateProfileImage(user.Username, user.Id); err != nil {
+ return nil, err
+ }
+
+ if user.LastPictureUpdate == 0 {
+ if err := WriteFile(img, path); err != nil {
+ return nil, err
+ }
+ }
+
+ } else {
+ img = data
+ }
+ }
+
+ return img, nil
+}
diff --git a/app/user_test.go b/app/user_test.go
new file mode 100644
index 000000000..ce2249ca0
--- /dev/null
+++ b/app/user_test.go
@@ -0,0 +1,27 @@
+// Copyright (c) 2016 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "testing"
+)
+
+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()
+ }
+}
diff --git a/app/webhook.go b/app/webhook.go
index dfd59349f..70ba1d07a 100644
--- a/app/webhook.go
+++ b/app/webhook.go
@@ -8,6 +8,7 @@ import (
"io"
"io/ioutil"
"net/http"
+ "regexp"
"strings"
l4g "github.com/alecthomas/log4go"
@@ -118,6 +119,10 @@ func handleWebhookEvents(post *model.Post, team *model.Team, channel *model.Chan
}
func CreateWebhookPost(userId, teamId, channelId, text, overrideUsername, overrideIconUrl string, props model.StringInterface, postType string) (*model.Post, *model.AppError) {
+ // parse links into Markdown format
+ linkWithTextRegex := regexp.MustCompile(`<([^<\|]+)\|([^>]+)>`)
+ text = linkWithTextRegex.ReplaceAllString(text, "[${2}](${1})")
+
post := &model.Post{UserId: userId, ChannelId: channelId, Message: text, Type: postType}
post.AddProp("from_webhook", "true")
@@ -135,12 +140,41 @@ func CreateWebhookPost(userId, teamId, channelId, text, overrideUsername, overri
}
}
- post.Message = parseSlackLinksToMarkdown(post.Message)
-
if len(props) > 0 {
for key, val := range props {
if key == "attachments" {
- parseSlackAttachment(post, val)
+ if list, success := val.([]interface{}); success {
+ // parse attachment links into Markdown format
+ for i, aInt := range list {
+ attachment := aInt.(map[string]interface{})
+ if aText, ok := attachment["text"].(string); ok {
+ aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})")
+ attachment["text"] = aText
+ list[i] = attachment
+ }
+ if aText, ok := attachment["pretext"].(string); ok {
+ aText = linkWithTextRegex.ReplaceAllString(aText, "[${2}](${1})")
+ attachment["pretext"] = aText
+ list[i] = attachment
+ }
+ if fVal, ok := attachment["fields"]; ok {
+ if fields, ok := fVal.([]interface{}); ok {
+ // parse attachment field links into Markdown format
+ for j, fInt := range fields {
+ field := fInt.(map[string]interface{})
+ if fValue, ok := field["value"].(string); ok {
+ fValue = linkWithTextRegex.ReplaceAllString(fValue, "[${2}](${1})")
+ field["value"] = fValue
+ fields[j] = field
+ }
+ }
+ attachment["fields"] = fields
+ list[i] = attachment
+ }
+ }
+ }
+ post.AddProp(key, list)
+ }
} else if key != "override_icon_url" && key != "override_username" && key != "from_webhook" {
post.AddProp(key, val)
}
diff --git a/app/webtrc.go b/app/webtrc.go
new file mode 100644
index 000000000..b526c96a6
--- /dev/null
+++ b/app/webtrc.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package app
+
+import (
+ "crypto/tls"
+ "encoding/base64"
+ "net/http"
+ "strings"
+
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+func RevokeWebrtcToken(sessionId string) {
+ token := base64.StdEncoding.EncodeToString([]byte(sessionId))
+ data := make(map[string]string)
+ data["janus"] = "remove_token"
+ data["token"] = token
+ data["transaction"] = model.NewId()
+ data["admin_secret"] = *utils.Cfg.WebrtcSettings.GatewayAdminSecret
+
+ rq, _ := http.NewRequest("POST", *utils.Cfg.WebrtcSettings.GatewayAdminUrl, strings.NewReader(model.MapToJson(data)))
+ rq.Header.Set("Content-Type", "application/json")
+
+ // we do not care about the response
+ tr := &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: *utils.Cfg.ServiceSettings.EnableInsecureOutgoingConnections},
+ }
+ httpClient := &http.Client{Transport: tr}
+ httpClient.Do(rq)
+}
diff --git a/cmd/platform/oldcommands.go b/cmd/platform/oldcommands.go
index 39560af7d..faf086cb7 100644
--- a/cmd/platform/oldcommands.go
+++ b/cmd/platform/oldcommands.go
@@ -781,7 +781,7 @@ func cmdResetMfa() {
}
}
- if err := api.DeactivateMfa(user.Id); err != nil {
+ if err := app.DeactivateMfa(user.Id); err != nil {
l4g.Error("%v", err)
flushLogAndExit(1)
}
diff --git a/cmd/platform/user.go b/cmd/platform/user.go
index aabc6afcf..4fa4cd36e 100644
--- a/cmd/platform/user.go
+++ b/cmd/platform/user.go
@@ -298,7 +298,7 @@ func resetUserMfaCmdF(cmd *cobra.Command, args []string) error {
return errors.New("Unable to find user '" + args[i] + "'")
}
- if err := api.DeactivateMfa(user.Id); err != nil {
+ if err := app.DeactivateMfa(user.Id); err != nil {
return err
}
}
diff --git a/i18n/en.json b/i18n/en.json
index 84ebd2748..f4b752dc1 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -301,19 +301,19 @@
},
{
"id": "api.channel.post_update_channel_displayname_message_and_forget.create_post.error",
- "translation": "Failed to post displayname update message %v"
+ "translation": "Failed to post displayname update message"
},
{
"id": "api.channel.post_update_channel_displayname_message_and_forget.retrieve_user.error",
- "translation": "Failed to retrieve user while trying to save update channel displayname message %v"
+ "translation": "Failed to retrieve user while trying to save update channel displayname message"
},
{
"id": "api.channel.post_update_channel_displayname_message_and_forget.updated_from",
"translation": "%s updated the channel display name from: %s to: %s"
},
{
- "id": "api.channel.post_update_channel_header_message_and_forget.join_leave.error",
- "translation": "Failed to post join/leave message %v"
+ "id": "api.channel.post_update_channel_header_message_and_forget.post.error",
+ "translation": "Failed to post update channel header message"
},
{
"id": "api.channel.post_update_channel_header_message_and_forget.removed",
@@ -333,7 +333,7 @@
},
{
"id": "api.channel.post_user_add_remove_message_and_forget.error",
- "translation": "Failed to post join/leave message %v"
+ "translation": "Failed to post join/leave message"
},
{
"id": "api.channel.remove.default.app_error",
@@ -2245,7 +2245,7 @@
},
{
"id": "api.user.add_direct_channels_and_forget.failed.error",
- "translation": "Failed to add direct channel preferences for user user_id=%s, team_id=%s, err=%v"
+ "translation": "Failed to add direct channel preferences for user user_id={{.UserId}}, team_id={{.TeamId}}, err={{.Error}}"
},
{
"id": "api.user.authorize_oauth_user.bad_response.app_error",