summaryrefslogtreecommitdiffstats
path: root/api4
diff options
context:
space:
mode:
Diffstat (limited to 'api4')
-rw-r--r--api4/api.go7
-rw-r--r--api4/apitestlib.go17
-rw-r--r--api4/brand.go2
-rw-r--r--api4/brand_test.go2
-rw-r--r--api4/channel.go2
-rw-r--r--api4/channel_test.go26
-rw-r--r--api4/cluster.go2
-rw-r--r--api4/cluster_test.go2
-rw-r--r--api4/command.go128
-rw-r--r--api4/command_test.go189
-rw-r--r--api4/compliance.go2
-rw-r--r--api4/context.go27
-rw-r--r--api4/emoji.go131
-rw-r--r--api4/emoji_test.go300
-rw-r--r--api4/file.go2
-rw-r--r--api4/file_test.go2
-rw-r--r--api4/ldap.go2
-rw-r--r--api4/ldap_test.go2
-rw-r--r--api4/params.go2
-rw-r--r--api4/post.go36
-rw-r--r--api4/post_test.go183
-rw-r--r--api4/preference.go2
-rw-r--r--api4/preference_test.go2
-rw-r--r--api4/reaction.go39
-rw-r--r--api4/reaction_test.go89
-rw-r--r--api4/saml.go2
-rw-r--r--api4/saml_test.go2
-rw-r--r--api4/status.go2
-rw-r--r--api4/system.go2
-rw-r--r--api4/team.go2
-rw-r--r--api4/team_test.go2
-rw-r--r--api4/user.go72
-rw-r--r--api4/user_test.go138
-rw-r--r--api4/webhook.go2
-rw-r--r--api4/webhook_test.go2
-rw-r--r--api4/webrtc.go29
-rw-r--r--api4/webrtc_test.go29
-rw-r--r--api4/websocket.go2
-rw-r--r--api4/websocket_test.go2
39 files changed, 1449 insertions, 37 deletions
diff --git a/api4/api.go b/api4/api.go
index dffed60e4..a91fb80b5 100644
--- a/api4/api.go
+++ b/api4/api.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -47,6 +47,7 @@ type Routes struct {
Posts *mux.Router // 'api/v4/posts'
Post *mux.Router // 'api/v4/posts/{post_id:[A-Za-z0-9]+}'
PostsForChannel *mux.Router // 'api/v4/channels/{channel_id:[A-Za-z0-9]+}/posts'
+ PostsForUser *mux.Router // 'api/v4/users/{user_id:[A-Za-z0-9]+}/posts'
Files *mux.Router // 'api/v4/files'
File *mux.Router // 'api/v4/files/{file_id:[A-Za-z0-9]+}'
@@ -126,6 +127,7 @@ func InitApi(full bool) {
BaseRoutes.Posts = BaseRoutes.ApiRoot.PathPrefix("/posts").Subrouter()
BaseRoutes.Post = BaseRoutes.Posts.PathPrefix("/{post_id:[A-Za-z0-9]+}").Subrouter()
BaseRoutes.PostsForChannel = BaseRoutes.Channel.PathPrefix("/posts").Subrouter()
+ BaseRoutes.PostsForUser = BaseRoutes.User.PathPrefix("/posts").Subrouter()
BaseRoutes.Files = BaseRoutes.ApiRoot.PathPrefix("/files").Subrouter()
BaseRoutes.File = BaseRoutes.Files.PathPrefix("/{file_id:[A-Za-z0-9]+}").Subrouter()
@@ -174,6 +176,9 @@ func InitApi(full bool) {
InitCommand()
InitStatus()
InitWebSocket()
+ InitEmoji()
+ InitReaction()
+ InitWebrtc()
app.Srv.Router.Handle("/api/v4/{anything:.*}", http.HandlerFunc(Handle404))
diff --git a/api4/apitestlib.go b/api4/apitestlib.go
index bd36f49cc..81a9ca311 100644
--- a/api4/apitestlib.go
+++ b/api4/apitestlib.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -564,6 +564,21 @@ func CheckInternalErrorStatus(t *testing.T, resp *model.Response) {
}
}
+func CheckPayLoadTooLargeStatus(t *testing.T, resp *model.Response) {
+ if resp.Error == nil {
+ debug.PrintStack()
+ t.Fatal("should have errored with status:" + strconv.Itoa(http.StatusRequestEntityTooLarge))
+ return
+ }
+
+ if resp.StatusCode != http.StatusRequestEntityTooLarge {
+ debug.PrintStack()
+ t.Log("actual: " + strconv.Itoa(resp.StatusCode))
+ t.Log("expected: " + strconv.Itoa(http.StatusRequestEntityTooLarge))
+ t.Fatal("wrong status code")
+ }
+}
+
func readTestFile(name string) ([]byte, error) {
path := utils.FindDir("tests")
file, err := os.Open(path + "/" + name)
diff --git a/api4/brand.go b/api4/brand.go
index f9a301acf..ac69f623b 100644
--- a/api4/brand.go
+++ b/api4/brand.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/brand_test.go b/api4/brand_test.go
index fd5e472a8..98a539574 100644
--- a/api4/brand_test.go
+++ b/api4/brand_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/channel.go b/api4/channel.go
index 78d4cc733..acef92415 100644
--- a/api4/channel.go
+++ b/api4/channel.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/channel_test.go b/api4/channel_test.go
index c5deda83e..50755fbe0 100644
--- a/api4/channel_test.go
+++ b/api4/channel_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -715,6 +715,30 @@ func TestDeleteChannel(t *testing.T) {
t.Fatal("should have failed")
}
+ // check system admin can delete a channel without any appropriate team or channel membership.
+ sdTeam := th.CreateTeamWithClient(Client)
+ sdPublicChannel := &model.Channel{
+ DisplayName: "dn_" + model.NewId(),
+ Name: GenerateTestChannelName(),
+ Type: model.CHANNEL_OPEN,
+ TeamId: sdTeam.Id,
+ }
+ sdPublicChannel, resp = Client.CreateChannel(sdPublicChannel)
+ CheckNoError(t, resp)
+ _, resp = th.SystemAdminClient.DeleteChannel(sdPublicChannel.Id)
+ CheckNoError(t, resp)
+
+ sdPrivateChannel := &model.Channel{
+ DisplayName: "dn_" + model.NewId(),
+ Name: GenerateTestChannelName(),
+ Type: model.CHANNEL_PRIVATE,
+ TeamId: sdTeam.Id,
+ }
+ sdPrivateChannel, resp = Client.CreateChannel(sdPrivateChannel)
+ CheckNoError(t, resp)
+ _, resp = th.SystemAdminClient.DeleteChannel(sdPrivateChannel.Id)
+ CheckNoError(t, resp)
+
th.LoginBasic()
publicChannel5 := th.CreatePublicChannel()
Client.Logout()
diff --git a/api4/cluster.go b/api4/cluster.go
index dbf198590..54ff15cf1 100644
--- a/api4/cluster.go
+++ b/api4/cluster.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/cluster_test.go b/api4/cluster_test.go
index 6d44ca209..573e309a4 100644
--- a/api4/cluster_test.go
+++ b/api4/cluster_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/command.go b/api4/command.go
index d6102bd70..f44363522 100644
--- a/api4/command.go
+++ b/api4/command.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -19,7 +19,11 @@ func InitCommand() {
BaseRoutes.Commands.Handle("", ApiSessionRequired(createCommand)).Methods("POST")
BaseRoutes.Commands.Handle("", ApiSessionRequired(listCommands)).Methods("GET")
+ BaseRoutes.Command.Handle("", ApiSessionRequired(updateCommand)).Methods("PUT")
+ BaseRoutes.Command.Handle("", ApiSessionRequired(deleteCommand)).Methods("DELETE")
+
BaseRoutes.Team.Handle("/commands/autocomplete", ApiSessionRequired(listAutocompleteCommands)).Methods("GET")
+ BaseRoutes.Command.Handle("/regen_token", ApiSessionRequired(regenCommandToken)).Methods("PUT")
}
func createCommand(c *Context, w http.ResponseWriter, r *http.Request) {
@@ -49,6 +53,91 @@ func createCommand(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(rcmd.ToJson()))
}
+func updateCommand(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireCommandId()
+ if c.Err != nil {
+ return
+ }
+
+ cmd := model.CommandFromJson(r.Body)
+ if cmd == nil || cmd.Id != c.Params.CommandId {
+ c.SetInvalidParam("command")
+ return
+ }
+
+ c.LogAudit("attempt")
+
+ oldCmd, err := app.GetCommand(c.Params.CommandId)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ if cmd.TeamId != oldCmd.TeamId {
+ c.Err = model.NewAppError("updateCommand", "api.command.team_mismatch.app_error", nil, "user_id="+c.Session.UserId, http.StatusBadRequest)
+ return
+ }
+
+ if !app.SessionHasPermissionToTeam(c.Session, oldCmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) {
+ c.LogAudit("fail - inappropriate permissions")
+ c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS)
+ return
+ }
+
+ if c.Session.UserId != oldCmd.CreatorId && !app.SessionHasPermissionToTeam(c.Session, oldCmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) {
+ c.LogAudit("fail - inappropriate permissions")
+ c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS)
+ return
+ }
+
+ rcmd, err := app.UpdateCommand(oldCmd, cmd)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ c.LogAudit("success")
+
+ w.Write([]byte(rcmd.ToJson()))
+}
+
+func deleteCommand(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireCommandId()
+ if c.Err != nil {
+ return
+ }
+
+ c.LogAudit("attempt")
+
+ cmd, err := app.GetCommand(c.Params.CommandId)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ if !app.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) {
+ c.LogAudit("fail - inappropriate permissions")
+ c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS)
+ return
+ }
+
+ if c.Session.UserId != cmd.CreatorId && !app.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) {
+ c.LogAudit("fail - inappropriate permissions")
+ c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS)
+ return
+ }
+
+ err = app.DeleteCommand(cmd.Id)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ c.LogAudit("success")
+
+ ReturnStatusOK(w)
+}
+
func listCommands(c *Context, w http.ResponseWriter, r *http.Request) {
customOnly, failConv := strconv.ParseBool(r.URL.Query().Get("custom_only"))
if failConv != nil {
@@ -113,3 +202,40 @@ func listAutocompleteCommands(c *Context, w http.ResponseWriter, r *http.Request
w.Write([]byte(model.CommandListToJson(commands)))
}
+
+func regenCommandToken(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireCommandId()
+ if c.Err != nil {
+ return
+ }
+
+ c.LogAudit("attempt")
+ cmd, err := app.GetCommand(c.Params.CommandId)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ if !app.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_SLASH_COMMANDS) {
+ c.LogAudit("fail - inappropriate permissions")
+ c.SetPermissionError(model.PERMISSION_MANAGE_SLASH_COMMANDS)
+ return
+ }
+
+ if c.Session.UserId != cmd.CreatorId && !app.SessionHasPermissionToTeam(c.Session, cmd.TeamId, model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS) {
+ c.LogAudit("fail - inappropriate permissions")
+ c.SetPermissionError(model.PERMISSION_MANAGE_OTHERS_SLASH_COMMANDS)
+ return
+ }
+
+ rcmd, err := app.RegenCommandToken(cmd)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ resp := make(map[string]string)
+ resp["token"] = rcmd.Token
+
+ w.Write([]byte(model.MapToJson(resp)))
+}
diff --git a/api4/command_test.go b/api4/command_test.go
index 75842886c..0aaca3c0f 100644
--- a/api4/command_test.go
+++ b/api4/command_test.go
@@ -1,12 +1,12 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
import (
"testing"
- // "time"
+ "github.com/mattermost/platform/app"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
)
@@ -60,6 +60,155 @@ func TestCreateCommand(t *testing.T) {
CheckErrorMessage(t, resp, "api.command.disabled.app_error")
}
+func TestUpdateCommand(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.SystemAdminClient
+ user := th.SystemAdminUser
+ team := th.BasicTeam
+
+ enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
+ defer func() {
+ utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
+ }()
+ *utils.Cfg.ServiceSettings.EnableCommands = true
+
+ cmd1 := &model.Command{
+ CreatorId: user.Id,
+ TeamId: team.Id,
+ URL: "http://nowhere.com",
+ Method: model.COMMAND_METHOD_POST,
+ Trigger: "trigger1",
+ }
+
+ cmd1, _ = app.CreateCommand(cmd1)
+
+ cmd2 := &model.Command{
+ CreatorId: GenerateTestId(),
+ TeamId: team.Id,
+ URL: "http://nowhere.com/change",
+ Method: model.COMMAND_METHOD_GET,
+ Trigger: "trigger2",
+ Id: cmd1.Id,
+ Token: "tokenchange",
+ }
+
+ rcmd, resp := Client.UpdateCommand(cmd2)
+ CheckNoError(t, resp)
+
+ if rcmd.Trigger != cmd2.Trigger {
+ t.Fatal("Trigger should have updated")
+ }
+
+ if rcmd.Method != cmd2.Method {
+ t.Fatal("Method should have updated")
+ }
+
+ if rcmd.URL != cmd2.URL {
+ t.Fatal("URL should have updated")
+ }
+
+ if rcmd.CreatorId != cmd1.CreatorId {
+ t.Fatal("CreatorId should have not updated")
+ }
+
+ if rcmd.Token != cmd1.Token {
+ t.Fatal("Token should have not updated")
+ }
+
+ cmd2.Id = GenerateTestId()
+
+ rcmd, resp = Client.UpdateCommand(cmd2)
+ CheckNotFoundStatus(t, resp)
+
+ if rcmd != nil {
+ t.Fatal("should be empty")
+ }
+
+ cmd2.Id = "junk"
+
+ _, resp = Client.UpdateCommand(cmd2)
+ CheckBadRequestStatus(t, resp)
+
+ cmd2.Id = cmd1.Id
+ cmd2.TeamId = GenerateTestId()
+
+ _, resp = Client.UpdateCommand(cmd2)
+ CheckBadRequestStatus(t, resp)
+
+ cmd2.TeamId = team.Id
+
+ _, resp = th.Client.UpdateCommand(cmd2)
+ CheckForbiddenStatus(t, resp)
+
+ Client.Logout()
+ _, resp = Client.UpdateCommand(cmd2)
+ CheckUnauthorizedStatus(t, resp)
+}
+
+func TestDeleteCommand(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.SystemAdminClient
+ user := th.SystemAdminUser
+ team := th.BasicTeam
+
+ enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
+ defer func() {
+ utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
+ }()
+ *utils.Cfg.ServiceSettings.EnableCommands = true
+
+ cmd1 := &model.Command{
+ CreatorId: user.Id,
+ TeamId: team.Id,
+ URL: "http://nowhere.com",
+ Method: model.COMMAND_METHOD_POST,
+ Trigger: "trigger1",
+ }
+
+ rcmd1, _ := app.CreateCommand(cmd1)
+
+ ok, resp := Client.DeleteCommand(rcmd1.Id)
+ CheckNoError(t, resp)
+
+ if !ok {
+ t.Fatal("should have returned true")
+ }
+
+ rcmd1, _ = app.GetCommand(rcmd1.Id)
+ if rcmd1 != nil {
+ t.Fatal("should be nil")
+ }
+
+ ok, resp = Client.DeleteCommand("junk")
+ CheckBadRequestStatus(t, resp)
+
+ if ok {
+ t.Fatal("should have returned false")
+ }
+
+ _, resp = Client.DeleteCommand(GenerateTestId())
+ CheckNotFoundStatus(t, resp)
+
+ cmd2 := &model.Command{
+ CreatorId: user.Id,
+ TeamId: team.Id,
+ URL: "http://nowhere.com",
+ Method: model.COMMAND_METHOD_POST,
+ Trigger: "trigger2",
+ }
+
+ rcmd2, _ := app.CreateCommand(cmd2)
+
+ _, resp = th.Client.DeleteCommand(rcmd2.Id)
+ CheckForbiddenStatus(t, resp)
+
+ Client.Logout()
+ _, resp = Client.DeleteCommand(rcmd2.Id)
+ CheckUnauthorizedStatus(t, resp)
+}
+
func TestListCommands(t *testing.T) {
th := Setup().InitBasic().InitSystemAdmin()
defer TearDown()
@@ -196,3 +345,39 @@ func TestListAutocompleteCommands(t *testing.T) {
}
})
}
+
+func TestRegenToken(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ enableCommands := *utils.Cfg.ServiceSettings.EnableCommands
+ defer func() {
+ utils.Cfg.ServiceSettings.EnableCommands = &enableCommands
+ }()
+ *utils.Cfg.ServiceSettings.EnableCommands = true
+
+ newCmd := &model.Command{
+ CreatorId: th.BasicUser.Id,
+ TeamId: th.BasicTeam.Id,
+ URL: "http://nowhere.com",
+ Method: model.COMMAND_METHOD_POST,
+ Trigger: "trigger"}
+
+ createdCmd, resp := th.SystemAdminClient.CreateCommand(newCmd)
+ CheckNoError(t, resp)
+ CheckCreatedStatus(t, resp)
+
+ token, resp := th.SystemAdminClient.RegenCommandToken(createdCmd.Id)
+ CheckNoError(t, resp)
+ if token == createdCmd.Token {
+ t.Fatal("should update the token")
+ }
+
+ token, resp = Client.RegenCommandToken(createdCmd.Id)
+ CheckForbiddenStatus(t, resp)
+ if token != "" {
+ t.Fatal("should not return the token")
+ }
+
+}
diff --git a/api4/compliance.go b/api4/compliance.go
index 37196c853..cabac6e21 100644
--- a/api4/compliance.go
+++ b/api4/compliance.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/context.go b/api4/context.go
index 484a6432f..847a8d55f 100644
--- a/api4/context.go
+++ b/api4/context.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -242,8 +242,7 @@ func (c *Context) IsSystemAdmin() bool {
func (c *Context) SessionRequired() {
if len(c.Session.UserId) == 0 {
- c.Err = model.NewLocAppError("", "api.context.session_expired.app_error", nil, "UserRequired")
- c.Err.StatusCode = http.StatusUnauthorized
+ c.Err = model.NewAppError("", "api.context.session_expired.app_error", nil, "UserRequired", http.StatusUnauthorized)
return
}
}
@@ -406,6 +405,17 @@ func (c *Context) RequireReportId() *Context {
return c
}
+func (c *Context) RequireEmojiId() *Context {
+ if c.Err != nil {
+ return c
+ }
+
+ if len(c.Params.EmojiId) != 26 {
+ c.SetInvalidUrlParam("emoji_id")
+ }
+ return c
+}
+
func (c *Context) RequireTeamName() *Context {
if c.Err != nil {
return c
@@ -477,3 +487,14 @@ func (c *Context) RequireHookId() *Context {
return c
}
+
+func (c *Context) RequireCommandId() *Context {
+ if c.Err != nil {
+ return c
+ }
+
+ if len(c.Params.CommandId) != 26 {
+ c.SetInvalidUrlParam("command_id")
+ }
+ return c
+}
diff --git a/api4/emoji.go b/api4/emoji.go
new file mode 100644
index 000000000..ff4919860
--- /dev/null
+++ b/api4/emoji.go
@@ -0,0 +1,131 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api4
+
+import (
+ "net/http"
+ "strings"
+
+ l4g "github.com/alecthomas/log4go"
+
+ "github.com/mattermost/platform/app"
+ "github.com/mattermost/platform/einterfaces"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+func InitEmoji() {
+ l4g.Debug(utils.T("api.emoji.init.debug"))
+
+ BaseRoutes.Emojis.Handle("", ApiSessionRequired(createEmoji)).Methods("POST")
+ BaseRoutes.Emojis.Handle("", ApiSessionRequired(getEmojiList)).Methods("GET")
+ BaseRoutes.Emoji.Handle("", ApiSessionRequired(deleteEmoji)).Methods("DELETE")
+ BaseRoutes.Emoji.Handle("", ApiSessionRequired(getEmoji)).Methods("GET")
+}
+
+func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !*utils.Cfg.ServiceSettings.EnableCustomEmoji {
+ c.Err = model.NewAppError("createEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented)
+ return
+ }
+
+ if emojiInterface := einterfaces.GetEmojiInterface(); emojiInterface != nil &&
+ !emojiInterface.CanUserCreateEmoji(c.Session.Roles, c.Session.TeamMembers) {
+ c.Err = model.NewAppError("getEmoji", "api.emoji.disabled.app_error", nil, "user_id="+c.Session.UserId, http.StatusUnauthorized)
+ return
+ }
+
+ if len(utils.Cfg.FileSettings.DriverName) == 0 {
+ c.Err = model.NewAppError("createEmoji", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented)
+ return
+ }
+
+ if r.ContentLength > app.MaxEmojiFileSize {
+ c.Err = model.NewAppError("createEmoji", "api.emoji.create.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge)
+ return
+ }
+
+ if err := r.ParseMultipartForm(app.MaxEmojiFileSize); err != nil {
+ c.Err = model.NewAppError("createEmoji", "api.emoji.create.parse.app_error", nil, err.Error(), http.StatusBadRequest)
+ return
+ }
+
+ m := r.MultipartForm
+ props := m.Value
+
+ emoji := model.EmojiFromJson(strings.NewReader(props["emoji"][0]))
+ if emoji == nil {
+ c.SetInvalidParam("createEmoji")
+ return
+ }
+
+ newEmoji, err := app.CreateEmoji(c.Session.UserId, emoji, m)
+ if err != nil {
+ c.Err = err
+ return
+ } else {
+ w.Write([]byte(newEmoji.ToJson()))
+ }
+}
+
+func getEmojiList(c *Context, w http.ResponseWriter, r *http.Request) {
+ if !*utils.Cfg.ServiceSettings.EnableCustomEmoji {
+ c.Err = model.NewAppError("getEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented)
+ return
+ }
+
+ listEmoji, err := app.GetEmojiList()
+ if err != nil {
+ c.Err = err
+ return
+ } else {
+ w.Write([]byte(model.EmojiListToJson(listEmoji)))
+ }
+}
+
+func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireEmojiId()
+ if c.Err != nil {
+ return
+ }
+
+ emoji, err := app.GetEmoji(c.Params.EmojiId)
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ if c.Session.UserId != emoji.CreatorId && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
+ c.Err = model.NewAppError("deleteImage", "api.emoji.delete.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusUnauthorized)
+ return
+ }
+
+ err = app.DeleteEmoji(emoji)
+ if err != nil {
+ c.Err = err
+ return
+ } else {
+ ReturnStatusOK(w)
+ }
+}
+
+func getEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireEmojiId()
+ if c.Err != nil {
+ return
+ }
+
+ if !*utils.Cfg.ServiceSettings.EnableCustomEmoji {
+ c.Err = model.NewAppError("getEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented)
+ return
+ }
+
+ emoji, err := app.GetEmoji(c.Params.EmojiId)
+ if err != nil {
+ c.Err = err
+ return
+ } else {
+ w.Write([]byte(emoji.ToJson()))
+ }
+}
diff --git a/api4/emoji_test.go b/api4/emoji_test.go
new file mode 100644
index 000000000..23188a3d2
--- /dev/null
+++ b/api4/emoji_test.go
@@ -0,0 +1,300 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api4
+
+import (
+ "testing"
+
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+func TestCreateEmoji(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ EnableCustomEmoji := *utils.Cfg.ServiceSettings.EnableCustomEmoji
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji
+ }()
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = false
+
+ emoji := &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ // try to create an emoji when they're disabled
+ _, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckNotImplementedStatus(t, resp)
+
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = true
+ // try to create a valid gif emoji when they're enabled
+ newEmoji, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+ if newEmoji.Name != emoji.Name {
+ t.Fatal("create with wrong name")
+ }
+
+ // try to create an emoji with a duplicate name
+ emoji2 := &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: newEmoji.Name,
+ }
+ _, resp = Client.CreateEmoji(emoji2, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckBadRequestStatus(t, resp)
+ CheckErrorMessage(t, resp, "api.emoji.create.duplicate.app_error")
+
+ // try to create a valid animated gif emoji
+ emoji = &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+ if newEmoji.Name != emoji.Name {
+ t.Fatal("create with wrong name")
+ }
+
+ // try to create a valid jpeg emoji
+ emoji = &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestJpeg(t, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+ if newEmoji.Name != emoji.Name {
+ t.Fatal("create with wrong name")
+ }
+
+ // try to create a valid png emoji
+ emoji = &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestPng(t, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+ if newEmoji.Name != emoji.Name {
+ t.Fatal("create with wrong name")
+ }
+
+ // try to create an emoji that's too wide
+ emoji = &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 1000, 10), "image.gif")
+ CheckNoError(t, resp)
+ if newEmoji.Name != emoji.Name {
+ t.Fatal("create with wrong name")
+ }
+
+ // try to create an emoji that's too tall
+ emoji = &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 1000), "image.gif")
+ CheckNoError(t, resp)
+ if newEmoji.Name != emoji.Name {
+ t.Fatal("create with wrong name")
+ }
+
+ // try to create an emoji that's too large
+ emoji = &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ _, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 100, 100, 10000), "image.gif")
+ if resp.Error == nil {
+ t.Fatal("should fail - emoji is too big")
+ }
+
+ // try to create an emoji with data that isn't an image
+ emoji = &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ _, resp = Client.CreateEmoji(emoji, make([]byte, 100, 100), "image.gif")
+ CheckBadRequestStatus(t, resp)
+ CheckErrorMessage(t, resp, "api.emoji.upload.image.app_error")
+
+ // try to create an emoji as another user
+ emoji = &model.Emoji{
+ CreatorId: th.BasicUser2.Id,
+ Name: model.NewId(),
+ }
+
+ _, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckForbiddenStatus(t, resp)
+}
+
+func TestGetEmojiList(t *testing.T) {
+ th := Setup().InitBasic()
+ defer TearDown()
+ Client := th.Client
+
+ EnableCustomEmoji := *utils.Cfg.ServiceSettings.EnableCustomEmoji
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji
+ }()
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = true
+
+ emojis := []*model.Emoji{
+ {
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ },
+ {
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ },
+ {
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ },
+ }
+
+ for idx, emoji := range emojis {
+ emoji, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+ emojis[idx] = emoji
+ }
+
+ listEmoji, resp := Client.GetEmojiList()
+ CheckNoError(t, resp)
+ for _, emoji := range emojis {
+ found := false
+ for _, savedEmoji := range listEmoji {
+ if emoji.Id == savedEmoji.Id {
+ found = true
+ break
+ }
+ }
+ if !found {
+ t.Fatalf("failed to get emoji with id %v", emoji.Id)
+ }
+ }
+
+ _, resp = Client.DeleteEmoji(emojis[0].Id)
+ CheckNoError(t, resp)
+ listEmoji, resp = Client.GetEmojiList()
+ CheckNoError(t, resp)
+ found := false
+ for _, savedEmoji := range listEmoji {
+ if savedEmoji.Id == emojis[0].Id {
+ found = true
+ break
+ }
+ if found {
+ t.Fatalf("should not get a deleted emoji %v", emojis[0].Id)
+ }
+ }
+
+}
+
+func TestDeleteEmoji(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ EnableCustomEmoji := *utils.Cfg.ServiceSettings.EnableCustomEmoji
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji
+ }()
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = true
+
+ emoji := &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ newEmoji, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+
+ ok, resp := Client.DeleteEmoji(newEmoji.Id)
+ CheckNoError(t, resp)
+ if ok != true {
+ t.Fatal("should return true")
+ } else {
+ _, err := Client.GetEmoji(newEmoji.Id)
+ if err == nil {
+ t.Fatal("should not return the emoji it was deleted")
+ }
+ }
+
+ //Admin can delete other users emoji
+ newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+
+ ok, resp = th.SystemAdminClient.DeleteEmoji(newEmoji.Id)
+ CheckNoError(t, resp)
+ if ok != true {
+ t.Fatal("should return true")
+ } else {
+ _, err := th.SystemAdminClient.GetEmoji(newEmoji.Id)
+ if err == nil {
+ t.Fatal("should not return the emoji it was deleted")
+ }
+ }
+
+ // Try to delete just deleted emoji
+ _, resp = Client.DeleteEmoji(newEmoji.Id)
+ CheckInternalErrorStatus(t, resp)
+
+ //Try to delete non-existing emoji
+ _, resp = Client.DeleteEmoji(model.NewId())
+ CheckInternalErrorStatus(t, resp)
+
+ //Try to delete without Id
+ _, resp = Client.DeleteEmoji("")
+ CheckNotFoundStatus(t, resp)
+
+ //Try to delete other user's custom emoji
+ newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+
+ Client.Logout()
+ th.LoginBasic2()
+ ok, resp = Client.DeleteEmoji(newEmoji.Id)
+ CheckUnauthorizedStatus(t, resp)
+}
+
+func TestGetEmoji(t *testing.T) {
+ th := Setup().InitBasic()
+ defer TearDown()
+ Client := th.Client
+
+ EnableCustomEmoji := *utils.Cfg.ServiceSettings.EnableCustomEmoji
+ defer func() {
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = EnableCustomEmoji
+ }()
+ *utils.Cfg.ServiceSettings.EnableCustomEmoji = true
+
+ emoji := &model.Emoji{
+ CreatorId: th.BasicUser.Id,
+ Name: model.NewId(),
+ }
+
+ newEmoji, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
+ CheckNoError(t, resp)
+
+ emoji, resp = Client.GetEmoji(newEmoji.Id)
+ CheckNoError(t, resp)
+ if emoji.Id != newEmoji.Id {
+ t.Fatal("wrong emoji was returned")
+ }
+
+ _, resp = Client.GetEmoji(model.NewId())
+ CheckInternalErrorStatus(t, resp)
+
+}
diff --git a/api4/file.go b/api4/file.go
index e4bdbcc3c..de1ce454d 100644
--- a/api4/file.go
+++ b/api4/file.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/file_test.go b/api4/file_test.go
index 5e0824d45..9124e893b 100644
--- a/api4/file_test.go
+++ b/api4/file_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/ldap.go b/api4/ldap.go
index e138fdc97..eac5778b6 100644
--- a/api4/ldap.go
+++ b/api4/ldap.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/ldap_test.go b/api4/ldap_test.go
index d8eaedc50..6d79a92ad 100644
--- a/api4/ldap_test.go
+++ b/api4/ldap_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/params.go b/api4/params.go
index 8bb072742..fa5d96d88 100644
--- a/api4/params.go
+++ b/api4/params.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/post.go b/api4/post.go
index 5cbfeae92..f8e4cc54b 100644
--- a/api4/post.go
+++ b/api4/post.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -22,6 +22,7 @@ func InitPost() {
BaseRoutes.Post.Handle("/thread", ApiSessionRequired(getPostThread)).Methods("GET")
BaseRoutes.Post.Handle("/files/info", ApiSessionRequired(getFileInfosForPost)).Methods("GET")
BaseRoutes.PostsForChannel.Handle("", ApiSessionRequired(getPostsForChannel)).Methods("GET")
+ BaseRoutes.PostsForUser.Handle("/flagged", ApiSessionRequired(getFlaggedPostsForUser)).Methods("GET")
BaseRoutes.Team.Handle("/posts/search", ApiSessionRequired(searchPosts)).Methods("POST")
BaseRoutes.Post.Handle("", ApiSessionRequired(updatePost)).Methods("PUT")
@@ -127,6 +128,39 @@ func getPostsForChannel(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(list.ToJson()))
}
+func getFlaggedPostsForUser(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireUserId()
+ if c.Err != nil {
+ return
+ }
+
+ if !app.SessionHasPermissionToUser(c.Session, c.Params.UserId) {
+ c.SetPermissionError(model.PERMISSION_EDIT_OTHER_USERS)
+ return
+ }
+
+ channelId := r.URL.Query().Get("in_channel")
+ teamId := r.URL.Query().Get("in_team")
+
+ var posts *model.PostList
+ var err *model.AppError
+
+ if len(channelId) > 0 {
+ posts, err = app.GetFlaggedPostsForChannel(c.Params.UserId, channelId, c.Params.Page, c.Params.PerPage)
+ } else if len(teamId) > 0 {
+ posts, err = app.GetFlaggedPostsForTeam(c.Params.UserId, teamId, c.Params.Page, c.Params.PerPage)
+ } else {
+ posts, err = app.GetFlaggedPosts(c.Params.UserId, c.Params.Page, c.Params.PerPage)
+ }
+
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ w.Write([]byte(posts.ToJson()))
+}
+
func getPost(c *Context, w http.ResponseWriter, r *http.Request) {
c.RequirePostId()
if c.Err != nil {
diff --git a/api4/post_test.go b/api4/post_test.go
index e5c72ae9e..c6e9dcb59 100644
--- a/api4/post_test.go
+++ b/api4/post_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -459,6 +459,187 @@ func TestGetPostsForChannel(t *testing.T) {
CheckNoError(t, resp)
}
+func TestGetFlaggedPostsForUser(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+ user := th.BasicUser
+ team1 := th.BasicTeam
+ channel1 := th.BasicChannel
+ post1 := th.CreatePost()
+ channel2 := th.CreatePublicChannel()
+ post2 := th.CreatePostWithClient(Client, channel2)
+
+ preference := model.Preference{
+ UserId: user.Id,
+ Category: model.PREFERENCE_CATEGORY_FLAGGED_POST,
+ Name: post1.Id,
+ Value: "true",
+ }
+ Client.UpdatePreferences(user.Id, &model.Preferences{preference})
+ preference.Name = post2.Id
+ Client.UpdatePreferences(user.Id, &model.Preferences{preference})
+
+ opl := model.NewPostList()
+ opl.AddPost(post1)
+ opl.AddOrder(post1.Id)
+
+ rpl, resp := Client.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 0, 10)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 1 {
+ t.Fatal("should have returned 1 post")
+ }
+
+ if !reflect.DeepEqual(rpl.Posts, opl.Posts) {
+ t.Fatal("posts should have matched")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 0, 1)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 1 {
+ t.Fatal("should have returned 1 post")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 1, 1)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 0 {
+ t.Fatal("should be empty")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, GenerateTestId(), 0, 10)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 0 {
+ t.Fatal("should be empty")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, "junk", 0, 10)
+ CheckBadRequestStatus(t, resp)
+
+ if rpl != nil {
+ t.Fatal("should be nil")
+ }
+
+ opl.AddPost(post2)
+ opl.AddOrder(post2.Id)
+
+ rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 0, 10)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 2 {
+ t.Fatal("should have returned 2 posts")
+ }
+
+ if !reflect.DeepEqual(rpl.Posts, opl.Posts) {
+ t.Fatal("posts should have matched")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 0, 1)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 1 {
+ t.Fatal("should have returned 1 post")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 1, 1)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 1 {
+ t.Fatal("should have returned 1 post")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 1000, 10)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 0 {
+ t.Fatal("should be empty")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, GenerateTestId(), 0, 10)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 0 {
+ t.Fatal("should be empty")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, "junk", 0, 10)
+ CheckBadRequestStatus(t, resp)
+
+ if rpl != nil {
+ t.Fatal("should be nil")
+ }
+
+ channel3 := th.CreatePrivateChannel()
+ post4 := th.CreatePostWithClient(Client, channel3)
+
+ preference.Name = post4.Id
+ Client.UpdatePreferences(user.Id, &model.Preferences{preference})
+
+ opl.AddPost(post4)
+ opl.AddOrder(post4.Id)
+
+ rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 10)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 3 {
+ t.Fatal("should have returned 3 posts")
+ }
+
+ if !reflect.DeepEqual(rpl.Posts, opl.Posts) {
+ t.Fatal("posts should have matched")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 2)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 2 {
+ t.Fatal("should have returned 2 posts")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 2, 2)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 1 {
+ t.Fatal("should have returned 1 post")
+ }
+
+ rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 1000, 10)
+ CheckNoError(t, resp)
+
+ if len(rpl.Posts) != 0 {
+ t.Fatal("should be empty")
+ }
+
+ _, resp = Client.GetFlaggedPostsForUser("junk", 0, 10)
+ CheckBadRequestStatus(t, resp)
+
+ _, resp = Client.GetFlaggedPostsForUser(GenerateTestId(), 0, 10)
+ CheckForbiddenStatus(t, resp)
+
+ Client.Logout()
+
+ rpl, resp = Client.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 0, 10)
+ CheckUnauthorizedStatus(t, resp)
+
+ rpl, resp = Client.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 0, 10)
+ CheckUnauthorizedStatus(t, resp)
+
+ rpl, resp = Client.GetFlaggedPostsForUser(user.Id, 0, 10)
+ CheckUnauthorizedStatus(t, resp)
+
+ rpl, resp = th.SystemAdminClient.GetFlaggedPostsForUserInChannel(user.Id, channel1.Id, 0, 10)
+ CheckNoError(t, resp)
+
+ rpl, resp = th.SystemAdminClient.GetFlaggedPostsForUserInTeam(user.Id, team1.Id, 0, 10)
+ CheckNoError(t, resp)
+
+ rpl, resp = th.SystemAdminClient.GetFlaggedPostsForUser(user.Id, 0, 10)
+ CheckNoError(t, resp)
+}
+
func TestGetPostsAfterAndBefore(t *testing.T) {
th := Setup().InitBasic()
defer TearDown()
diff --git a/api4/preference.go b/api4/preference.go
index 9ba6b85d2..f9a5bfba6 100644
--- a/api4/preference.go
+++ b/api4/preference.go
@@ -1,4 +1,4 @@
-// // Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// // Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// // See License.txt for license information.
package api4
diff --git a/api4/preference_test.go b/api4/preference_test.go
index 02df99b9b..6efc96ff3 100644
--- a/api4/preference_test.go
+++ b/api4/preference_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/reaction.go b/api4/reaction.go
new file mode 100644
index 000000000..4deae4370
--- /dev/null
+++ b/api4/reaction.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api4
+
+import (
+ "net/http"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/app"
+ "github.com/mattermost/platform/model"
+ "github.com/mattermost/platform/utils"
+)
+
+func InitReaction() {
+ l4g.Debug(utils.T("api.reaction.init.debug"))
+
+ BaseRoutes.Post.Handle("/reactions", ApiSessionRequired(getReactions)).Methods("GET")
+}
+
+func getReactions(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequirePostId()
+ if c.Err != nil {
+ return
+ }
+
+ if !app.SessionHasPermissionToChannelByPost(c.Session, c.Params.PostId, model.PERMISSION_READ_CHANNEL) {
+ c.SetPermissionError(model.PERMISSION_READ_CHANNEL)
+ return
+ }
+
+ if reactions, err := app.GetReactionsForPost(c.Params.PostId); err != nil {
+ c.Err = err
+ return
+ } else {
+ w.Write([]byte(model.ReactionsToJson(reactions)))
+ return
+ }
+}
diff --git a/api4/reaction_test.go b/api4/reaction_test.go
new file mode 100644
index 000000000..9e0847c2d
--- /dev/null
+++ b/api4/reaction_test.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api4
+
+import (
+ "testing"
+
+ "reflect"
+
+ "github.com/mattermost/platform/app"
+ "github.com/mattermost/platform/model"
+)
+
+func TestGetReactions(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+ userId := th.BasicUser.Id
+ user2Id := th.BasicUser2.Id
+ postId := th.BasicPost.Id
+
+ userReactions := []*model.Reaction{
+ {
+ UserId: userId,
+ PostId: postId,
+ EmojiName: "smile",
+ },
+ {
+ UserId: userId,
+ PostId: postId,
+ EmojiName: "happy",
+ },
+ {
+ UserId: userId,
+ PostId: postId,
+ EmojiName: "sad",
+ },
+ {
+ UserId: user2Id,
+ PostId: postId,
+ EmojiName: "smile",
+ },
+ {
+ UserId: user2Id,
+ PostId: postId,
+ EmojiName: "sad",
+ },
+ }
+
+ var reactions []*model.Reaction
+
+ for _, userReaction := range userReactions {
+ if result := <-app.Srv.Store.Reaction().Save(userReaction); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ reactions = append(reactions, result.Data.(*model.Reaction))
+ }
+ }
+
+ rr, resp := Client.GetReactions(postId)
+ CheckNoError(t, resp)
+
+ if len(rr) != 5 {
+ t.Fatal("reactions should returned correct length")
+ }
+
+ if !reflect.DeepEqual(rr, reactions) {
+ t.Fatal("reactions should have matched")
+ }
+
+ rr, resp = Client.GetReactions("junk")
+ CheckBadRequestStatus(t, resp)
+
+ if len(rr) != 0 {
+ t.Fatal("reactions should return empty")
+ }
+
+ _, resp = Client.GetReactions(GenerateTestId())
+ CheckForbiddenStatus(t, resp)
+
+ Client.Logout()
+
+ _, resp = Client.GetReactions(postId)
+ CheckUnauthorizedStatus(t, resp)
+
+ _, resp = th.SystemAdminClient.GetReactions(postId)
+ CheckNoError(t, resp)
+}
diff --git a/api4/saml.go b/api4/saml.go
index e2c35f30d..b8167b505 100644
--- a/api4/saml.go
+++ b/api4/saml.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/saml_test.go b/api4/saml_test.go
index 7e4722a3b..0e16612d7 100644
--- a/api4/saml_test.go
+++ b/api4/saml_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/status.go b/api4/status.go
index 46d4f963b..3a2c5c762 100644
--- a/api4/status.go
+++ b/api4/status.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/system.go b/api4/system.go
index 098f7e4b7..dfc702c8c 100644
--- a/api4/system.go
+++ b/api4/system.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/team.go b/api4/team.go
index 22ab15bfd..b8ba47054 100644
--- a/api4/team.go
+++ b/api4/team.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/team_test.go b/api4/team_test.go
index b58a4dc72..7fd240480 100644
--- a/api4/team_test.go
+++ b/api4/team_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/user.go b/api4/user.go
index b30d066ab..70182c1ab 100644
--- a/api4/user.go
+++ b/api4/user.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -32,6 +32,7 @@ func InitUser() {
BaseRoutes.User.Handle("/patch", ApiSessionRequired(patchUser)).Methods("PUT")
BaseRoutes.User.Handle("", ApiSessionRequired(deleteUser)).Methods("DELETE")
BaseRoutes.User.Handle("/roles", ApiSessionRequired(updateUserRoles)).Methods("PUT")
+ BaseRoutes.User.Handle("/active", ApiSessionRequired(updateUserActive)).Methods("PUT")
BaseRoutes.User.Handle("/password", ApiSessionRequired(updatePassword)).Methods("PUT")
BaseRoutes.Users.Handle("/password/reset", ApiHandler(resetPassword)).Methods("POST")
BaseRoutes.Users.Handle("/password/reset/send", ApiHandler(sendPasswordReset)).Methods("POST")
@@ -43,6 +44,7 @@ func InitUser() {
BaseRoutes.User.Handle("/mfa/generate", ApiSessionRequired(generateMfaSecret)).Methods("POST")
BaseRoutes.Users.Handle("/login", ApiHandler(login)).Methods("POST")
+ BaseRoutes.Users.Handle("/login/switch", ApiHandler(switchAccountType)).Methods("POST")
BaseRoutes.Users.Handle("/logout", ApiHandler(logout)).Methods("POST")
BaseRoutes.UserByUsername.Handle("", ApiSessionRequired(getUserByUsername)).Methods("GET")
@@ -586,6 +588,37 @@ func updateUserRoles(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w)
}
+func updateUserActive(c *Context, w http.ResponseWriter, r *http.Request) {
+ c.RequireUserId()
+ if c.Err != nil {
+ return
+ }
+
+ props := model.StringInterfaceFromJson(r.Body)
+
+ active, ok := props["active"].(bool)
+ if !ok {
+ c.SetInvalidParam("active")
+ return
+ }
+
+ // true when you're trying to de-activate yourself
+ isSelfDeactive := !active && c.Params.UserId == c.Session.UserId
+
+ if !isSelfDeactive && !app.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
+ c.Err = model.NewLocAppError("updateUserActive", "api.user.update_active.permissions.app_error", nil, "userId="+c.Params.UserId)
+ c.Err.StatusCode = http.StatusForbidden
+ return
+ }
+
+ if ruser, err := app.UpdateActiveNoLdap(c.Params.UserId, active); err != nil {
+ c.Err = err
+ } else {
+ c.LogAuditWithUserId(ruser.Id, fmt.Sprintf("active=%v", active))
+ ReturnStatusOK(w)
+ }
+}
+
func checkUserMfa(c *Context, w http.ResponseWriter, r *http.Request) {
props := model.MapFromJson(r.Body)
@@ -981,3 +1014,40 @@ func sendVerificationEmail(c *Context, w http.ResponseWriter, r *http.Request) {
ReturnStatusOK(w)
}
+
+func switchAccountType(c *Context, w http.ResponseWriter, r *http.Request) {
+ switchRequest := model.SwitchRequestFromJson(r.Body)
+ if switchRequest == nil {
+ c.SetInvalidParam("switch_request")
+ return
+ }
+
+ link := ""
+ var err *model.AppError
+
+ if switchRequest.EmailToOAuth() {
+ link, err = app.SwitchEmailToOAuth(switchRequest.Email, switchRequest.Password, switchRequest.MfaCode, switchRequest.NewService)
+ } else if switchRequest.OAuthToEmail() {
+ c.SessionRequired()
+ if c.Err != nil {
+ return
+ }
+
+ link, err = app.SwitchOAuthToEmail(switchRequest.Email, switchRequest.NewPassword, c.Session.UserId)
+ } else if switchRequest.EmailToLdap() {
+ link, err = app.SwitchEmailToLdap(switchRequest.Email, switchRequest.Password, switchRequest.MfaCode, switchRequest.LdapId, switchRequest.NewPassword)
+ } else if switchRequest.LdapToEmail() {
+ link, err = app.SwitchLdapToEmail(switchRequest.Password, switchRequest.MfaCode, switchRequest.Email, switchRequest.NewPassword)
+ } else {
+ c.SetInvalidParam("switch_request")
+ return
+ }
+
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ c.LogAudit("success")
+ w.Write([]byte(model.MapToJson(map[string]string{"follow_link": link})))
+}
diff --git a/api4/user_test.go b/api4/user_test.go
index b3e4edc3d..95271984c 100644
--- a/api4/user_test.go
+++ b/api4/user_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
@@ -850,6 +850,49 @@ func TestUpdateUserRoles(t *testing.T) {
CheckBadRequestStatus(t, resp)
}
+func TestUpdateUserActive(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ Client := th.Client
+ SystemAdminClient := th.SystemAdminClient
+ user := th.BasicUser
+
+ pass, resp := Client.UpdateUserActive(user.Id, false)
+ CheckNoError(t, resp)
+
+ if !pass {
+ t.Fatal("should have returned true")
+ }
+
+ pass, resp = Client.UpdateUserActive(user.Id, false)
+ CheckUnauthorizedStatus(t, resp)
+
+ if pass {
+ t.Fatal("should have returned false")
+ }
+
+ th.LoginBasic2()
+
+ _, resp = Client.UpdateUserActive(user.Id, true)
+ CheckForbiddenStatus(t, resp)
+
+ _, resp = Client.UpdateUserActive(GenerateTestId(), true)
+ CheckForbiddenStatus(t, resp)
+
+ _, resp = Client.UpdateUserActive("junk", true)
+ CheckBadRequestStatus(t, resp)
+
+ Client.Logout()
+
+ _, resp = Client.UpdateUserActive(user.Id, true)
+ CheckUnauthorizedStatus(t, resp)
+
+ _, resp = SystemAdminClient.UpdateUserActive(user.Id, true)
+ CheckNoError(t, resp)
+
+ _, resp = SystemAdminClient.UpdateUserActive(user.Id, false)
+ CheckNoError(t, resp)
+}
+
func TestGetUsers(t *testing.T) {
th := Setup().InitBasic()
defer TearDown()
@@ -1297,7 +1340,7 @@ func TestUpdateUserPassword(t *testing.T) {
// Should fail because account is locked out
_, resp = Client.UpdateUserPassword(th.BasicUser.Id, th.BasicUser.Password, "newpwd")
CheckErrorMessage(t, resp, "api.user.check_user_login_attempts.too_many.app_error")
- CheckForbiddenStatus(t, resp)
+ CheckUnauthorizedStatus(t, resp)
// System admin can update another user's password
adminSetPassword := "pwdsetbyadmin"
@@ -1651,3 +1694,94 @@ func TestSetProfileImage(t *testing.T) {
t.Fatal(err)
}
}
+
+func TestSwitchAccount(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ enableGitLab := utils.Cfg.GitLabSettings.Enable
+ defer func() {
+ utils.Cfg.GitLabSettings.Enable = enableGitLab
+ }()
+ utils.Cfg.GitLabSettings.Enable = true
+
+ Client.Logout()
+
+ sr := &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_EMAIL,
+ NewService: model.USER_AUTH_SERVICE_GITLAB,
+ Email: th.BasicUser.Email,
+ Password: th.BasicUser.Password,
+ }
+
+ link, resp := Client.SwitchAccountType(sr)
+ CheckNoError(t, resp)
+
+ if link == "" {
+ t.Fatal("bad link")
+ }
+
+ th.LoginBasic()
+
+ fakeAuthData := "1"
+ if result := <-app.Srv.Store.User().UpdateAuthData(th.BasicUser.Id, model.USER_AUTH_SERVICE_GITLAB, &fakeAuthData, th.BasicUser.Email, true); result.Err != nil {
+ t.Fatal(result.Err)
+ }
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_GITLAB,
+ NewService: model.USER_AUTH_SERVICE_EMAIL,
+ Email: th.BasicUser.Email,
+ NewPassword: th.BasicUser.Password,
+ }
+
+ link, resp = Client.SwitchAccountType(sr)
+ CheckNoError(t, resp)
+
+ if link != "/login?extra=signin_change" {
+ t.Log(link)
+ t.Fatal("bad link")
+ }
+
+ Client.Logout()
+ _, resp = Client.Login(th.BasicUser.Email, th.BasicUser.Password)
+ CheckNoError(t, resp)
+ Client.Logout()
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_GITLAB,
+ NewService: model.SERVICE_GOOGLE,
+ }
+
+ _, resp = Client.SwitchAccountType(sr)
+ CheckBadRequestStatus(t, resp)
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_EMAIL,
+ NewService: model.USER_AUTH_SERVICE_GITLAB,
+ Password: th.BasicUser.Password,
+ }
+
+ _, resp = Client.SwitchAccountType(sr)
+ CheckNotFoundStatus(t, resp)
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_EMAIL,
+ NewService: model.USER_AUTH_SERVICE_GITLAB,
+ Email: th.BasicUser.Email,
+ }
+
+ _, resp = Client.SwitchAccountType(sr)
+ CheckUnauthorizedStatus(t, resp)
+
+ sr = &model.SwitchRequest{
+ CurrentService: model.USER_AUTH_SERVICE_GITLAB,
+ NewService: model.USER_AUTH_SERVICE_EMAIL,
+ Email: th.BasicUser.Email,
+ NewPassword: th.BasicUser.Password,
+ }
+
+ _, resp = Client.SwitchAccountType(sr)
+ CheckUnauthorizedStatus(t, resp)
+}
diff --git a/api4/webhook.go b/api4/webhook.go
index 86f8bcc2e..6602c7a26 100644
--- a/api4/webhook.go
+++ b/api4/webhook.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/webhook_test.go b/api4/webhook_test.go
index 1691b27e5..96451f8a7 100644
--- a/api4/webhook_test.go
+++ b/api4/webhook_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/webrtc.go b/api4/webrtc.go
new file mode 100644
index 000000000..81a1599b2
--- /dev/null
+++ b/api4/webrtc.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api4
+
+import (
+ "net/http"
+
+ l4g "github.com/alecthomas/log4go"
+ "github.com/mattermost/platform/app"
+ "github.com/mattermost/platform/utils"
+)
+
+func InitWebrtc() {
+ l4g.Debug(utils.T("api.webrtc.init.debug"))
+
+ BaseRoutes.Webrtc.Handle("/token", ApiSessionRequired(webrtcToken)).Methods("GET")
+}
+
+func webrtcToken(c *Context, w http.ResponseWriter, r *http.Request) {
+ result, err := app.GetWebrtcInfoForSession(c.Session.Id)
+
+ if err != nil {
+ c.Err = err
+ return
+ }
+
+ w.Write([]byte(result.ToJson()))
+}
diff --git a/api4/webrtc_test.go b/api4/webrtc_test.go
new file mode 100644
index 000000000..806118f76
--- /dev/null
+++ b/api4/webrtc_test.go
@@ -0,0 +1,29 @@
+// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package api4
+
+import (
+ "testing"
+
+ "github.com/mattermost/platform/utils"
+)
+
+func TestGetWebrtcToken(t *testing.T) {
+ th := Setup().InitBasic().InitSystemAdmin()
+ defer TearDown()
+ Client := th.Client
+
+ enableWebrtc := *utils.Cfg.WebrtcSettings.Enable
+ defer func() {
+ *utils.Cfg.WebrtcSettings.Enable = enableWebrtc
+ }()
+ *utils.Cfg.WebrtcSettings.Enable = false
+
+ _, resp := Client.GetWebrtcToken()
+ CheckNotImplementedStatus(t, resp)
+
+ Client.Logout()
+ _, resp = Client.GetWebrtcToken()
+ CheckUnauthorizedStatus(t, resp)
+}
diff --git a/api4/websocket.go b/api4/websocket.go
index c70327222..1caef6c4a 100644
--- a/api4/websocket.go
+++ b/api4/websocket.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4
diff --git a/api4/websocket_test.go b/api4/websocket_test.go
index 6018bf7da..1098f4759 100644
--- a/api4/websocket_test.go
+++ b/api4/websocket_test.go
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 Mattermost, Inc. All Rights Reserved.
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
package api4